API & MCP Reference

Everything you need to integrate with Intake — via REST API or MCP. Base URL: https://dointake.com/api/v1

Introduction

Intake offers two integration paths: a REST API and an MCP server (Model Context Protocol). Both provide full access to clients, document requests, forms, and webhooks.

REST API Base URL: https://dointake.com/api/v1

MCP Server: Connect any MCP-compatible client (Claude, Cursor, etc.) — see the MCP Protocol section for setup.

All REST requests and responses use JSON. Timestamps are returned in ISO 8601 format. All resource IDs are opaque strings.

Plan limits:

FreePro ($19/month)
Clients10Unlimited
Deals3Unlimited
Parties per deal5Unlimited

Authentication

Authenticate by including your API key in the Authorization header as a Bearer token.

API keys are created under Avatar menu → Settings → API Keys. Each key has scopes that control access:

ScopeAccess
readList and retrieve resources
writeCreate and update resources
webhooksManage webhook subscriptions

Rate limiting: 120 requests per minute per API key. When exceeded, the API returns 429 with a Retry-After header.

Test vs. Live Keys

API keys have a mode that controls data access:

Key PrefixModeData Access
dk_live_...LiveProduction data only
dk_test_...TestSandbox data only

Test keys create and access isolated sandbox data — your production data is never affected. Test keys also bypass plan limits, so you can test freely without upgrading.

Create test keys in Settings → API Keys by selecting "Test" mode.

GET /api/v1/me

Test your API key

Returns information about the authenticated user.

Scope: read
Request
curl https://dointake.com/api/v1/me \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": 1,
    "name": "Your Name",
    "email": "[email protected]",
    "subscription_status": "active"
  }
}

Errors

The API uses standard HTTP status codes. Errors return a JSON object with an error key containing code and message fields.

StatusMeaning
200Success
201Created
204Deleted (no content)
401Unauthorized — missing or invalid API key
403Forbidden — insufficient scope or plan limit exceeded
404Not found
422Validation error
429Rate limited
500Internal server error
GET /api/v1/...

Error response format

All errors follow this structure. Validation errors include a `details` object mapping field names to error messages.

Response
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "details": {
      "email": [
        "can't be blank"
      ]
    }
  }
}

Pagination

List endpoints return all matching records. Use query parameters to filter results:

  • Clients: ?search=term to filter by name or email; ?external_id=x&external_source=y to match by external system; ?tags=vip,residential to filter by tags
  • Requests: ?status=pending&client_id=xxx&deal_id=xxx
  • Deals: ?status=active; ?external_id=x&external_source=y to match by external system

Clients

Manage your clients. Clients are the people you collect documents from.

GET /api/v1/clients

List clients

Returns all clients for your account. Optionally filter by search term, external ID, or tags.

Scope: read
Parameter Type Required Description
search string optional Filter by name or email
external_id string optional Filter by external ID
external_source string optional Filter by external source (used with external_id)
tags string optional Filter by tags (comma-separated)
Request
curl https://dointake.com/api/v1/clients?search=jane \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "metadata": {
        "lead_score": 85
      },
      "email": "[email protected]",
      "updated_at": "2025-01-15T10:30:00Z",
      "tags": [
        "vip",
        "residential"
      ],
      "created_at": "2025-01-15T10:30:00Z",
      "phone": "+1234567890",
      "external_id": "contact_123",
      "external_source": "salesforce",
      "notification_preference": "email",
      "sms_consent": null,
      "preferred_language": "en",
      "email_undeliverable_at": null
    }
  ]
}
GET /api/v1/clients/:id

Get a client

Retrieve a single client by their ID.

Scope: read
Parameter Type Required Description
id string required Client ID
Request
curl https://dointake.com/api/v1/clients/cl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "cl_abc123",
    "name": "Jane Smith",
    "metadata": {
      "lead_score": 85
    },
    "email": "[email protected]",
    "updated_at": "2025-01-15T10:30:00Z",
    "tags": [
      "vip",
      "residential"
    ],
    "created_at": "2025-01-15T10:30:00Z",
    "phone": "+1234567890",
    "external_id": "contact_123",
    "external_source": "salesforce",
    "notification_preference": "external",
    "sms_consent": {
      "method": "verbal",
      "attested_at": "2025-01-15T10:28:00Z"
    },
    "preferred_language": "en",
    "email_undeliverable_at": null
  }
}
POST /api/v1/clients

Create a client

Create a new client.

Scope: write
Parameter Type Required Description
name string required Client's full name
email string required Client's email address
phone string optional Client's phone number
external_id string optional External system ID
external_source string optional External system name (e.g. salesforce, hubspot)
metadata object optional Arbitrary key-value metadata
tags array optional List of string tags
preferred_language string optional Portal & email language: en, pl, nl, or es (default en)
notification_preference string optional email (Intake emails the client, default) or external (your system delivers notifications, e.g. SMS from webhook events)
sms_consent_method string optional How the client consented to texts: verbal, written, or portal. Setting it records an attestation (timestamp + recording user); sms_consent_attested_at may be supplied to backdate
Request
curl -X POST https://dointake.com/api/v1/clients \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Jane Smith", "email": "[email protected]"}'
Response
{
  "data": {
    "id": "cl_abc123",
    "name": "Jane Smith",
    "metadata": {},
    "email": "[email protected]",
    "updated_at": "2025-01-15T10:30:00Z",
    "tags": [],
    "created_at": "2025-01-15T10:30:00Z",
    "phone": null,
    "external_id": null,
    "external_source": null,
    "notification_preference": "email",
    "sms_consent": null,
    "preferred_language": "en",
    "email_undeliverable_at": null
  }
}
PATCH /api/v1/clients/:id

Update a client

Update a client's name, email, phone, or metadata.

Scope: write
Parameter Type Required Description
id string required Client ID
name string optional Client's full name
email string optional Client's email address
phone string optional Client's phone number
external_id string optional External system ID
external_source string optional External system name
metadata object optional Arbitrary key-value metadata
tags array optional List of string tags
preferred_language string optional Portal & email language: en, pl, nl, or es (default en)
notification_preference string optional email (Intake emails the client, default) or external (your system delivers notifications, e.g. SMS from webhook events)
sms_consent_method string optional How the client consented to texts: verbal, written, or portal. Setting it records an attestation (timestamp + recording user); sms_consent_attested_at may be supplied to backdate
Request
curl -X PATCH https://dointake.com/api/v1/clients/cl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Jane Doe", "phone": "+1987654321"}'
Response
{
  "data": {
    "id": "cl_abc123",
    "name": "Jane Doe",
    "metadata": {},
    "email": "[email protected]",
    "updated_at": "2025-01-20T14:00:00Z",
    "tags": [],
    "created_at": "2025-01-15T10:30:00Z",
    "phone": "+1987654321",
    "external_id": null,
    "external_source": null,
    "notification_preference": "email",
    "sms_consent": null,
    "preferred_language": "en",
    "email_undeliverable_at": null
  }
}
PUT /api/v1/clients/upsert

Upsert a client

Create or update a client by matching on `external_id` + `external_source`, or by `email` if no external ID is provided. Returns `201` if a new client was created, `200` if an existing client was updated.

Scope: write
Parameter Type Required Description
email string required Client's email address
name string optional Client's full name
phone string optional Client's phone number
external_id string optional External system ID (used for matching)
external_source string optional External system name (used with external_id for matching)
metadata object optional Arbitrary key-value metadata
tags array optional List of string tags
preferred_language string optional Portal & email language: en, pl, nl, or es (default en)
notification_preference string optional email (Intake emails the client, default) or external (your system delivers notifications, e.g. SMS from webhook events)
sms_consent_method string optional How the client consented to texts: verbal, written, or portal. Setting it records an attestation (timestamp + recording user); sms_consent_attested_at may be supplied to backdate
Request
curl -X PUT https://dointake.com/api/v1/clients/upsert \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "name": "Jane Smith", "external_id": "contact_123", "external_source": "salesforce"}'
Response
{
  "data": {
    "id": "cl_abc123",
    "name": "Jane Smith",
    "metadata": {},
    "email": "[email protected]",
    "updated_at": "2025-01-15T10:30:00Z",
    "tags": [],
    "created_at": "2025-01-15T10:30:00Z",
    "phone": null,
    "external_id": "contact_123",
    "external_source": "salesforce",
    "notification_preference": "email",
    "sms_consent": null,
    "preferred_language": "en",
    "email_undeliverable_at": null
  }
}

Requests

Document requests are sent to clients to collect files and information. Each request has a checklist of items and collects uploads from the client.

GET /api/v1/requests

List requests

Returns all document requests. Filter by status, client, or deal.

Scope: read
Parameter Type Required Description
status string optional Filter by status: draft, sent, in_progress, complete, archived (archived means canceled)
client_id string optional Filter by client ID
deal_id string optional Filter by deal ID
Request
curl "https://dointake.com/api/v1/requests?status=sent" \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "req_xyz789",
      "status": "sent",
      "title": "Tax Documents 2025",
      "client": {
        "id": "cl_abc123",
        "name": "Jane Smith",
        "email": "[email protected]"
      },
      "completed_at": null,
      "sent_at": "2025-01-20T09:00:00Z",
      "created_at": "2025-01-15T10:30:00Z",
      "due_date": "2025-04-15",
      "item_count": 5
    }
  ]
}
GET /api/v1/requests/:id

Get a request

Retrieve a single request with its checklist items.

Scope: read
Parameter Type Required Description
id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "sent",
    "description": "Please upload your tax documents",
    "title": "Tax Documents 2025",
    "forms": [
      {
        "id": "sub_abc123",
        "name": "Client Intake Form",
        "status": "completed",
        "completed_at": "2025-01-20T14:30:00Z",
        "submitted_ip": "203.0.113.42",
        "responses": [
          {
            "label": "Full Name",
            "type": "text",
            "value": "Jane Smith",
            "field_id": "field_1"
          },
          {
            "label": "Signature",
            "signed": true,
            "type": "signature",
            "value": null,
            "field_id": "field_2"
          }
        ]
      }
    ],
    "items": [
      {
        "id": "item_001",
        "name": "W-2 Form",
        "position": 0,
        "status": "pending",
        "description": "From your employer",
        "required": true,
        "created_at": "2025-01-15T10:30:00Z",
        "upload_count": 0
      }
    ],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "completed_at": null,
    "sent_at": "2025-01-20T09:00:00Z",
    "recipients": [],
    "created_at": "2025-01-15T10:30:00Z",
    "due_date": "2025-04-15",
    "deal_id": null,
    "max_reminders": 10,
    "notify_professional_after_days": 7,
    "reminder_count": 3,
    "item_count": 1,
    "last_reminder_at": "2025-02-10T09:00:00Z",
    "next_reminder_at": "2025-02-12",
    "pdf_forms": [
      {
        "id": "pfr_abc123",
        "status": "completed",
        "completed_at": "2025-01-20T15:00:00Z",
        "template_name": "Engagement Letter",
        "download_url": "https://dointake.com/downloads/tok_xyz789"
      }
    ]
  }
}
POST /api/v1/requests

Create a request

Create a new document request for a client. Optionally include checklist items and co-clients (additional recipients who each get their own portal link).

Scope: write
Parameter Type Required Description
client_id string required Primary client ID to send the request to
co_client_ids array optional Existing client IDs to add as co-recipients
co_clients array optional Co-recipients to create inline as {name, email} objects (an existing client is reused when the email matches)
title string required Request title
description string optional Description or instructions
due_date string optional Due date (YYYY-MM-DD)
locale string optional Override portal/email language for this request: en, pl, nl, es (defaults to the client's language)
items array optional Array of checklist items to create
Request
curl -X POST https://dointake.com/api/v1/requests \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"client_id": "cl_abc123", "title": "Tax Documents", "co_clients": [{"name": "John Smith", "email": "[email protected]"}], "items": [{"name": "W-2 Form"}]}'
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "pending",
    "description": null,
    "title": "Tax Documents",
    "items": [
      {
        "id": "item_001",
        "name": "W-2 Form",
        "position": 0,
        "status": "pending",
        "description": null,
        "required": true,
        "created_at": "2025-01-15T10:30:00Z",
        "upload_count": 0
      }
    ],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "locale": null,
    "completed_at": null,
    "sent_at": null,
    "recipients": [
      {
        "id": "cl_def456",
        "name": "John Smith",
        "email": "[email protected]",
        "preferred_language": null
      }
    ],
    "created_at": "2025-01-15T10:30:00Z",
    "due_date": null,
    "deal_id": null,
    "item_count": 1
  }
}
PATCH /api/v1/requests/:id

Update a request

Update an existing document request.

Scope: write
Parameter Type Required Description
id string required Request ID
title string optional Request title
description string optional Description or instructions
due_date string optional Due date (YYYY-MM-DD)
locale string optional Override portal/email language for this request: en, pl, nl, es (defaults to the client's language)
reminder_enabled boolean optional Enable email reminders
reminder_frequency_days integer optional Days between reminders (1–30)
max_reminders integer optional Max reminders to send (1–100, null for unlimited)
notify_professional_after_days integer optional Notify professional if no response after N days (1–90, null to disable)
Request
curl -X PATCH https://dointake.com/api/v1/requests/req_xyz789 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Title"}'
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "sent",
    "description": null,
    "title": "Updated Title",
    "items": [],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "completed_at": null,
    "sent_at": "2025-01-20T09:00:00Z",
    "created_at": "2025-01-15T10:30:00Z",
    "due_date": "2025-04-15",
    "deal_id": null,
    "item_count": 0
  }
}
POST /api/v1/requests/:id/send

Send a request

Send a draft request to the client via email. Changes status from draft to sent. Set `skip_email` to activate the request without emailing the client (useful for sharing the portal link manually).

Scope: write
Parameter Type Required Description
id string required Request ID
skip_email boolean optional If true, activate the request without sending an email to the client. Default: false.
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/send \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "sent",
    "title": "Tax Documents",
    "items": [],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "completed_at": null,
    "sent_at": "2025-01-20T09:00:00Z",
    "created_at": "2025-01-15T10:30:00Z",
    "deal_id": null,
    "item_count": 0
  }
}
POST /api/v1/requests/:id/complete

Complete a request

Mark a document request as complete.

Scope: write
Parameter Type Required Description
id string required Request ID
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/complete \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "complete",
    "title": "Tax Documents",
    "items": [],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "completed_at": "2025-02-01T14:00:00Z",
    "created_at": "2025-01-15T10:30:00Z",
    "deal_id": null,
    "item_count": 0
  }
}
POST /api/v1/requests/:id/cancel

Cancel a request

Cancel a document request. Sets its status to `archived`, closes the client's portal link immediately, and stops all reminder emails. No email is sent to the client. Reopen the request to make it active again.

Scope: write
Parameter Type Required Description
id string required Request ID
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/cancel \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "archived",
    "title": "Tax Documents",
    "items": [],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "completed_at": null,
    "created_at": "2025-01-15T10:30:00Z",
    "deal_id": null,
    "item_count": 0
  }
}
POST /api/v1/requests/:id/reopen

Reopen a request

Reopen a completed or canceled document request.

Scope: write
Parameter Type Required Description
id string required Request ID
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/reopen \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "req_xyz789",
    "status": "in_progress",
    "title": "Tax Documents",
    "items": [],
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "completed_at": null,
    "created_at": "2025-01-15T10:30:00Z",
    "deal_id": null,
    "item_count": 0
  }
}
GET /api/v1/requests/:request_id/reminders

List reminders

Returns the reminder audit trail for a request. Shows when each reminder was sent, to whom, and which items were pending.

Scope: read
Parameter Type Required Description
request_id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789/reminders \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "sent_at": "2025-02-01T09:00:00Z",
      "recipient_email": "[email protected]",
      "urgency": "friendly",
      "pending_items": [
        "W-2 Form",
        "1099 Form"
      ],
      "recipient_type": "client"
    },
    {
      "sent_at": "2025-02-04T09:00:00Z",
      "recipient_email": "[email protected]",
      "urgency": "urgent",
      "pending_items": [
        "W-2 Form"
      ],
      "recipient_type": "client"
    },
    {
      "sent_at": "2025-02-08T09:00:00Z",
      "recipient_email": "[email protected]",
      "urgency": "overdue",
      "pending_items": [],
      "recipient_type": "professional"
    }
  ]
}
POST /api/v1/requests/:request_id/recipients

Add a recipient

Add a co-client to an existing request. Each co-client gets their own portal link and is emailed when the request is sent. Pass either client_id for an existing client, or email (with an optional name) to create or reuse one.

Scope: write
Parameter Type Required Description
request_id string required Request ID
client_id string optional Existing client ID to add. Required unless email is given.
email string optional Email of the co-client to create or reuse
name string optional Name used when creating a new client from email
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/recipients \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "John Smith", "email": "[email protected]"}'
Response
{
  "data": {
    "id": "cl_def456",
    "name": "John Smith",
    "email": "[email protected]",
    "preferred_language": null
  }
}
DELETE /api/v1/requests/:request_id/recipients/:client_id

Remove a recipient

Remove a co-client from a request, invalidating their portal link. Returns 404 for the primary client, who cannot be removed this way.

Scope: write
Parameter Type Required Description
request_id string required Request ID
client_id string required Client ID of the co-client to remove
Request
curl -X DELETE https://dointake.com/api/v1/requests/req_xyz789/recipients/cl_def456 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
204 No Content

Checklist Items

Checklist items are individual documents or pieces of information requested within a document request.

GET /api/v1/requests/:request_id/items

List checklist items

Returns all checklist items for a given request.

Scope: read
Parameter Type Required Description
request_id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789/items \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "item_001",
      "name": "W-2 Form",
      "position": 0,
      "status": "pending",
      "description": "From your employer",
      "required": true,
      "created_at": "2025-01-15T10:30:00Z",
      "review_note": null,
      "review_status": null,
      "upload_count": 0
    }
  ]
}
POST /api/v1/requests/:request_id/items

Create a checklist item

Add a new checklist item to a request.

Scope: write
Parameter Type Required Description
request_id string required Request ID
name string required Item name
description string optional Instructions for the client
required boolean optional Whether this item is required (default: true)
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/items \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "W-2 Form", "description": "From your employer"}'
Response
{
  "data": {
    "id": "item_002",
    "name": "W-2 Form",
    "position": 1,
    "status": "pending",
    "description": "From your employer",
    "required": true,
    "created_at": "2025-01-15T10:30:00Z",
    "review_note": null,
    "review_status": null,
    "upload_count": 0
  }
}
PATCH /api/v1/items/:id

Update item review status

Set the review status and optional note on a checklist item. Only works when the item's status is "received". Review status values are validated against the user's configured list (defaults: in_review, approved, rejected).

Scope: write
Parameter Type Required Description
id string required Item ID
review_status string required Review status (user-configured, defaults: in_review, approved, rejected)
review_note string optional Optional note (e.g. reason for rejection)
Request
curl -X PATCH https://dointake.com/api/v1/items/itm_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"review_status": "in_review"}'
Response
{
  "data": {
    "id": "itm_abc123",
    "name": "W-2 Form",
    "position": 0,
    "status": "received",
    "description": "From your employer",
    "required": true,
    "created_at": "2025-01-15T10:30:00Z",
    "review_note": null,
    "review_status": "in_review",
    "upload_count": 1
  }
}
POST /api/v1/items/:id/re-request

Re-request item

Re-request a checklist item. Resets status to pending and clears review_status; the review_note is kept and shown to the client on the portal (and emailed to them) until a replacement upload arrives. Previous uploads are kept for reference but marked `superseded` and excluded from exports.

Scope: write
Parameter Type Required Description
id string required Item ID
Request
curl -X POST https://dointake.com/api/v1/items/itm_abc123/re-request \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "itm_abc123",
    "name": "W-2 Form",
    "position": 0,
    "status": "pending",
    "description": "From your employer",
    "required": true,
    "created_at": "2025-01-15T10:30:00Z",
    "review_note": null,
    "review_status": null,
    "upload_count": 1
  }
}

Uploads

Uploads are files submitted by clients in response to document requests, or attached by the firm on a client's behalf. Uploads can be reassigned to a different checklist item, e.g. to correct a misfiled document. Every upload carries two provenance fields: source says who created it (portal = the client via the upload portal, api = the firm via the API or MCP), and assignment_method says how it got attached to its item (ai = the classifier, client = the client dropped it directly onto the item, firm = a firm user or API call assigned it, null = not yet assigned). Reassigning an upload overwrites assignment_method. Both fields are null on uploads that predate provenance tracking.

GET /api/v1/requests/:request_id/uploads

List uploads

Returns all uploads for a given request. An item can hold multiple uploads (e.g. one photo per page of a multi-page document); `page_number` gives each upload's position within its item, assigned in upload order. It is `null` for uploads not yet assigned to an item.

Scope: read
Parameter Type Required Description
request_id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789/uploads \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "upl_001",
      "status": "processed",
      "file_size": 245000,
      "source": "portal",
      "content_type": "application/pdf",
      "created_at": "2025-01-20T14:30:00Z",
      "ai_classification": "W-2",
      "ai_confidence": 0.97,
      "original_filename": "w2-2025.pdf",
      "submitted_ip": "203.0.113.42",
      "page_number": 1,
      "assignment_method": "ai",
      "item_id": "item_001"
    }
  ]
}
PATCH /api/v1/uploads/:id

Assign upload to item

Assigns an upload to a checklist item on the same request. Use this to correct a misfiled upload, e.g. after classifying it yourself from a `document.uploaded` webhook. The upload's status becomes `classified` and the item becomes `received` (firing the `item.received` webhook). If the item the upload was moved away from has no uploads left, it is reset to `pending` and its review status is cleared. If the item already has uploads, the upload is appended as its next page (`page_number`). Assigning an item from a different request returns a 422 with code `cross_request_item`.

Scope: write
Parameter Type Required Description
id string required Upload ID
item_id string required Item ID on the same request to assign the upload to
Request
curl -X PATCH https://dointake.com/api/v1/uploads/upl_001 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"item_id": "itm_abc123"}'
Response
{
  "data": {
    "id": "upl_001",
    "status": "classified",
    "file_size": 245000,
    "source": "portal",
    "content_type": "application/pdf",
    "created_at": "2025-01-20T14:30:00Z",
    "ai_classification": "W-2",
    "ai_confidence": 0.97,
    "original_filename": "w2-2025.pdf",
    "submitted_ip": "203.0.113.42",
    "page_number": 1,
    "assignment_method": "firm",
    "item_id": "itm_abc123"
  }
}
POST /api/v1/items/:item_id/uploads

Upload a file to an item

Uploads a document file directly onto a checklist item on the client's behalf — e.g. when a client emailed you the document instead of using the portal. Send the file as `multipart/form-data` under the `file` field. The upload is attached to the item and the item becomes `received` (firing `document.uploaded` and `item.received` webhooks), so reminders for it stop. The upload gets `source: "api"` and `assignment_method: "firm"`. Accepted content: PDF or image (validated by file content, not extension), max 10 MB. Returns 400 with code `missing_file` if no file is sent, 422 with `invalid_file` for unaccepted content, 422 with `file_too_large` over the limit.

Scope: write
Parameter Type Required Description
item_id string required Item ID
file file required The document file (multipart/form-data)
Request
curl -X POST https://dointake.com/api/v1/items/itm_abc123/uploads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "[email protected]"
Response
{
  "data": {
    "id": "upl_002",
    "status": "classified",
    "file_size": 182000,
    "source": "api",
    "content_type": "application/pdf",
    "created_at": "2025-01-21T09:15:00Z",
    "ai_classification": null,
    "ai_confidence": null,
    "original_filename": "bank-statement.pdf",
    "submitted_ip": "198.51.100.7",
    "page_number": 1,
    "assignment_method": "firm",
    "item_id": "itm_abc123"
  }
}
GET /api/v1/requests/:request_id/export

Export a request's files

Downloads a zip archive containing every file uploaded to the request, plus a PDF of each completed form's responses. Responds with the binary zip (`Content-Type: application/zip`), not JSON. Returns 404 if the request has no uploads or completed forms.

Scope: read
Parameter Type Required Description
request_id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789/export \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o request-files.zip
Response
(binary zip archive)
GET /api/v1/deals/:deal_id/export

Export a deal's files

Downloads a zip archive of every file uploaded across the deal's requests, plus a PDF of each completed form's responses, with one folder per request. Responds with the binary zip (`Content-Type: application/zip`), not JSON. Returns 404 if the deal has no uploads or completed forms.

Scope: read
Parameter Type Required Description
deal_id string required Deal ID
Request
curl https://dointake.com/api/v1/deals/deal_abc123/export \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -o deal-files.zip
Response
(binary zip archive)

Form Templates

Form templates define reusable sets of fields that can be attached to requests. When attached, the template's fields are snapshotted so future edits don't affect existing requests.

GET /api/v1/form-templates

List form templates

Returns all form templates for your account.

Scope: read
Request
curl https://dointake.com/api/v1/form-templates \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "tmpl_abc123",
      "name": "Client Intake",
      "description": "Basic client information form",
      "updated_at": "2025-01-15T10:30:00Z",
      "created_at": "2025-01-15T10:30:00Z",
      "field_count": 5
    }
  ]
}
GET /api/v1/form-templates/:id

Get a form template

Retrieve a form template with all its field definitions.

Scope: read
Parameter Type Required Description
id string required Template ID
Request
curl https://dointake.com/api/v1/form-templates/tmpl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "tmpl_abc123",
    "name": "Client Intake",
    "description": "Basic client information form",
    "fields": [
      {
        "id": 1,
        "label": "Full Legal Name",
        "position": 0,
        "type": "text",
        "config": {
          "validation": "none"
        },
        "description": null,
        "required": true
      },
      {
        "id": 2,
        "label": "Email Address",
        "position": 1,
        "type": "email",
        "config": {
          "validation": "email"
        },
        "description": null,
        "required": true
      },
      {
        "id": 3,
        "label": "Filing Status",
        "position": 2,
        "type": "select",
        "config": {
          "options": [
            "Single",
            "Married",
            "Head of Household"
          ]
        },
        "description": "Select your tax filing status",
        "required": true
      }
    ],
    "updated_at": "2025-01-15T10:30:00Z",
    "created_at": "2025-01-15T10:30:00Z"
  }
}
POST /api/v1/form-templates

Create a form template

Create a new form template, optionally with fields in one call. Field types: text, textarea, number, date, email, phone, select, radio, checkbox, signature, section_heading. Fields with options (select, radio, checkbox) need config.options. Layout-only types (section_heading) render visually but have no response value — they are skipped during validation and submission.

Scope: write
Parameter Type Required Description
name string required Template name
description string optional Template description
fields array optional List of field objects with type, label, required, description, config
Request
curl -X POST https://dointake.com/api/v1/form-templates \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Client Intake", "fields": [{"type": "text", "label": "Full Name", "required": true}, {"type": "email", "label": "Email", "config": {"validation": "email"}}]}'
Response
{
  "data": {
    "id": "tmpl_abc123",
    "name": "Client Intake",
    "description": null,
    "fields": [
      {
        "id": 1,
        "label": "Full Name",
        "position": 0,
        "type": "text",
        "config": {
          "validation": "none"
        },
        "description": null,
        "required": true
      },
      {
        "id": 2,
        "label": "Email",
        "position": 1,
        "type": "email",
        "config": {
          "validation": "email"
        },
        "description": null,
        "required": false
      }
    ],
    "updated_at": "2025-01-15T10:30:00Z",
    "created_at": "2025-01-15T10:30:00Z"
  }
}
DELETE /api/v1/form-templates/:id

Delete a form template

Delete a form template. Returns 409 if the template is attached to any requests.

Scope: write
Parameter Type Required Description
id string required Template ID
Request
curl -X DELETE https://dointake.com/api/v1/form-templates/tmpl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
204 No Content
POST /api/v1/form-templates/:form_template_id/fields

Add a field

Add a field to an existing form template. The field is appended to the end. A field can be conditionally shown based on an earlier select/radio/checkbox field's answer via config.visibility: {"combinator": "all", "conditions": [{"field_id": "3", "operator": "equals", "value": "Yes"}]}. Use operator "equals" for select/radio parents and "contains" for checkbox parents; exactly one condition is supported. Hidden fields skip validation and are omitted from responses.

Scope: write
Parameter Type Required Description
form_template_id string required Template ID
type string required Field type: text, textarea, number, date, email, phone, select, radio, checkbox, signature, section_heading (layout-only, no response value)
label string required Field label
description string optional Help text
required boolean optional Whether field is required (default: false)
config object optional Field config — options for select/radio/checkbox, validation rule, visibility condition
Request
curl -X POST https://dointake.com/api/v1/form-templates/tmpl_abc123/fields \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "select", "label": "State", "config": {"options": ["CA", "NY", "TX"]}}'
Response
{
  "data": {
    "id": 4,
    "label": "State",
    "position": 3,
    "type": "select",
    "config": {
      "options": [
        "CA",
        "NY",
        "TX"
      ]
    },
    "description": null,
    "required": false
  }
}
PATCH /api/v1/form-templates/:form_template_id/fields/:id

Update a field

Update a field on a form template.

Scope: write
Parameter Type Required Description
form_template_id string required Template ID
id integer required Field ID
label string optional Updated label
required boolean optional Updated required flag
config object optional Updated config
Request
curl -X PATCH https://dointake.com/api/v1/form-templates/tmpl_abc123/fields/4 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label": "Updated Label", "required": true}'
Response
{
  "data": {
    "id": 4,
    "label": "Updated Label",
    "position": 3,
    "type": "select",
    "config": {
      "options": [
        "CA",
        "NY",
        "TX"
      ]
    },
    "description": null,
    "required": true
  }
}
DELETE /api/v1/form-templates/:form_template_id/fields/:id

Delete a field

Remove a field from a form template.

Scope: write
Parameter Type Required Description
form_template_id string required Template ID
id integer required Field ID
Request
curl -X DELETE https://dointake.com/api/v1/form-templates/tmpl_abc123/fields/4 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
204 No Content

Request Forms

Attach form templates or inline forms to requests. Clients fill them out in the portal alongside document uploads.

POST /api/v1/requests/:request_id/forms

Attach a form

Attach a saved template or create an inline form on a request. To attach a template, send template_id. For an inline form, send name and fields.

Scope: write
Parameter Type Required Description
request_id string required Request ID
template_id string optional Template ID (for saved templates)
name string optional Form name (for inline forms)
fields array optional Field definitions (for inline forms)
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/forms \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "tmpl_abc123"}'
Response
{
  "data": {
    "id": "sub_def456",
    "fields": [
      {
        "id": "1",
        "label": "Full Name",
        "position": 0,
        "type": "text",
        "config": {},
        "required": true
      },
      {
        "id": "2",
        "label": "Email",
        "position": 1,
        "type": "email",
        "config": {
          "validation": "email"
        },
        "required": true
      }
    ],
    "completed": false,
    "completed_at": null,
    "template_name": "Client Intake",
    "created_at": "2025-01-20T09:00:00Z"
  }
}
GET /api/v1/requests/:request_id/forms

List attached forms

List all forms attached to a request. Completed forms include responses.

Scope: read
Parameter Type Required Description
request_id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789/forms \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "sub_def456",
      "fields": [
        {
          "id": "1",
          "label": "Full Name",
          "position": 0,
          "type": "text",
          "config": {},
          "required": true
        }
      ],
      "completed": true,
      "completed_at": "2025-01-21T15:00:00Z",
      "template_name": "Client Intake",
      "created_at": "2025-01-20T09:00:00Z",
      "responses": [
        {
          "label": "Full Name",
          "type": "text",
          "value": "Jane Smith",
          "field_id": "1"
        }
      ]
    }
  ]
}
DELETE /api/v1/requests/:request_id/forms/:id

Detach a form

Remove an attached form from a request. Also deletes any responses.

Scope: write
Parameter Type Required Description
request_id string required Request ID
id string required Request form ID (sub_xxx)
Request
curl -X DELETE https://dointake.com/api/v1/requests/req_xyz789/forms/sub_def456 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
204 No Content

PDF Templates

Upload and manage form-fillable PDF templates. Uploaded PDFs are analyzed for native AcroForm fields. You can also place custom fields via the editor UI.

GET /api/v1/pdf-templates

List PDF templates

List all PDF templates for the authenticated user.

Scope: read
Request
curl https://dointake.com/api/v1/pdf-templates \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "ptl_abc123",
      "name": "W-9 Form",
      "filename": "w9.pdf",
      "file_size": 85432,
      "created_at": "2025-02-01T10:00:00Z",
      "has_native_fields": true,
      "page_count": 1,
      "custom_field_count": 0,
      "native_field_count": 12
    }
  ]
}
GET /api/v1/pdf-templates/:id

Get a PDF template

Get a PDF template with field definitions and an editor URL for the placement UI.

Scope: read
Parameter Type Required Description
id string required Template ID (ptl_xxx)
Request
curl https://dointake.com/api/v1/pdf-templates/ptl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "ptl_abc123",
    "name": "W-9 Form",
    "filename": "w9.pdf",
    "file_size": 85432,
    "content_type": "application/pdf",
    "created_at": "2025-02-01T10:00:00Z",
    "native_field_definitions": [
      {
        "name": "topmostSubform[0].Page1[0].f1_1[0]",
        "type": "Text"
      }
    ],
    "custom_field_definitions": [],
    "has_native_fields": true,
    "page_count": 1,
    "editor_url": "https://dointake.com/dashboard/pdf-templates/ptl_abc123"
  }
}
POST /api/v1/pdf-templates

Upload a PDF template

Upload a PDF file as a new template. Use multipart/form-data with a 'file' field. The PDF is analyzed for page count and native form fields.

Scope: write
Parameter Type Required Description
file file required PDF file (multipart upload)
Request
curl -X POST https://dointake.com/api/v1/pdf-templates \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "[email protected]"
Response
{
  "data": {
    "id": "ptl_def456",
    "name": "w9",
    "filename": "w9.pdf",
    "file_size": 85432,
    "content_type": "application/pdf",
    "created_at": "2025-02-01T10:00:00Z",
    "native_field_definitions": [
      {
        "name": "topmostSubform[0].Page1[0].f1_1[0]",
        "type": "Text"
      }
    ],
    "custom_field_definitions": [],
    "has_native_fields": true,
    "page_count": 1,
    "editor_url": "https://dointake.com/dashboard/pdf-templates/ptl_def456"
  }
}
DELETE /api/v1/pdf-templates/:id

Delete a PDF template

Delete a PDF template. Returns 409 if the template is attached to any requests.

Scope: write
Parameter Type Required Description
id string required Template ID (ptl_xxx)
Request
curl -X DELETE https://dointake.com/api/v1/pdf-templates/ptl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
204 No Content

PDF Fill Requests

Attach PDF templates to requests as fill requests. Clients fill in the PDF fields in the portal and the completed PDF is stored as an upload.

POST /api/v1/requests/:request_id/pdf-fills

Attach a PDF fill request

Attach a PDF template to a request. The client will be prompted to fill in the PDF fields. For documents needing multiple signatures (e.g. two spouses plus the account owner), pass an ordered `signers` list — each entry is either `{"client_id": "cl_xxx"}` or `{"owner": true}` with an optional `role_label` — and optionally `field_assignments` mapping custom field ids to a 0-based signer index. Signers sign in parallel via their own links; the final PDF (with a certificate listing every signer) is composed server-side and delivered to everyone once the last signer finishes.

Scope: write
Parameter Type Required Description
request_id string required Request ID
pdf_template_id string required PDF template ID (ptl_xxx)
signers array optional Ordered signer list for multi-signature documents. Each entry: {"client_id": "cl_xxx"} or {"owner": true}, optional "role_label".
field_assignments object optional Map of custom field id to 0-based signer index. Assigned fields are only editable by that signer.
Request
curl -X POST https://dointake.com/api/v1/requests/req_xyz789/pdf-fills \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pdf_template_id": "ptl_abc123",
    "signers": [
      {"client_id": "cl_husband1", "role_label": "Husband"},
      {"client_id": "cl_wife1", "role_label": "Wife"},
      {"owner": true, "role_label": "Attorney"}
    ],
    "field_assignments": {"2": 0, "3": 1, "4": 2}
  }'
Response
{
  "data": {
    "id": "pfr_ghi789",
    "status": "pending",
    "completed_at": null,
    "template_id": "ptl_abc123",
    "template_name": "Service Agreement",
    "created_at": "2025-02-01T12:00:00Z",
    "completed_upload_id": null,
    "signatures_count": 0,
    "signers": [
      {
        "id": "pfs_aaa111",
        "owner": false,
        "status": "pending",
        "signed_at": null,
        "client_id": "cl_husband1",
        "role_label": "Husband"
      },
      {
        "id": "pfs_bbb222",
        "owner": false,
        "status": "pending",
        "signed_at": null,
        "client_id": "cl_wife1",
        "role_label": "Wife"
      },
      {
        "id": "pfs_ccc333",
        "owner": true,
        "status": "pending",
        "signed_at": null,
        "client_id": null,
        "role_label": "Attorney"
      }
    ],
    "has_native_fields": false
  }
}
GET /api/v1/requests/:request_id/pdf-fills

List PDF fill requests

List all PDF fill requests for a given request.

Scope: read
Parameter Type Required Description
request_id string required Request ID
Request
curl https://dointake.com/api/v1/requests/req_xyz789/pdf-fills \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "pfr_ghi789",
      "status": "completed",
      "completed_at": "2025-02-02T09:30:00Z",
      "template_id": "ptl_abc123",
      "template_name": "W-9 Form",
      "created_at": "2025-02-01T12:00:00Z",
      "completed_upload_id": "upl_xyz123",
      "signatures_count": 2,
      "signers": [
        {
          "id": "pfs_aaa111",
          "owner": false,
          "status": "signed",
          "signed_at": "2025-02-02T09:30:00Z",
          "client_id": "cl_abc123",
          "role_label": null
        }
      ],
      "has_native_fields": true
    }
  ]
}
DELETE /api/v1/requests/:request_id/pdf-fills/:id

Delete a PDF fill request

Remove a PDF fill request from a request.

Scope: write
Parameter Type Required Description
request_id string required Request ID
id string required Fill request ID (pfr_xxx)
Request
curl -X DELETE https://dointake.com/api/v1/requests/req_xyz789/pdf-fills/pfr_ghi789 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
204 No Content
GET /api/v1/requests/:request_id/pdf-fills/:fill_id/signatures

List signature audit records

Return the audit trail for a completed fill: per-signature field name, SHA-256 hash, IP, user agent, signed-at timestamp, and (for multi-signer documents) the signer's identity. These are the same records rendered on the Certificate of Completion page appended to every signed PDF.

Scope: read
Parameter Type Required Description
request_id string required Request ID
fill_id string required Fill request ID (pfr_xxx)
Request
curl https://dointake.com/api/v1/requests/req_xyz789/pdf-fills/pfr_ghi789/signatures \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "signed_at": "2025-02-02T09:29:55Z",
      "user_agent": "Mozilla/5.0",
      "ip_address": "203.0.113.42",
      "signer_name": "Jane Borrower",
      "signer_email": "[email protected]",
      "field_name": "borrower_signature",
      "signature_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "signer_id": "pfs_aaa111"
    }
  ]
}

Deals

Deals represent multi-party transactions (e.g. real estate closings, loan applications). Each deal has parties, and each party can have associated document requests.

GET /api/v1/deals

List deals

Returns all deals. Optionally filter by status, external ID, or external source.

Scope: read
Parameter Type Required Description
status string optional Filter by status (e.g. active, closed)
external_id string optional Filter by external ID
external_source string optional Filter by external source (used with external_id)
Request
curl https://dointake.com/api/v1/deals \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "deal_001",
      "name": "123 Main St Closing",
      "status": "active",
      "metadata": {},
      "created_at": "2025-01-15T10:30:00Z",
      "external_id": "opp_456",
      "external_source": "salesforce",
      "property_address": "123 Main St, Springfield, IL 62701",
      "party_count": 3
    }
  ]
}
GET /api/v1/deals/:id

Get a deal

Retrieve a deal with all parties and their requests.

Scope: read
Parameter Type Required Description
id string required Deal ID
Request
curl https://dointake.com/api/v1/deals/deal_001 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "deal_001",
    "name": "123 Main St Closing",
    "status": "active",
    "description": "Residential purchase",
    "metadata": {},
    "created_at": "2025-01-15T10:30:00Z",
    "external_id": "opp_456",
    "external_source": "salesforce",
    "parties": [
      {
        "id": "pty_001",
        "position": 0,
        "requests": [
          {
            "id": "req_xyz789",
            "status": "sent",
            "title": "Buyer Documents",
            "item_count": 5
          }
        ],
        "client": {
          "id": "cl_abc123",
          "name": "Jane Smith",
          "email": "[email protected]"
        },
        "role": "Buyer",
        "created_at": "2025-01-15T10:30:00Z",
        "cc_emails": [],
        "delegate_to_id": null,
        "external_id": null,
        "external_source": null
      }
    ],
    "property_address": "123 Main St, Springfield, IL 62701",
    "party_count": 1
  }
}
POST /api/v1/deals

Create a deal

Create a new deal. Optionally create from a deal template by passing `template_id`.

Scope: write
Parameter Type Required Description
name string required Deal name
description string optional Deal description
template_id string optional Create from a deal template
external_id string optional External system ID
external_source string optional External system name
metadata object optional Arbitrary key-value metadata
property_address string optional Property address for the deal
Request
curl -X POST https://dointake.com/api/v1/deals \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "123 Main St Closing"}'
Response
{
  "data": {
    "id": "deal_002",
    "name": "123 Main St Closing",
    "status": "active",
    "description": null,
    "metadata": {},
    "created_at": "2025-01-15T10:30:00Z",
    "external_id": null,
    "external_source": null,
    "parties": [],
    "property_address": null,
    "party_count": 0
  }
}
PATCH /api/v1/deals/:id

Update a deal

Update an existing deal.

Scope: write
Parameter Type Required Description
id string required Deal ID
name string optional Deal name
description string optional Deal description
external_id string optional External system ID
external_source string optional External system name
metadata object optional Arbitrary key-value metadata
property_address string optional Property address for the deal
Request
curl -X PATCH https://dointake.com/api/v1/deals/deal_001 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "456 Oak Ave Closing"}'
Response
{
  "data": {
    "id": "deal_001",
    "name": "456 Oak Ave Closing",
    "status": "active",
    "description": "Residential purchase",
    "metadata": {},
    "created_at": "2025-01-15T10:30:00Z",
    "external_id": null,
    "external_source": null,
    "parties": [],
    "property_address": null,
    "party_count": 1
  }
}
PUT /api/v1/deals/upsert

Upsert a deal

Create or update a deal by matching on `external_id` + `external_source`. Returns `201` if a new deal was created, `200` if an existing deal was updated.

Scope: write
Parameter Type Required Description
name string required Deal name
description string optional Deal description
external_id string required External system ID (used for matching)
external_source string required External system name (used with external_id for matching)
metadata object optional Arbitrary key-value metadata
property_address string optional Property address for the deal
Request
curl -X PUT https://dointake.com/api/v1/deals/upsert \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "123 Main St Closing", "external_id": "opp_456", "external_source": "salesforce", "property_address": "123 Main St, Springfield, IL 62701"}'
Response
{
  "data": {
    "id": "deal_002",
    "name": "123 Main St Closing",
    "status": "active",
    "description": null,
    "metadata": {},
    "created_at": "2025-01-15T10:30:00Z",
    "external_id": "opp_456",
    "external_source": "salesforce",
    "parties": [],
    "property_address": "123 Main St, Springfield, IL 62701",
    "party_count": 0
  }
}
POST /api/v1/deals/dispatch

Dispatch a deal

One-call deal dispatch. Creates a deal from a template, assigns clients to parties (creating clients as needed), creates requests, and optionally sends them. Clients are matched by `external_id` + `external_source` or by `email`.

Scope: write
Parameter Type Required Description
template_id string required Deal template ID (dtpl_xxx)
name string required Deal name
property_address string optional Property address
external_id string optional External system ID for the deal
external_source string optional External system name for the deal
auto_send boolean optional If true, automatically send all requests to clients (default: false)
parties array required Array of party objects with role and client info: {role, client: {email, name, external_id, external_source}}
Request
curl -X POST https://dointake.com/api/v1/deals/dispatch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template_id": "dtpl_abc123", "name": "Smith Purchase", "auto_send": true, "parties": [{"role": "Borrower", "client": {"email": "[email protected]", "name": "Jane Smith"}}]}'
Response
{
  "data": {
    "id": "deal_xyz789",
    "name": "Smith Purchase",
    "status": "active",
    "description": null,
    "metadata": {},
    "created_at": "2025-01-15T10:30:00Z",
    "external_id": null,
    "external_source": null,
    "parties": [
      {
        "id": "pty_001",
        "position": 0,
        "requests": [
          {
            "id": "req_abc789",
            "status": "sent",
            "title": "Borrower — Documents",
            "item_count": 3
          }
        ],
        "client": {
          "id": "cl_abc123",
          "name": "Jane Smith",
          "email": "[email protected]"
        },
        "role": "Borrower",
        "created_at": "2025-01-15T10:30:00Z",
        "cc_emails": [],
        "delegate_to_id": null,
        "external_id": null,
        "external_source": null
      }
    ],
    "property_address": null,
    "party_count": 1
  }
}

Deal Templates

Deal templates define reusable multi-party workflows. Each template has party roles with pre-configured form templates, PDF templates, and document checklists. Create a deal from a template to get all parties set up in one call.

GET /api/v1/deal-templates

List deal templates

Returns all deal templates available to the user, including system templates.

Scope: read
Request
curl https://dointake.com/api/v1/deal-templates \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "dtpl_abc123",
      "name": "Mortgage Loan",
      "description": "Standard mortgage loan closing workflow",
      "created_at": "2025-01-15T10:30:00Z",
      "is_system": true,
      "party_count": 10
    }
  ]
}
GET /api/v1/deal-templates/:id

Get a deal template

Retrieve a deal template with all party roles and their attached form templates, PDF templates, and document types.

Scope: read
Parameter Type Required Description
id string required Deal template ID
Request
curl https://dointake.com/api/v1/deal-templates/dtpl_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "dtpl_abc123",
    "name": "Mortgage Loan",
    "description": "Standard mortgage loan closing workflow",
    "created_at": "2025-01-15T10:30:00Z",
    "parties": [
      {
        "position": 0,
        "role": "Borrower",
        "document_types": [
          {
            "id": 1,
            "name": "Pay Stub"
          }
        ],
        "form_templates": [
          {
            "id": "ftpl_abc123",
            "name": "Personal Info"
          }
        ],
        "pdf_templates": [
          {
            "id": "ptl_abc123",
            "name": "W-9 Form"
          }
        ]
      },
      {
        "position": 1,
        "role": "Escrow",
        "document_types": [],
        "form_templates": [],
        "pdf_templates": []
      }
    ],
    "is_system": true,
    "party_count": 2
  }
}
POST /api/v1/deals

Create deal from template

Create a deal from a template by passing `template_id`. All party roles from the template are created automatically. Assign clients to parties afterward.

Scope: write
Parameter Type Required Description
name string required Deal name
template_id string required Deal template ID (dtpl_xxx)
description string optional Deal description
Request
curl -X POST https://dointake.com/api/v1/deals \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Smith Purchase", "template_id": "dtpl_abc123"}'
Response
{
  "data": {
    "id": "deal_xyz789",
    "name": "Smith Purchase",
    "status": "draft",
    "description": null,
    "metadata": {},
    "created_at": "2025-01-15T10:30:00Z",
    "external_id": null,
    "external_source": null,
    "parties": [
      {
        "id": "pty_001",
        "position": 0,
        "requests": [],
        "client": null,
        "role": "Borrower",
        "created_at": "2025-01-15T10:30:00Z",
        "cc_emails": [],
        "delegate_to_id": null,
        "external_id": null,
        "external_source": null
      },
      {
        "id": "pty_002",
        "position": 1,
        "requests": [],
        "client": null,
        "role": "Escrow",
        "created_at": "2025-01-15T10:30:00Z",
        "cc_emails": [],
        "delegate_to_id": null,
        "external_id": null,
        "external_source": null
      }
    ],
    "property_address": null,
    "party_count": 2
  }
}
POST /api/v1/deals/:deal_id/parties/:party_id/requests/from-template

Create request from template

Create a fully-loaded request for a party using the deal template's attachments. Automatically attaches all form templates, PDF templates, and document checklists defined for this party's role. The party must have a client assigned.

Scope: write
Parameter Type Required Description
deal_id string required Deal ID
party_id string required Party ID
Request
curl -X POST https://dointake.com/api/v1/deals/deal_xyz789/parties/pty_001/requests/from-template \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": {
    "id": "req_abc789",
    "status": "draft",
    "title": "Borrower — Documents",
    "items": [
      {
        "id": "item_001",
        "name": "Pay Stub",
        "status": "pending"
      }
    ],
    "client": {
      "id": "cli_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "created_at": "2025-01-15T10:30:00Z"
  }
}

Parties

Parties represent participants in a deal (e.g. buyer, seller, lender). Each party can be linked to a client.

GET /api/v1/deals/:deal_id/parties

List parties

Returns all parties for a deal.

Scope: read
Parameter Type Required Description
deal_id string required Deal ID
Request
curl https://dointake.com/api/v1/deals/deal_001/parties \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "id": "pty_001",
      "position": 0,
      "client": {
        "id": "cl_abc123",
        "name": "Jane Smith",
        "email": "[email protected]"
      },
      "role": "Buyer",
      "created_at": "2025-01-15T10:30:00Z",
      "cc_emails": [
        "[email protected]"
      ],
      "delegate_to_id": null,
      "external_id": null,
      "external_source": null
    }
  ]
}
POST /api/v1/deals/:deal_id/parties

Create a party

Add a party to a deal. Optionally link to an existing client.

Scope: write
Parameter Type Required Description
deal_id string required Deal ID
role string required Party role (e.g. Buyer, Seller)
client_id string optional Link to an existing client
cc_emails array of strings optional CC email addresses for notifications (max 10)
delegate_to_id string optional Party ID to delegate requests to. The delegate's client receives emails instead. Must be in the same deal. Set to null to clear.
external_id string optional External system ID for this party
external_source string optional External system name (e.g. follow_up_boss)
Request
curl -X POST https://dointake.com/api/v1/deals/deal_001/parties \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role": "Seller", "client_id": "cl_def456", "cc_emails": ["[email protected]"]}'
Response
{
  "data": {
    "id": "pty_002",
    "position": 1,
    "client": {
      "id": "cl_def456",
      "name": "Bob Jones",
      "email": "[email protected]"
    },
    "role": "Seller",
    "created_at": "2025-01-15T10:30:00Z",
    "cc_emails": [
      "[email protected]"
    ],
    "delegate_to_id": null,
    "external_id": null,
    "external_source": null
  }
}
PATCH /api/v1/parties/:id

Update a party

Update a party's role or linked client.

Scope: write
Parameter Type Required Description
id string required Party ID
role string optional Party role
client_id string optional Link to a different client
cc_emails array of strings optional CC email addresses for notifications (max 10)
delegate_to_id string optional Party ID to delegate requests to. The delegate's client receives emails instead. Must be in the same deal. Set to null to clear.
external_id string optional External system ID for this party
external_source string optional External system name (e.g. follow_up_boss)
Request
curl -X PATCH https://dointake.com/api/v1/parties/pty_001 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"delegate_to_id": "pty_002"}'
Response
{
  "data": {
    "id": "pty_001",
    "position": 0,
    "client": {
      "id": "cl_abc123",
      "name": "Jane Smith",
      "email": "[email protected]"
    },
    "role": "Primary Buyer",
    "created_at": "2025-01-15T10:30:00Z",
    "cc_emails": [
      "[email protected]",
      "[email protected]"
    ],
    "delegate_to_id": "pty_002",
    "external_id": null,
    "external_source": null
  }
}

Activity Log

An immutable audit trail of events in your account. Use this endpoint to track changes to deals, requests, parties, and documents.

GET /api/v1/activity

List activity

Returns activity log entries for your account, newest first. Supports cursor-based pagination and filtering.

Scope: read
Parameter Type Required Description
deal_id string optional Filter by deal public ID
party_id string optional Filter by party public ID
request_id string optional Filter by request public ID
resource_type string optional Filter by resource type (deal, request, party, item, upload)
event_type string optional Filter by event type (e.g. request.sent, deal.created)
before string optional Cursor for pagination — pass an activity ID to fetch older entries
limit integer optional Max entries to return (1–100, default 50)
Request
curl https://dointake.com/api/v1/activity?deal_id=deal_abc123 \
  -H "Authorization: Bearer sk_live_..."
Response
{
  "data": [
    {
      "id": "act_x1y2z3a4b5",
      "metadata": {
        "client_name": "Jane Doe",
        "request_title": "Tax Documents 2025"
      },
      "request_id": "req_abc123",
      "deal_id": "deal_abc123",
      "party_id": "pty_def456",
      "event_type": "request.sent",
      "resource_id": "req_abc123",
      "resource_type": "request",
      "occurred_at": "2025-03-15T10:30:00Z"
    }
  ]
}

Webhooks

Subscribe to events in your account. When an event occurs, we send an HTTP POST to your configured URL with a JSON payload.

GET /api/v1/webhooks

List webhooks

Returns all webhook subscriptions.

Scope: webhooks
Request
curl https://dointake.com/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
{
  "data": [
    {
      "active": true,
      "id": "wh_001",
      "events": [
        "request.completed",
        "document.uploaded"
      ],
      "description": "Production webhook",
      "url": "https://example.com/webhook",
      "created_at": "2025-01-15T10:30:00Z"
    }
  ]
}
POST /api/v1/webhooks

Create a webhook

Create a new webhook subscription. The response includes a `secret` for verifying webhook signatures — store it securely, it won't be shown again.

Scope: webhooks
Parameter Type Required Description
url string required URL to receive webhook events
events array required Event types to subscribe to
description string optional Description for this webhook
active boolean optional Whether the webhook is active (default: true)
Request
curl -X POST https://dointake.com/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/webhook", "events": ["request.completed"]}'
Response
{
  "data": {
    "active": true,
    "id": "wh_002",
    "events": [
      "request.completed"
    ],
    "description": null,
    "secret": "whsec_abc123...",
    "url": "https://example.com/webhook",
    "created_at": "2025-01-15T10:30:00Z"
  }
}
PATCH /api/v1/webhooks/:id

Update a webhook

Update a webhook subscription.

Scope: webhooks
Parameter Type Required Description
id string required Webhook ID
url string optional URL to receive webhook events
events array optional Event types to subscribe to
description string optional Description
active boolean optional Whether the webhook is active
Request
curl -X PATCH https://dointake.com/api/v1/webhooks/wh_001 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active": false}'
Response
{
  "data": {
    "active": false,
    "id": "wh_001",
    "events": [
      "request.completed",
      "document.uploaded"
    ],
    "description": "Production webhook",
    "url": "https://example.com/webhook",
    "created_at": "2025-01-15T10:30:00Z"
  }
}
DELETE /api/v1/webhooks/:id

Delete a webhook

Permanently delete a webhook subscription.

Scope: webhooks
Parameter Type Required Description
id string required Webhook ID
Request
curl -X DELETE https://dointake.com/api/v1/webhooks/wh_001 \
  -H "Authorization: Bearer YOUR_API_KEY"
Response
# 204 No Content

Webhook Events

When an event occurs, we send a POST request to your webhook URL with a JSON payload.

Available event types:

EventFired when
request.sentA request is sent to the client
request.completedA request is marked complete
request.reopenedA completed or canceled request is reopened (portal link live again)
request.canceledA request is canceled (portal closed, reminders stopped)
request.overdueA request passes its due date
reminder.sentThe reminder scheduler sends (or, if delegated, would have sent) a client reminder
portal.renewal_requestedA client hits an expired portal link and asks for a fresh one
document.uploadedA file is uploaded — by the client, or by you via the API
document.classifiedAn upload is auto-classified
item.createdA checklist item is added
item.receivedAn item receives an upload
item.re_requestedAn upload is rejected and the item is sent back to the client
all_items_receivedEvery item on a request has been received
pdf_form.completedA client completes a fillable PDF form
pdf_form.signer_signedOne signer of a multi-signer PDF signs
client.created / client.updatedA client is created / updated
deal.created / deal.completed / deal.status_changedDeal lifecycle
party.created / party.assignedDeal party lifecycle
email.bouncedAn email to a client hard-bounced (address is dead; reminders stop)
email.complainedA client marked an Intake email as spam (treated like a bounce)
pingTest event, sent from the webhook settings page

Envelope:

{
  "id": "dlv_9k2m4p71xw",
  "event": "reminder.sent",
  "mode": "live",
  "data": { ... },
  "created_at": "2026-08-05T17:20:00Z"
}

id is stable across delivery retries of the same event — dedupe on it. (It is also sent as the X-Webhook-Delivery header.) Two reminders are two events with two ids; a retry of one reminder reuses the same id.

The mode field is "test" for events from sandbox data, "live" for production data. Use this to filter or route test events in your automation platform.

Request-scoped payloads. Events about a request (request.*, reminder.sent, item.re_requested, portal.renewal_requested, all_items_received) nest shared blocks under data:

{
  "request": {
    "id": "req_a1b2c3",
    "title": "Chapter 7 — Intake Documents",
    "status": "in_progress",
    "due_date": "2026-08-20",
    "sent_at": "2026-08-01T15:04:00Z",
    "completed_at": null,
    "locale": "en",
    "portal_url": "https://dointake.com/p/XXXXXXXX",
    "portal_url_expires_at": null
  },
  "client": {
    "id": "cli_d4e5f6",
    "name": "Maria Alvarez",
    "email": "[email protected]",
    "phone": "+15551234567",
    "external_id": "CASE-2026-081",
    "external_source": null,
    "tags": [],
    "notification_preference": "email",
    "sms_consent": null
  },
  "delivery": { "intake_email_sent": true }
}

portal_url is the live client portal link (the same link the email carries) — treat webhook endpoint URLs as secrets and use HTTPS, since payloads contain live portal tokens. portal_url_expires_at is null while the request is active and set to completion + 90 days once it completes.

delivery.intake_email_sent says whether Intake also emailed the client for this event. A 200 from your endpoint means your automation received the event — not that the client was reached; anything you send downstream is your responsibility.

email.bounced / email.complained carry the same request and client blocks plus an email block (email_id, subject, and for bounces a bounce object with type/subType/message) — no delivery block, since these events are about a failed email, not a notification you might deliver.

Upload provenance. Upload payloads (document.uploaded, document.classified, and the upload block on item.received) include source and assignment_method. source says who created the upload: "portal" (the client) or "api" (your integration, e.g. an on-behalf-of upload). assignment_method says how the upload got attached to its item: "ai" (the classifier), "client" (the client dropped it directly onto the item, bypassing classification — ai_classification will be null), "firm" (a firm user, API call, or MCP tool assigned it), or null (not yet assigned — always the case on document.uploaded for portal uploads that haven't been classified yet; item.received carries the final value). Both fields are null on uploads that predate provenance tracking.

reminder.sent adds a reminder block: number (this reminder's count), cap (the request's max), final (number == cap), days_since_last_contact, and pending_items — ordered as the portal displays them, each with id, type (form | document | pdf), name, description, required. item.re_requested adds an item block including the professional's reason note.

Delivery retries: 3 attempts per delivery — 60s after the first failure, 5 minutes after the second, with a 10-second response timeout. Any non-2xx response or timeout counts as a failure. After 10 consecutive failures across deliveries, the endpoint is disabled and must be re-enabled in settings.

Notification delegation. In Settings → Webhooks you can delegate delivery of request.sent, reminder.sent, item.re_requested, portal.renewal_requested, and request.completed to one of your endpoints. For clients whose notification_preference is external, Intake then stops emailing them for that event and your automation delivers it instead (delivery.intake_email_sent will be false). Clients preferring email keep getting Intake emails regardless. If the delegated endpoint is deleted, disabled, or auto-disabled after repeated failures, the delegation lapses: Intake email resumes immediately and you're alerted — clients are never left without notifications.

Webhook Security

Every webhook delivery includes an X-Intake-Signature header in the form:

X-Intake-Signature: t=1712345678,v1=5257a869e7...

t is the Unix timestamp (seconds) when the delivery was signed, and v1 is a hex-encoded HMAC-SHA256 signature of the string "{t}.{raw_request_body}", signed with your webhook secret.

To verify a delivery:

  1. Split the header on , and extract t and v1.
  2. Concatenate the timestamp, a literal ., and the raw request body (before any JSON parsing).
  3. Compute HMAC-SHA256 of that string with your webhook secret and hex-encode it.
  4. Compare it to v1 using a constant-time comparison.
  5. Optionally reject deliveries whose timestamp is too old (e.g. more than 5 minutes) to guard against replay.

Always verify signatures to ensure payloads are from Intake and haven't been tampered with.

POST Verification — Node.js

Verify in Node.js

Request
const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('='))
  );
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(parts.v1),
    Buffer.from(expected)
  );
}
POST Verification — Python

Verify in Python

Request
import hmac, hashlib

def verify_webhook(raw_body, signature_header, secret):
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    signed_payload = f"{parts['t']}.{raw_body}"
    expected = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(parts["v1"], expected)
POST Verification — Ruby

Verify in Ruby

Request
require 'openssl'

def verify_webhook(raw_body, signature_header, secret)
  parts = signature_header.split(',').to_h { |p| p.split('=', 2) }
  expected = OpenSSL::HMAC.hexdigest(
    'sha256', secret, "#{parts['t']}.#{raw_body}"
  )
  Rack::Utils.secure_compare(parts['v1'], expected)
end
POST Verification — PHP

Verify in PHP

Request
function verifyWebhook($rawBody, $signatureHeader, $secret) {
    $parts = [];
    foreach (explode(',', $signatureHeader) as $pair) {
        [$k, $v] = explode('=', $pair, 2);
        $parts[$k] = $v;
    }
    $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1']);
}

MCP Protocol

Intake exposes a native Model Context Protocol (MCP) server, letting AI agents like Claude, GPT, or custom agents manage your entire intake workflow programmatically.

Endpoint: POST https://dointake.com/mcp

Protocol: JSON-RPC 2.0 (MCP spec 2025-03-26)

Authentication: OAuth 2.1 (recommended) or API key Bearer token. Tool access respects read / write scopes.


Connecting as a Claude Connector (Recommended)

Intake is available as a native Claude Connector. This is the easiest way to connect — no API key or config files needed.

In Claude Desktop or claude.ai:

  1. Open Settings → Connectors
  2. Click Add Connector
  3. Enter the URL: https://dointake.com/mcp
  4. Claude will automatically discover the authorization server, open a consent screen, and connect to your Intake account

That's it. Claude handles authorization and token management automatically.


Connecting with an API Key

For programmatic agents, CI/CD, or clients that don't support OAuth, you can use an API key instead.

Claude Desktop — Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "intake": {
      "url": "https://dointake.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Cursor — Add to your .cursor/mcp.json:

{
  "mcpServers": {
    "intake": {
      "url": "https://dointake.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Protocol Flow

A typical MCP session follows three steps:

1. Initialize — Negotiate protocol version and discover server capabilities.

→ {"jsonrpc": "2.0", "id": 1, "method": "initialize",
   "params": {"protocolVersion": "2025-03-26",
              "clientInfo": {"name": "my-agent", "version": "1.0"}}}
← {"jsonrpc": "2.0", "id": 1, "result": {
     "protocolVersion": "2025-03-26",
     "capabilities": {"tools": {"listChanged": false}},
     "serverInfo": {"name": "intake", "version": "1.0.0", "mode": "live"}}}

The mode field in serverInfo reflects the credential used to connect — "test" for test API keys or test OAuth clients, "live" for production credentials.

2. List tools — Discover available tools and their input schemas.

→ {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
← {"jsonrpc": "2.0", "id": 2, "result": {"tools": [...]}}

3. Call a tool — Execute a tool with arguments.

→ {"jsonrpc": "2.0", "id": 3, "method": "tools/call",
   "params": {"name": "list_clients", "arguments": {}}}
← {"jsonrpc": "2.0", "id": 3, "result": {
     "content": [{"type": "text", "text": "[{...}]"}]}}

Tool results are returned as JSON text in the content array. Errors set isError: true:

← {"jsonrpc": "2.0", "id": 4, "result": {
     "content": [{"type": "text", "text": "Request not found"}],
     "isError": true}}

Example: Full Workflow

An agent can handle a complete intake flow without any dashboard interaction:

1. create_client(name, email)
2. create_request(client_id, title, items, co_clients)
3. attach_inline_form(request_id, name, fields)  — or attach_form_to_request
4. upload_pdf_template(file_base64, filename)     — optional
5. attach_pdf_to_request(request_id, template_id) — optional
6. send_request(request_id)
7. get_request(id) → poll for completion
8. get_form_responses(request_id) → read submitted data
TOOL tools/call → list_clients

list_clients

List all clients

Scope: read
TOOL tools/call → create_client

create_client

Create a new client

Scope: write
TOOL tools/call → update_client

update_client

Update a client's name, email, phone, notification preference, or SMS consent

Scope: write
TOOL tools/call → list_requests

list_requests

List document requests

Scope: read
TOOL tools/call → get_request

get_request

Get request with items, uploads, and portal URLs for every recipient

Scope: read
TOOL tools/call → create_request

create_request

Create a request with checklist items and co-clients

Scope: write
TOOL tools/call → update_request

update_request

Update title, description, due date, reminders, max_reminders, notify_professional_after_days

Scope: write
TOOL tools/call → add_checklist_item

add_checklist_item

Add a document item to a request

Scope: write
TOOL tools/call → update_item

update_item

Set review status and note on an item

Scope: write
TOOL tools/call → re_request_item

re_request_item

Re-request a rejected item

Scope: write
TOOL tools/call → send_request

send_request

Send a draft request to the client (supports skip_email)

Scope: write
TOOL tools/call → complete_request

complete_request

Mark a sent request as complete

Scope: write
TOOL tools/call → cancel_request

cancel_request

Cancel a request — closes the client portal and stops reminders, no email sent

Scope: write
TOOL tools/call → list_request_reminders

list_request_reminders

List reminder audit trail for a request

Scope: read
TOOL tools/call → list_uploads

list_uploads

List uploads for a request

Scope: read
TOOL tools/call → assign_upload

assign_upload

Assign an upload to a checklist item (correct misfiles)

Scope: write
TOOL tools/call → upload_to_item

upload_to_item

Upload a file (base64) directly onto a checklist item on the client's behalf

Scope: write
TOOL tools/call → list_form_templates

list_form_templates

List form templates

Scope: read
TOOL tools/call → get_form_template

get_form_template

Get template with fields

Scope: read
TOOL tools/call → create_form_template

create_form_template

Create template with fields

Scope: write
TOOL tools/call → add_form_field

add_form_field

Add a field to a form template

Scope: write
TOOL tools/call → attach_form_to_request

attach_form_to_request

Attach a form template to a request

Scope: write
TOOL tools/call → attach_inline_form

attach_inline_form

Create and attach a one-off form

Scope: write
TOOL tools/call → get_form_responses

get_form_responses

Get submitted form responses

Scope: read
TOOL tools/call → list_pdf_templates

list_pdf_templates

List PDF templates

Scope: read
TOOL tools/call → get_pdf_template

get_pdf_template

Get template with field info

Scope: read
TOOL tools/call → upload_pdf_template

upload_pdf_template

Upload a PDF (base64-encoded)

Scope: write
TOOL tools/call → attach_pdf_to_request

attach_pdf_to_request

Attach a PDF to a request as a fill request

Scope: write
TOOL tools/call → get_pdf_fill_signatures

get_pdf_fill_signatures

Return the signature audit trail (field, hash, IP, UA, signed_at) for a completed fill

Scope: read

Sandbox Mode

Test your integrations without affecting production data. Same API, same endpoints, same MCP tools — just use a test key.

How It Works

  1. Create a test API key in Settings → API Keys (select "Test" mode)
  2. Use the test key in your integration — same Authorization: Bearer header, same endpoints
  3. Toggle to Test mode in the dashboard to view sandbox data and captured emails
  4. When ready, swap to a live key — no code changes needed

What's Different in Sandbox

FeatureLive ModeTest Mode
DataProduction dataIsolated sandbox data
EmailsSent to real recipientsCaptured in Sandbox Inbox
Plan limitsEnforcedBypassed (unlimited test data)
WebhooksDelivered with "mode": "live"Delivered with "mode": "test"
File uploadsNormalNormal
Portal linksNormalShows "test request" banner
MCP toolsProduction dataSandbox data

OAuth Clients (MCP)

When registering an OAuth client via dynamic registration (POST /oauth/register), include "mode": "test" to create a test-mode client. All tokens issued by that client will access sandbox data only.

POST /oauth/register
{
  "client_name": "My Test Agent",
  "redirect_uris": ["https://example.com/callback"],
  "mode": "test"
}

Sandbox Inbox

In test mode, a Sandbox Inbox appears in the dashboard sidebar. Emails that would normally be sent to clients are captured here instead, showing the exact HTML that would have been delivered. Click any email to preview it, including working portal links.

Make.com Integration

Connect Intake to 1,000+ apps using Make.com (formerly Integromat). Here's a step-by-step guide:

1. Create a webhook in Make.com

Create a new scenario and add a "Webhooks" module → "Custom webhook". Copy the webhook URL.

2. Register the webhook in Intake

Use the API to create a webhook pointing to your Make.com URL:

POST /api/v1/webhooks
{"url": "https://hook.make.com/your-id", "events": ["request.completed"]}
3. Map the data

When Make.com receives its first webhook, it will learn the data structure. Use the fields to trigger actions in other apps — create a row in Google Sheets, send a Slack notification, update a CRM, etc.

4. Common recipes
  • Request completed → Slack notification — Get notified when a client finishes uploading documents
  • Upload created → Google Drive — Automatically save uploaded files to a specific folder
  • Deal created → CRM update — Sync new deals to your CRM system
Testing with Sandbox

Before going live, test your Make.com scenario with sandbox data:

  1. Create a test API key in Settings → API Keys
  2. Use the test key when registering your webhook: Authorization: Bearer dk_test_...
  3. Trigger test events — Make.com will receive webhooks with "mode": "test" in the payload
  4. Validate your scenario works end-to-end
  5. Swap to your live API key to go live

Zapier Integration

Connect Intake to 6,000+ apps using Zapier. Use webhooks to trigger Zaps when events occur in Intake.

1. Create a Catch Hook in Zapier

Create a new Zap and choose Webhooks by Zapier → Catch Hook as the trigger. Copy the webhook URL.

2. Register the webhook in Intake
POST /api/v1/webhooks
Authorization: Bearer YOUR_API_KEY
{"url": "https://hooks.zapier.com/hooks/catch/your-id", "events": ["request.completed", "document.uploaded"]}
3. Test and map fields

Trigger a test event (send a document request to a test client), then return to Zapier to map the incoming fields to your action step.

Testing with Sandbox
  1. Create a test API key in Settings → API Keys
  2. Register your webhook using the test key
  3. Create and send test requests — Zapier receives webhooks with "mode": "test"
  4. Build and validate your Zap with sandbox data
  5. When ready, create a new webhook with your live key to go live

Twin.so / MCP Client Setup

Connect any MCP-compatible AI agent (Twin.so, Claude Desktop, custom agents) to Intake.

Quick Start with API Key

Configure your MCP client with the Intake server URL and an API key:

{
  "url": "https://dointake.com/mcp",
  "headers": {"Authorization": "Bearer YOUR_API_KEY"}
}
OAuth 2.1 (Recommended for Production)

For production integrations, use OAuth 2.1 with dynamic client registration:

  1. Register your client: POST /oauth/register
  2. Direct user to authorize: GET /oauth/authorize
  3. Exchange code for token: POST /oauth/token
  4. Use the token: Authorization: Bearer ACCESS_TOKEN

See the MCP Protocol section for the full protocol flow.

Testing with Sandbox
  1. Create a test API key or register an OAuth client with "mode": "test"
  2. Connect your MCP client with test credentials
  3. The initialize response will show "mode": "test" in serverInfo
  4. All tool calls create and access sandbox data
  5. Emails are captured in the Sandbox Inbox — check the dashboard
  6. When satisfied, swap to live credentials — same tools, real data

Zoho One Integration

Connect Intake to Zoho One using Zoho Flow or Deluge scripts.

Option 1: Zoho Flow (No Code)
  1. In Zoho Flow, create a new flow with a Webhook trigger
  2. Copy the webhook URL
  3. Register it in Intake:
POST /api/v1/webhooks
Authorization: Bearer YOUR_API_KEY
{"url": "https://flow.zoho.com/your-webhook-url", "events": ["request.completed"]}
  1. Map incoming webhook fields to Zoho CRM, Zoho Books, or other Zoho apps
Option 2: Deluge Scripts (Custom Logic)

Call the Intake API directly from Deluge scripts in Zoho CRM, Creator, or other Zoho apps:

// Deluge script example: List clients
headers = Map();
headers.put("Authorization", "Bearer YOUR_API_KEY");
response = invokeurl
[
  url: "https://dointake.com/api/v1/clients"
  type: GET
  headers: headers
];
Testing with Sandbox
  1. Create a test API key in Intake (Settings → API Keys, mode: Test)
  2. Use dk_test_... key in your Zoho Flow webhooks or Deluge scripts
  3. Test your entire workflow with sandbox data
  4. Swap to your dk_live_... key when ready to go live