Skip to main content
Everything on /v1 follows the rules on this page. They are applied by one shared request pipeline, so they hold for every endpoint.

Requests

  • Base URL https://api.nextlevelmca.com/v1, HTTPS only.
  • JSON in, JSON out. Send Content-Type: application/json with any request body.
  • Authenticate with Authorization: Bearer <key or token>. See Authentication.

Response envelope

Every successful response is an object with data and meta.
Single object
List
meta is empty for single objects, carries pagination for lists, and sometimes adds context (for example meta.deal on lender matches, meta.primary_offer_id on offers, meta.warning when a send step was skipped). Even DELETE /v1/webhooks/{id} returns an object ({ "deleted": true, "id": … }) so the envelope never changes shape. Creates return 201, everything else 200.

Errors

Every error is an error object with the same five fields, never a bare message.
string
required
One of the seven types below. Use it to decide how to react.
string
required
Human-readable and safe to show to an operator or an AI agent.
string
Stable and machine-readable. Branch on this, not on message.
string
The request field, header or scope the error refers to, when there is one.
string
required
The same value as the X-Request-Id response header. Quote it when contacting support.
Errors can carry extra machine-readable fields next to the standard ones; existing_id on duplicate_business and duplicate_person (the latter also has matched_by: email or phone) is the common case.

Request ids

Every response, success or error, carries X-Request-Id: req_…. Error bodies repeat it as error.request_id. Log it next to your own request logs; support uses it to find the server-side trace.

Pagination

List endpoints page with a cursor.
  • meta.has_more is true while there are more rows; meta.next_cursor is null on the last page.
  • Keep filters identical between pages. Cursors are opaque; do not build or edit them. A cursor the server cannot read returns 400 with code: "invalid_cursor".
  • Lists are ordered newest first (created_at desc, id desc) unless the endpoint says otherwise.
  • Some lists also report meta.total (lender matches, submissions, deal activity).
In v1 the cursor encodes an offset, so a row inserted while you page can shift the window by one. If you need exactly-once processing across pages, dedupe on id.

Idempotency

Send an Idempotency-Key header on any POST to make it safe to retry. Use it on everything that sends email or text messages: submissions, document requests, portal links, webhook tests.
This is exactly what the server does:
  • Keys are scoped to the credential, so two keys in the same location never collide. Maximum 255 characters; longer keys are a 400 with code: "invalid_idempotency_key". UUIDs are a good choice.
  • The first successful response (status and body) is stored for 24 hours. A retry with the same key, method, path and body returns the stored response with the header Idempotent-Replayed: true and the original status code. The action does not run again.
  • The same key with a different body or path is a 409 with code: "idempotency_key_reused".
  • While the first request is still running, a concurrent retry gets 409 with code: "idempotency_in_progress". Wait a moment and retry.
  • Only successful responses are stored. If the first attempt fails with a 4xx or 5xx, retrying with the same key runs the request again.
  • Only POST reads the header. PATCH, PUT and DELETE are repeatable by nature.

Rate limits

Each credential (API key or OAuth client) is limited, per location, to:
  • 300 requests per minute, and
  • a burst of 50 requests per second.
Both are fixed windows. Every response carries the minute-window headers: Exceeding either limit returns 429 with type: "rate_limit_error", code: "rate_limited" and a Retry-After header in seconds: the time to the minute reset when the per-minute limit tripped, or 1 when the burst did. Sleep for Retry-After, then retry. Do not retry in a tight loop. Limits are not per endpoint. Webhooks are the cheap way to learn about changes; poll only for what webhooks do not cover.

Timestamps, ids and values

  • Timestamps are ISO 8601 in UTC with millisecond precision, for example 2026-09-04T14:02:11.000Z.
  • Ids are UUIDs, as strings. CRM ids (such as ghl_contact_id) appear as read-only fields where a record is synced.
  • Money is a JSON number in US dollars; percentages are numbers from 0 to 100; rates such as factor_rate are decimals (1.32).
  • Phone numbers are returned as stored. GET /v1/people?phone= accepts any format and normalises both sides to E.164 digits before matching.

Versioning

The major version is in the path: /v1. Within v1, changes are additive: new endpoints, new optional parameters, new response fields and new webhook event types can appear without notice, so ignore fields you do not recognise. Breaking changes (removing or renaming fields, changing types or semantics) only ship in a new major version, with the old one kept running through a published sunset date. Changes are listed in the changelog.

Related topics

QuickstartChangelog