POST/api/crm/v1/intake-submissions

Submit a lead from a website form

For a website's server holding a Vision API client with crm:intake:create (identity class website_bff or app_service), bound to the CRM workspace. Creates one quarantined intake submission in the caller's own workspace; never reads. See the guide 'Collect leads from your website'.

App
CRM
Contract
CRM API 9.12.1
Lifecycle
preview
Runtime
restricted
Operation ID
Not declared (legacy exception)
Canonical origin
Not declared; gateway routing required

Purpose and use cases

Submit a lead from a website form

The owning App is Vision CRM. CRM owns its records, workflow rules, workspace membership, provenance, and record-level authorization.

Request

POST /api/crm/v1/intake-submissions

This contract intentionally declares no direct server URL. Obtain the routed App origin before attempting the request.

Authentication and authorization

Send a Vision-issued bearer token for the exact App audience. The token must include the crm entitlement and satisfy the crm workspace required binding.

  • Required scope: crm:intake:create

Human roles are not declared in OpenAPI. Record-level and business permission checks remain the owning App's authority.

Headers

  • Authorization: Bearer <access-token> — required.
  • Accept: application/json — recommended where a JSON response is declared.
  • Content-Type: application/json — required for the documented request representation.

Undeclared tracing, idempotency, conditional-request, and version headers are not assumed on this page.

Path and query parameters

This operation declares no path or query parameters.

Request body

Media type: application/json. The body is required.

FieldTypeRulesDescription
contractVersion"vision-crm-lead-form.v1"requiredNot described in the contract.
sourcestringrequired; minimum length 1; maximum length 120Which form this came from, in the owner's words. Becomes the lead's source and tag.
namestringrequired; minimum length 2; maximum length 120Not described in the contract.
emailstring | null · emailoptional; maximum length 254Not described in the contract.
phonestring | nulloptional; pattern ^\+?[0-9() .-]{7,32}$Not described in the contract.
companystring | nulloptional; maximum length 160Not described in the contract.
messagestring | nulloptional; maximum length 4000Not described in the contract.
answersarrayoptional; maximum items 100Extra labelled answers in display order, shown on the lead as Form answers.
consentobject | nulloptionalOnly when the form really asked: an explicit checkbox and its notice version.
consent.purposeinquiry_response | consultation | marketingrequiredNot described in the contract.
consent.channelemail | phonerequiredNot described in the contract.
consent.action"granted"requiredNot described in the contract.
consent.captureMethod"explicit_checkbox"requiredNot described in the contract.
consent.noticeVersionstringrequired; minimum length 1; maximum length 160Not described in the contract.
consent.occurredAtstring · date-timerequiredNot described in the contract.
pipelineKeystring | nulloptional; maximum length 60; pattern ^[a-z][a-z0-9_-]*$Stable key of the deal pipeline an accepted lead opens in (its first open stage). Omit for the main pipeline.
requestedRoutefollow_up | bookoptional; default "follow_up"Not described in the contract.
pagestringoptional; default "/"; maximum length 160; pattern ^/[a-z0-9/_-]*$Not described in the contract.
websitestringoptional; default ""; maximum length 200Honeypot. Pass the hidden field's value through; anything non-empty is dropped with 204.
testbooleanoptional; default falseTest submission: kept out of the Leads queue, never accepted, expires in 30 days.
idempotencyKeystringrequired; pattern ^[A-Za-z0-9_-]{16,120}$One per form submission; reuse only to retry the identical payload.

Runnable examples

cURL
curl '${APP_BASE_URL}/api/crm/v1/intake-submissions' \
  --request POST \
  --header 'authorization: Bearer ${ACCESS_TOKEN}' \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --data '{
    "contractVersion": "vision-crm-lead-form.v1",
    "source": "<source>",
    "name": "<name>",
    "email": "<email>",
    "phone": "<phone>",
    "company": "<company>",
    "message": "<message>",
    "answers": [
      {
        "label": "<label>",
        "value": "<value>"
      }
    ],
    "consent": {
      "purpose": "inquiry_response",
      "channel": "email",
      "action": "granted",
      "captureMethod": "explicit_checkbox",
      "noticeVersion": "<noticeVersion>",
      "occurredAt": "2026-08-21T18:00:00Z"
    },
    "pipelineKey": "<pipelineKey>",
    "requestedRoute": "follow_up",
    "page": "/",
    "website": "",
    "test": false,
    "idempotencyKey": "<idempotencyKey>"
  }'
JavaScript (server-side)
const response = await fetch('${APP_BASE_URL}/api/crm/v1/intake-submissions', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${ACCESS_TOKEN}`,
    accept: 'application/json',
    'content-type': 'application/json',
  },
  body: JSON.stringify({
  "contractVersion": "vision-crm-lead-form.v1",
  "source": "<source>",
  "name": "<name>",
  "email": "<email>",
  "phone": "<phone>",
  "company": "<company>",
  "message": "<message>",
  "answers": [
    {
      "label": "<label>",
      "value": "<value>"
    }
  ],
  "consent": {
    "purpose": "inquiry_response",
    "channel": "email",
    "action": "granted",
    "captureMethod": "explicit_checkbox",
    "noticeVersion": "<noticeVersion>",
    "occurredAt": "2026-08-21T18:00:00Z"
  },
  "pipelineKey": "<pipelineKey>",
  "requestedRoute": "follow_up",
  "page": "/",
  "website": "",
  "test": false,
  "idempotencyKey": "<idempotencyKey>"
}),
}

if (!response.ok) throw new Error(`Vision API ${response.status}`)
const result = await response.json()
Python 3 standard library
import json
import urllib.parse
import urllib.request

request = urllib.request.Request(
    '${APP_BASE_URL}/api/crm/v1/intake-submissions',
    data=json.dumps({"contractVersion":"vision-crm-lead-form.v1","source":"<source>","name":"<name>","email":"<email>","phone":"<phone>","company":"<company>","message":"<message>","answers":[{"label":"<label>","value":"<value>"}],"consent":{"purpose":"inquiry_response","channel":"email","action":"granted","captureMethod":"explicit_checkbox","noticeVersion":"<noticeVersion>","occurredAt":"2026-08-21T18:00:00Z"},"pipelineKey":"<pipelineKey>","requestedRoute":"follow_up","page":"/","website":"","test":false,"idempotencyKey":"<idempotencyKey>"}).encode(),
    headers={
    "Accept": "application/json",
    "Authorization": "Bearer <ACCESS_TOKEN>",
    "Content-Type": "application/json"
},
    method='POST',
)
with urllib.request.urlopen(request, timeout=30) as response:
    result = json.load(response)

The current TypeScript reference client covers Platform token exchange and core reads. No native SDK helper is declared for this operation; use the HTTP contract directly and follow the SDK guidance.

Responses

StatusMeaningSchema
200Exact replay of an earlier submission with the same idempotency keyobject
201Received for reviewobject
204Honeypot filled: nothing storednot specified
400The request is invalidobject
401Authentication is missing or invalidobject
403The authenticated actor lacks the CRM capability or workspace bindingobject
409Idempotency key reused with a different payloadobject
413Body larger than 64 KiBobject
429Too many submissions from this API client (300 per 5 minutes). Honour Retry-After.object
503The CRM dependency or authenticated API configuration is unavailableobject

Success response schema

FieldTypeRulesDescription
schemaVersion"vision-crm-intake-receipt.v1"requiredNot described in the contract.
status"received_for_review"requiredNot described in the contract.
publicReferencestringrequired; pattern ^IN-[A-Z0-9]{12}$Not described in the contract.
receivedAtstring · date-timerequiredNot described in the contract.
replayedbooleanrequiredNot described in the contract.
schedulingContinuationobject | nullrequiredNot described in the contract.
testbooleanrequiredNot described in the contract.
200 illustrative response
{
  "schemaVersion": "vision-crm-intake-receipt.v1",
  "status": "received_for_review",
  "publicReference": "<publicReference>",
  "receivedAt": "2026-08-21T18:00:00Z",
  "replayed": true,
  "schedulingContinuation": "<schedulingContinuation>",
  "test": true
}

Errors and troubleshooting

Error bodies and codes are shown only where the approved contract declares them. Use status, the declared error schema, and any correlation identifier returned by the App; do not infer that two Apps share one envelope.

  • 400 — The request is invalid
  • 401 — Authentication is missing or invalid
  • 403 — The authenticated actor lacks the CRM capability or workspace binding
  • 409 — Idempotency key reused with a different payload
  • 413 — Body larger than 64 KiB
  • 429 — Too many submissions from this API client (300 per 5 minutes). Honour Retry-After.
  • 503 — The CRM dependency or authenticated API configuration is unavailable

Collection behavior

Pagination: not declared. Filtering/search: not declared. Sorting: not declared.

Cursor lifetime, cursor binding, stable ordering, maximum traversal, and unknown-filter behavior are not assumed unless stated by a parameter description or schema constraint above.

Operational behavior

  • Rate limit: The operation declares 429, but the numeric policy is not in OpenAPI.
  • Retry: Retry only when the documented error model or response headers authorize it. Timeout and backoff values are not declared by this operation.
  • Idempotency: No idempotency header or replay window is declared.
  • Concurrency: No ETag, If-Match, or optimistic-version header is declared.

Security and privacy

  • Keep client credentials and bearer tokens on trusted servers.
  • Send only to the exact approved HTTPS origin and audience.
  • Treat response data according to the owning App's classification and retention policy; the OpenAPI contract does not itself grant data access.
  • Do not log credentials, tokens, complete private payloads, or secrets.

Edge cases and known documentation gaps

  • No stable operation ID (legacy exception).
  • No direct server URL; gateway routing is unresolved here.
  • The request body has no purpose or conditional-rule description.
  • Business roles, timeout, retry budget, rate value, idempotency window, and concurrency behavior are absent unless explicitly stated above.

Contract history and evidence