Skip to content
Levr API Documentation
Download OpenAPI specOpenAPI

Hand off to an AI agent

Copy instructions to your AI agent

Copy this guide into your AI agent. It links to the current REST API and MCP documentation.

You are helping someone integrate their system with Levr. Use Levr's published documentation and machine-readable contracts as the source of truth. Do not invent endpoints, fields, or MCP tools.

  1. Start with the Levr API documentation to choose between a server-to-server REST integration and a user-authorized MCP connection.
  2. For REST, read the Markdown REST API guide, choose the user’s task, then fetch only the relevant resource guide and operation Markdown. Resource indexes include task guidance. For funding recommendations, start with the Matchmaking guide. Use the OpenAPI specification for exact paths, authentication, parameters, and envelope schemas.
  3. For REST client intake, Deal applications, or project Criteria, fetch that resource's contract_url. Its Application Contract is the authority for the current questions, stable keys, repeatable groups, file-transfer steps, accepted answer shapes, conditional visibility, supported operation URLs, and integration instructions. Repeatable fields require the returned item_id. File bytes use the expiring upload_url and multipart field named file; they never belong in JSON. Do not substitute a static example or the latest application definition.
  4. For an AI client acting with a signed-in user's access, read the MCP connection instructions in Markdown first, then the Model Context Protocol guide if needed. Let the connected MCP server expose its current tools and input contracts; do not infer tools from the REST API. Read the relevant MCP resource guide and follow the discovered tool’s revision, file-transfer, and confirmation requirements.
  5. Use URLs from the record handoff when supplied; otherwise use this documentation environment. Keep API keys and file-transfer credentials out of browser code, logs, prompts, and source control.
  6. Before implementing a write, verify authentication, required inputs, idempotency, error responses, and retry guidance in the current contract. If an operation is not documented, do not assume it exists.

Early access

Request an integration

Tell us what your integration needs to do. Add an email if you want a reply.

Intake application guides

Complete and submit client intake

Read as Markdown

Complete an existing client intake: save answers, attach documents, and submit it to the receiving Portal, or email the form to the client to finish. Start with the intake returned by business creation or an existing intake you can access.

Before you start: your key needs client_intake_applications:write with access to this intake. This permission covers reading, editing, and submitting. Emailing the form separately requires client_intake_sharing:write. Saving answers alone does not submit the intake.

Every request uses Authorization: Bearer <api-key>. JSON writes use Content-Type: application/json. Give each answer write, file upload, and submission its own Idempotency-Key; retain the exact request and key for retries. Examples use abbreviated responses and illustrative IDs. Use returned URLs and question keys from the live contract.

Read the application's detail URL to find the links for this workflow:

GET /v1/client-intake-applications/ciappl_example/

200 OK — relevant fields

{
  "application": {
    "id": "ciappl_example",
    "status": { "key": "pre_submitted" },
    "contract_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/contract/",
    "answers_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/answers/",
    "submission_readiness_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/submission-readiness/",
    "submit_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/submit/"
  }
}

1. Read the questions and save answers

Read the application’s contract_url. It describes this intake's questions, accepted values, and available operations.

GET /v1/client-intake-applications/ciappl_example/contract/

Response — example question, abbreviated

{
  "questions": [
    {
      "block_key": "request",
      "label": "Funding request",
      "fields": [
        {
          "field_key": "amount",
          "value_schema": { "type": "string" }
        }
      ]
    }
  ]
}

The keys above are examples, not standard fields present in every intake. Match your data to the actual questions and their value_schema.

Read the application's answers_url to obtain the current values and its ETag header. Suppose that header is ETag: "0". Save the amount using that exact ETag:

PATCH /v1/client-intake-applications/ciappl_example/answers/
If-Match: "0"
Idempotency-Key: website-lead-12345-amount-1
{
  "updates": [
    { "block_key": "request", "field_key": "amount", "value": "250000" }
  ]
}

200 OK — relevant fields; response header ETag: "1"

{
  "answers_revision": 1,
  "updated_answers": [
    { "block_key": "request", "field_key": "amount", "value": "250000" }
  ]
}

When reading answers, pass a non-null next_cursor as the next request’s cursor query parameter until you have read every relevant page.

To correct the amount later, PATCH the same keys with the new value, the latest answers ETag, and a new idempotency key. Save only known facts. For repeated rows such as owners, POST to the contract's operations.repeatable_items_url with the group's block_key, the latest answers ETag, and a new idempotency key. Retain the returned item.item_id, then PATCH that URL with item_id and updates. Use the group and field keys from your contract.

2. Attach a supporting file

If the contract contains a file question, request an upload URL at its operations.file_uploads_url. For example, an intake might ask for a bank statement under the block key bank_statement:

POST /v1/client-intake-applications/ciappl_example/answers/files/
{ "block_key": "bank_statement" }

201 Created — relevant fields

{
  "upload_url": "https://YOUR_API_HOST/v1/client-intake-applications/ciappl_example/answers/files/upl_example/",
  "multipart_field": "file"
}

Send the file bytes to that upload_url as one multipart field named file. Use the current answers ETag, respect the returned expiry and size limit, and let your HTTP client generate the multipart boundary. The short command here illustrates the binary upload; the other examples use JSON.

curl -X POST '<upload_url>' \
  -H 'Authorization: Bearer <api-key>' \
  -H 'If-Match: "1"' \
  -H 'Idempotency-Key: website-lead-12345-statement-1' \
  -F 'file=@bank-statement.pdf'

201 Created — relevant fields; response header ETag: "2"

{ "answers_revision": 2 }

The upload attaches the file to its question. Do not add a file URL to the answer JSON. Read operations.files_url for attachment metadata; for a file inside a repeated row, include its item_id when requesting the upload capability.

After a connection failure, retry the original upload URL, file, key, and ETag to recover the result. Request a new upload URL only after a confirmed expiry rejection. On 412, read the latest answers and reconcile changes before starting a new write attempt.

3. Complete and submit the intake

Your integration can finish the intake, or the client can complete it in a branded web form. Both use the same saved answers and files.

Let the client finish

With client_intake_sharing:write, email the client a secure link to fill in the remaining answers, upload documents, and submit:

POST /v1/client-intake-applications/ciappl_example/share/
Idempotency-Key: website-lead-12345-share
{
  "recipients": [{ "email": "maya@example.com" }],
  "message": "Please complete and submit your intake form."
}

201 Created — relevant fields

{
  "grants": [{ "email": "maya@example.com", "access_mode": "edit" }],
  "delivery_state": "queued"
}

The request queues an email containing the link. These intake links grant editing access and expire after 30 days. queued does not confirm delivery. Follow the submission webhook below to know when the client finishes.

Retry the original request and idempotency key after a lost response. To resend access deliberately, use a new key: this refreshes the invitation and revokes the recipient's previous sessions, so they should use the newest email.

Submit through your integration

With client_intake_applications:write, complete the remaining questions, then GET the application's submission_readiness_url:

GET /v1/client-intake-applications/ciappl_example/submission-readiness/

Ready response — relevant fields

{
  "has_blocking_issues": false,
  "blocking_issue_count": 0,
  "issues": [],
  "submission_token": "<returned-submission-token>"
}

Readiness checks both the form and whether its current status permits submission. If there are blocking issues, submission_token is null. Correct the reported issues and check readiness again. A ready response supplies a token for the application and answers you just checked. Pass it unchanged to submit_url:

POST /v1/client-intake-applications/ciappl_example/submit/
Idempotency-Key: intake-example-submit-1
{ "submission_token": "<returned-submission-token>" }

200 OK — relevant fields

{
  "application": {
    "id": "ciappl_example",
    "status": { "key": "submitted" },
    "submitted_at": "2026-10-15T16:30:00Z"
  }
}

If the application or answers change after the readiness check, submission returns 412. Review the changes, check readiness again, and send the new token with a new idempotency key. After a connection failure, retry the original token and key to recover the result. Submission uses the token instead of an If-Match header; answer edits still use ETags.

Submission validates the answers again, records the submission, and notifies the receiving team according to the Portal's notification settings. If the Portal has enabled stage synchronization and mapped a stage to submitted intakes, submission moves the business to that stage. It also starts automated intake preparation when applicable. That background work can continue after the submission response; a successful submit does not mean downstream preparation is finished.

In the receiving Portal, open Businesses → the business → Client intake to review the submitted form and attachments.

4. Follow subsequent updates

Subscribe to client_intake_application.submitted and client_intake_application.status_changed using the webhook setup guide. Set up the subscription before handing off or submitting the form if you need to capture that first event. Verify signatures, deduplicate deliveries, and reread the application to get its current state. Add client_intake_application.updated if you also need to follow saved answer changes before submission.

The Portal and shared form manage their own live websocket updates. Your API integration uses webhooks or REST polling; it does not need a websocket connection to follow the client's progress.

Without webhooks, poll the application detail URL at an appropriate interval. Your import is complete when the intended answers and files are saved and application.status.key is submitted; later review belongs to the receiving team.

If a step fails

ResponseNext action
401, 403, or record not foundCheck the environment, ID, key permissions, and source scope. Read-only access uses client_intake_applications:read; editing and submitting require client_intake_applications:write.
412Reread the affected resource and reconcile concurrent changes. For submission, check readiness again after reviewing the latest application and answers.
409 on a retryReuse a key only for the identical command, including its precondition.
Validation errorCorrect the named field against the live contract; for submission, inspect readiness and complete the missing answers.

Keep the application ID after any failure and resume the same intake.