> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nextlevelmca.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How the API behaves

> Every answer has the same shape, errors tell you what to fix, long lists page, retries are safe, and there is a fair-use limit.

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](/developers/guides/webhooks) 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](/changelog/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](/developers/authentication)). Creates return `201`, everything else `200`.

### Response envelope

```json Single object theme={null}
{ "data": { "id": "0f6b0d2e-1c3a-4b8e-9d2f-7a1e5c4b3d21", "legal_name": "ACME Plumbing LLC" }, "meta": {} }
```

```json List theme={null}
{ "data": [ { "id": "3c9d1f70-…" }, { "id": "8a7b6c5d-…" } ], "meta": { "next_cursor": "eyJvIjoyNX0", "has_more": true } }
```

`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

```json theme={null}
{
  "error": {
    "type": "validation_error",
    "message": "requested_amount must be a positive number",
    "code": "invalid_request",
    "param": "requested_amount",
    "request_id": "req_Kq3xT9vLwZ1a"
  }
}
```

`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.

| HTTP | `type` | Typical `code` values |
| - | - | - |
| 400 | `validation_error` | `invalid_request` (`param` names the field), `invalid_field`, `invalid_id`, `invalid_cursor`, `invalid_idempotency_key`, `invalid_phone`, `invalid_document`, `stage_rule` (a stage move the board would refuse; the message is the one the app shows) |
| 401 | `authentication_error` | `unauthorized` (missing, malformed, expired or revoked credential) |
| 403 | `permission_error` | `missing_scope` (`param` is the scope you need), `forbidden` (the role lacks the permission) |
| 404 | `not_found` | `deal_not_found`, `business_not_found` and so on (`<resource>_not_found`). Records in another workspace are a 404, not a 403. |
| 409 | `conflict` | `duplicate_business`, `duplicate_person`, `duplicate_lender`, `idempotency_key_reused`, `idempotency_in_progress`, `no_email_sender`, `cannot_withdraw`, `upload_not_found`, `no_primary_offer`, `already_funded`, `already_dead` |
| 429 | `rate_limit_error` | `rate_limited` |
| 500 | `api_error` | `internal_error`. Retry with the same `Idempotency-Key`, or contact support with the request id. |

### 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.

```bash theme={null}
curl "$NLMCA_API/deals?limit=50&cursor=$NEXT_CURSOR" -H "Authorization: Bearer $NLMCA_KEY"
```

### 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.