Middledoc API & Integrations
Connect Middledoc to n8n, Make, Zapier, Pipedream, or any HTTP client. Use the REST API to manage clients, receive webhooks when events happen, and poll the event log for automation workflows.
Authentication
All API requests are authenticated with a bearer token. Tokens start with mdoc_ and are generated in your dashboard.
Getting your API key
- Open Settings → Integrations
- Scroll to API & Zapier Inbound
- Click Generate API Key
- Copy the key immediately — it is shown once and then hashed in storage.
Using the key
Pass the key in the Authorization header on every request:
Authorization: Bearer mdoc_your_key_hereBase URL: https://middledoc.com/api/v1
Rate limit: 60 requests per minute per API key
Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining
Example request
curl https://middledoc.com/api/v1/clients \
-H "Authorization: Bearer mdoc_your_key_here"Clients API
Clients are the contacts you collect documents from. You can create, list, and look up clients via the API.
/clientsList clients (paginated)/clientsCreate a single client/clients/bulkBulk import up to 100 clients/clients/{id}Get a single client by IDGET /clients — List clients
Returns a paginated list of all clients in your account. Supports search and pagination query params.
| Param | Type | Required | Description |
|---|---|---|---|
page | integer | optional | Page number (default: 1) |
per_page | integer | optional | Results per page, max 100 (default: 50) |
search | string | optional | Filter by name or email (case-insensitive) |
curl "https://middledoc.com/api/v1/clients?page=1&per_page=20&search=alice" \
-H "Authorization: Bearer mdoc_your_key_here"{
"data": [
{
"id": 42,
"name": "Alice Johnson",
"email": "[email protected]",
"company_name": "Johnson Consulting",
"phone": "+1 555 000 1234",
"category": "Tax",
"labels": ["VIP", "US Client"],
"created_at": "2026-06-01T09:00:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1
}
}POST /clients — Create a client
Creates a single client. Returns 201 on success. Fires the client.created webhook event.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | required | Full name of the client |
email | string | required | Email address (must be unique per account) |
company_name | string | optional | Company or firm name |
phone | string | optional | Phone number (any format) |
category | string | optional | Custom category label |
labels | string[] | optional | Array of label names to assign; created if missing |
curl -X POST https://middledoc.com/api/v1/clients \
-H "Authorization: Bearer mdoc_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Alice Johnson",
"email": "[email protected]",
"company_name": "Johnson Consulting",
"phone": "+1 555 000 1234",
"category": "Tax",
"labels": ["VIP", "US Client"]
}'{
"data": {
"id": 42,
"name": "Alice Johnson",
"email": "[email protected]",
"company_name": "Johnson Consulting",
"phone": "+1 555 000 1234",
"category": "Tax",
"labels": ["VIP", "US Client"],
"created_at": "2026-07-13T10:00:00Z"
}
}POST /clients/bulk — Bulk import
Import up to 100 clients in a single request. Send a JSON array of client objects. Each item is processed independently — rows with validation errors are skipped without failing the entire batch. Duplicate emails are silently skipped.
curl -X POST https://middledoc.com/api/v1/clients/bulk \
-H "Authorization: Bearer mdoc_your_key_here" \
-H "Content-Type: application/json" \
-d '[
{ "name": "Alice Johnson", "email": "[email protected]", "company_name": "Johnson Consulting" },
{ "name": "Bob Smith", "email": "[email protected]", "labels": ["Prospect"] },
{ "name": "Bad Row", "email": "not-an-email" }
]'{
"summary": {
"total": 3,
"imported": 2,
"skipped": 0,
"errors": 1
},
"results": [
{ "index": 0, "status": "imported", "id": 43, "name": "Alice Johnson", "email": "[email protected]" },
{ "index": 1, "status": "imported", "id": 44, "name": "Bob Smith", "email": "[email protected]" },
{ "index": 2, "status": "error", "reason": "Invalid email format" }
]
}GET /clients/{id} — Get a client
Returns a single client by numeric ID. Returns 404 with code NOT_FOUND if the client does not exist or belongs to another account.
curl https://middledoc.com/api/v1/clients/42 \
-H "Authorization: Bearer mdoc_your_key_here"{
"data": {
"id": 42,
"name": "Alice Johnson",
"email": "[email protected]",
"company_name": "Johnson Consulting",
"phone": "+1 555 000 1234",
"category": "Tax",
"labels": ["VIP", "US Client"],
"created_at": "2026-06-01T09:00:00Z"
}
}Webhooks
Webhooks let Middledoc push real-time notifications to your server or automation tool when events happen in your account. Register a URL and choose which events to subscribe to. Middledoc delivers a POST request within seconds of each event. Requires https:// URLs. Maximum 20 subscriptions per account.
Webhook endpoints
/webhooksList all webhook subscriptions/webhooksCreate a webhook subscription/webhooks/{id}Update a subscription (url, events, active)/webhooks/{id}Delete a subscription/webhooks/{id}/testSend a test event to the subscriptionCreating a subscription
| Field | Type | Required | Description |
|---|---|---|---|
url | string | required | Target URL (must start with https://) |
events | string[] | optional | Event types to subscribe to. Empty array = all events. |
description | string | optional | Human-readable label for this webhook |
secret | string | optional | Optional HMAC secret for signature verification |
curl -X POST https://middledoc.com/api/v1/webhooks \
-H "Authorization: Bearer mdoc_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourserver.com/webhook",
"events": ["client.created", "document.uploaded", "request.completed"],
"description": "n8n production webhook",
"secret": "my_signing_secret"
}'{
"data": {
"id": 7,
"url": "https://yourserver.com/webhook",
"events": ["client.created", "document.uploaded", "request.completed"],
"description": "n8n production webhook",
"is_active": true,
"created_at": "2026-07-13T10:00:00Z"
}
}Event types
| Event | When it fires |
|---|---|
client.created | A new client is added to your account |
document.uploaded | A client uploads documents via the portal |
request.completed | All documents accepted — request is complete |
request.created | A new document request is created |
invoice.paid | An invoice is marked as paid |
document.rejected | A document is rejected during review |
request.overdue | A request passes its deadline |
invoice.overdue | An invoice passes its due date |
Payload shape
Every webhook delivery is a POST with Content-Type: application/json. The body always follows this shape:
{
"event": "document.uploaded",
"timestamp": "2026-07-14T10:00:00Z",
"data": {
"client_id": 42,
"client_name": "Alice Johnson",
"document_name": "bank_statement_june.pdf",
"request_id": 18
}
}HMAC signature verification
When you supply a secret when creating a subscription, Middledoc signs each delivery. The signature is sent in the X-Webhook-Signature header as sha256=<hex>. Compute HMAC-SHA256 of the raw request body using your secret and compare.
import { createHmac } from 'node:crypto'
function verifyWebhookSignature(rawBody, secret, signatureHeader) {
// signatureHeader = "sha256=abc123..."
const expected = 'sha256=' + createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
// Use a timing-safe comparison
const a = Buffer.from(expected)
const b = Buffer.from(signatureHeader)
if (a.length !== b.length) return false
return crypto.timingSafeEqual(a, b)
}
// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature']
if (!verifyWebhookSignature(req.body, process.env.WEBHOOK_SECRET, sig)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body)
console.log('Received event:', event.event)
res.sendStatus(200)
})Events API (polling)
Every action in Middledoc is logged to an immutable event ledger. The Events API lets you poll this log — useful when you cannot expose a public webhook URL or need to replay historical events.
GET /events — Poll the event log
| Param | Type | Required | Description |
|---|---|---|---|
type | string | optional | Filter by event type, e.g. document.uploaded |
since | ISO 8601 | optional | Return events after this timestamp (exclusive) |
until | ISO 8601 | optional | Return events before this timestamp (exclusive) |
page | integer | optional | Page number (default: 1) |
per_page | integer | optional | Results per page, max 100 (default: 50) |
# Poll for all events in the last 5 minutes
curl "https://middledoc.com/api/v1/events?since=2026-07-13T10:00:00Z&type=document.uploaded" \
-H "Authorization: Bearer mdoc_your_key_here"{
"data": [
{
"id": 1001,
"event_type": "document.uploaded",
"payload": {
"client_id": 42,
"client_name": "Alice Johnson",
"document_name": "bank_statement_june.pdf"
},
"created_at": "2026-07-13T10:02:31Z"
}
],
"pagination": {
"page": 1,
"per_page": 50,
"total": 1,
"total_pages": 1
}
}Poll every 5 minutes — a typical n8n schedule trigger pattern:
// Store the last-polled timestamp somewhere persistent
const since = lastPolledAt.toISOString()
const url = `https://middledoc.com/api/v1/events?since=${since}`
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.MIDDLEDOC_API_KEY}` },
})
const { data } = await res.json()
// Process data...
lastPolledAt = new Date()n8n Integration Guide
n8n supports both push (webhook) and pull (scheduled HTTP request) patterns with Middledoc. Use push for real-time automation; use pull when your n8n instance is behind a private network.
Push: Middledoc triggers n8n
- 1
Generate your API key
In Middledoc: Settings → Integrations → API & Zapier Inbound → Generate. Copy the key — it is only shown once.
- 2
Add a Webhook node in n8n
Create a new workflow. Add a "Webhook" trigger node. Set Method to POST, note the generated webhook URL.
- 3
Register the URL in Middledoc
Go to Settings → Integrations → Webhooks → Add webhook. Paste the n8n webhook URL. Select the events you want (e.g.
document.uploaded). Optionally add a signing secret and copy it into n8n for verification. - 4
Build your n8n logic
Add downstream nodes — Slack, Google Sheets, email, database write, etc. The webhook body contains event, timestamp, and data fields.
Pull: n8n polls Middledoc on a schedule
- 1
Add a Schedule Trigger node
Set the interval (e.g. every 5 minutes). This fires the workflow on a timer.
- 2
Add an HTTP Request node
Method:
GET. URL:https://middledoc.com/api/v1/events. Add headerAuthorization: Bearer mdoc_your_key_here. Add query params:since(use an expression referencing your last-run timestamp). - 3
Process the results
Add an "Item Lists" or "Split In Batches" node to loop over returned events. Route by event_type using an IF or Switch node.
Zapier Integration Guide
Middledoc integrates with Zapier in both directions: Zapier can receive events from Middledoc (outbound / Trigger) and Zapier can write data back to Middledoc (inbound / Action).
Outbound: Middledoc triggers Zapier
- 1
Create a Zap with "Webhooks by Zapier" as trigger
Choose Catch Hook. Copy the custom webhook URL Zapier gives you.
- 2
Register the URL in Middledoc
Go to Settings → Integrations → Zapier and paste the Catch Hook URL. Alternatively, go to Settings → Integrations → Webhooks → Add to register it via the API.
- 3
Test the trigger
Perform an action in Middledoc (e.g. add a client) and click "Test trigger" in Zapier to confirm the payload arrives.
- 4
Add Zapier action steps
Connect to Google Sheets, Slack, HubSpot, Airtable, or any other Zapier integration using the event fields (event, timestamp, data.*).
Inbound: Zapier writes to Middledoc
Use Webhooks by Zapier as an Action in any Zap to create clients in Middledoc:
- 1
Add a "Webhooks by Zapier" Action step
Choose "POST" as the event type.
- 2
Set the URL
https://middledoc.com/api/v1/clientsfor a single client, orhttps://middledoc.com/api/v1/clients/bulkto import multiple. - 3
Add the Authorization header
Key:
Authorization— Value:Bearer mdoc_your_key_here - 4
Map the payload
Set Content-Type to application/json. Map name and email from your trigger step fields.
Error Reference
All error responses are JSON with an error message and a machine-readable code:
{ "error": "A client with this email already exists", "code": "CONFLICT" }| HTTP | Code | Meaning |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid API key |
400 | VALIDATION_ERROR | Request body or params failed validation |
409 | CONFLICT | Resource already exists (e.g. duplicate email) |
404 | NOT_FOUND | Resource not found or not owned by your account |
429 | RATE_LIMITED | Exceeded 60 req/min — back off and retry |
500 | INTERNAL_ERROR | Unexpected server error — contact support |