Skip to main content
Every part of the API follows the same handful of rules, applied by one shared layer, so once your developer has handled them for one request they are handled for all of them.

The short version

  • Every answer has the same shape. A successful response carries the record, or the list of records, under data, and anything extra under meta. Nothing arrives in a surprising place.
  • Errors tell you what to fix. Each error says what kind of problem it is, which field or setting it refers to, and gives a message that is safe to show to a person. Every error also carries a support id you can quote to us.
  • Long lists page. Deals, people, offers and so on come back a page at a time, up to 100 per page, with a pointer to the next page.
  • Retries are safe. Anything that sends something, such as a submission or a document request, can be retried without sending twice: the request carries an idempotency key, and a repeat with the same key replays the first answer instead of running again.
  • There is a fair-use limit of 300 requests a minute per key, which is far more than a busy brokerage’s integration needs. Notifications are the better way to learn about changes than asking repeatedly.
  • Nothing breaks without notice. New fields and endpoints can appear at any time; existing ones are not removed or renamed within the same version. Changes are listed in the changelog.

For developers

Requests

Base URL https://api.nextlevelmca.com/v1, HTTPS only, JSON in and out (Content-Type: application/json with any body), Authorization: Bearer <key or token> on every request (see Access and permissions). Creates return 201, everything else 200.

Response envelope

Single object
List
meta is empty for single objects, carries pagination for lists, and sometimes adds context (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.

Errors

type is one of seven values (branch on it); code is stable and machine-readable (branch on it, never on message); param names the field, header or scope concerned when there is one; request_id matches the X-Request-Id header every response carries, success or error, and is what support uses to find the trace. Some errors add fields: existing_id on duplicate_business and duplicate_person (the latter also has matched_by: email or phone), retry_after_seconds on a blocked submission retry.

Pagination

limit is the page size (1 to 100, default 25); cursor is the meta.next_cursor from the previous page. meta.has_more is true while there are more rows and meta.next_cursor is null on the last page. Keep filters identical between pages; cursors are opaque, and one the server cannot read is a 400 with code: "invalid_cursor". Lists are newest first (created_at desc, id desc) unless the endpoint says otherwise, and some also report meta.total (lender matches, submissions, deal activity). On /deals, /businesses, /businesses/{id}/deals and /people (unless filtered by business_id, phone or email) the cursor marks the last row you received, so a record created or deleted while you page doesn’t repeat or skip a row. Other lists page by position in the result (an offset), where a change between fetches can shift the window by one; dedupe on id if you need exactly-once processing. Cursors issued earlier keep working.

Idempotency

Send an Idempotency-Key header on any POST to make it safe to retry; use it on everything that sends email or text: submissions, document requests, portal links, webhook tests. UUIDs are a good choice.
  • Keys are scoped to the credential, up to 255 characters (longer is 400 with code: "invalid_idempotency_key").
  • The first successful response (status and body) is stored for 24 hours. A retry with the same key, method, path and body returns it with the header Idempotent-Replayed: true; the action does not run again.
  • The same key with a different body or path is 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: after a 4xx or 5xx, the same key runs the request again. Only POST reads the header; PATCH, PUT and DELETE are repeatable by nature.

Rate limits

Each credential is limited, per workspace, to 300 requests per minute with a burst of 50 requests per second, both fixed windows. Every response carries X-RateLimit-Limit (300), X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds when the minute resets). 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, or 1 when the burst tripped). Sleep for Retry-After, then retry; never retry in a tight loop. Limits are not per endpoint.

Timestamps, ids and values

Timestamps are ISO 8601 in UTC with millisecond precision (2026-09-04T14:02:11.000Z). Ids are UUID 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 run 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, optional parameters, response fields and webhook event types can appear without notice, so ignore fields you do not recognise. Breaking changes ship only in a new major version, with the old one kept running through a published sunset date.