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

# API Conventions

> Patterns and conventions used across the Conduit API

## Versioning

All endpoints are prefixed with `/v2`. The version is part of the URL path, not a request header.

```bash theme={null}
GET https://api.conduit.financial/v2/customers
```

## Request Format

All request bodies use JSON (`Content-Type: application/json`), except file uploads which use `multipart/form-data`.

## Response Format

All responses return JSON.

* **Dates** are ISO 8601 strings in UTC (e.g., `2026-01-15T09:30:00.000Z`).
* **Optional fields with no value** are omitted from the response, never sent as `null` — a field like `clientReferenceId` is simply absent when none was supplied.
* **A few fields are nullable** — when a field's description says it can be `null` (for example the wire-receive bank details on a deposit, or `payout.rail` on webhook payloads), `null` means "no value yet / not applicable", distinct from an omitted optional field. These fields are always present within their parent object — but the parent itself may be optional.
* **Empty collections** are returned as `[]`, never `null`.

## IDs

Resources use prefixed IDs (e.g., `cus_abc123`, `app_xyz789`). The prefix identifies the resource type:

| Prefix | Resource                 |
| ------ | ------------------------ |
| `app`  | Application              |
| `cus`  | Customer                 |
| `doc`  | Document                 |
| `org`  | Organization             |
| `ord`  | Order                    |
| `txn`  | Transaction              |
| `usr`  | User                     |
| `vac`  | Virtual account          |
| `vrf`  | Verification             |
| `wda`  | Webhook delivery attempt |
| `wdl`  | Webhook delivery         |
| `wep`  | Webhook endpoint         |
| `wlr`  | Whitelist recipient      |
| `wlt`  | Wallet                   |
| `wsg`  | Wallet signer            |

## Pagination

List endpoints use cursor-based pagination. Pass query parameters to control paging:

| Parameter   | Default   | Description                                                      |
| ----------- | --------- | ---------------------------------------------------------------- |
| `cursor`    | *(none)*  | Opaque cursor from a previous response. Omit for the first page. |
| `limit`     | 20        | Maximum results to return (1-100).                               |
| `direction` | `forward` | `forward` or `backward` relative to the cursor.                  |

All list responses use the `{data, meta}` envelope shown above. `meta.mode` is `"cursor"` or `"offset"` depending on the endpoint.

```json theme={null}
{
  "data": [...],
  "meta": {
    "mode": "cursor",
    "nextCursor": "eyJpZCI6ImN1c18yeFBxTjhSIn0",
    "previousCursor": null,
    "total": 42
  }
}
```

Use `meta.nextCursor` as the `cursor` parameter in the next request to fetch the next page. When `nextCursor` is `null`, there are no more results.

A cursor is tied to the sort order it was issued under. If you change the sort (`sortBy`/`sortOrder`) you must start again from the first page without a cursor — reusing an older cursor under a different sort returns `400 INVALID_CURSOR` instead of a misleading page.

<Note>
  Cursors are opaque strings. Do not parse or construct them manually.
</Note>

## Errors

All error responses follow [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) (Problem Details for HTTP APIs):

```json theme={null}
{
  "type": "VALIDATION_ERROR",
  "title": "Validation Error",
  "status": 400,
  "detail": "The field 'email' is not a valid email address.",
  "resolution": "Check the 'errors' array in the response for specific field-level issues and correct your request payload accordingly.",
  "docs": "https://conduit-v2.mintlify.app/errors#validation-error",
  "instance": "/v2/customers",
  "correlationId": "corr_xyz789",
  "timestamp": "2026-01-15T09:30:00.000Z"
}
```

| Field           | Description                                                                                            |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `type`          | Stable string code identifying the error. Use this for programmatic error handling.                    |
| `title`         | Human-readable summary. May change between versions.                                                   |
| `status`        | HTTP status code.                                                                                      |
| `detail`        | Specific explanation of what went wrong in this request.                                               |
| `resolution`    | Suggested steps to fix the issue.                                                                      |
| `docs`          | Link to the documentation page for this error code.                                                    |
| `instance`      | The resolved request URI for this occurrence (query string stripped). Include when contacting support. |
| `correlationId` | Request correlation ID for tracing.                                                                    |
| `timestamp`     | When the error occurred (ISO 8601 UTC).                                                                |

<Note>
  Always match on the `type` field for error handling, not `status` or `title`.
  The `type` code is stable across API versions.
</Note>

`VALIDATION_ERROR` (400), `ONBOARDING_NOT_READY` (422), and `DOCUMENT_IDS_NOT_FOUND` (400) include an `errors` array with one entry per offending field. Each entry has a `pointer` (JSON pointer to the invalid field), a `detail` (what is wrong, never echoes the rejected value), and an optional `category` (`field`, `document`, or `individual`) when the entry came from the requirements validator. `category` lets you group blockers without parsing pointers; it's omitted on top-level schema rejections.

```json theme={null}
{
  "type": "ONBOARDING_NOT_READY",
  "title": "Onboarding Not Ready",
  "status": 422,
  "detail": "Validation failed",
  "resolution": "Check the errors array for field-level details and fix the request body.",
  "docs": "https://conduit-v2.mintlify.app/errors#onboarding-not-ready",
  "instance": "/v2/onboarding",
  "correlationId": "req_xyz",
  "timestamp": "2026-04-27T20:00:00.000Z",
  "errors": [
    {
      "pointer": "/businessInfo/taxId",
      "detail": "Tax ID is required",
      "category": "field"
    },
    {
      "pointer": "/documentIds",
      "detail": "At least one document is required",
      "category": "document"
    },
    {
      "pointer": "/ownership/persons",
      "detail": "At least 1 CONTROLLING_PERSON is required",
      "category": "individual"
    }
  ]
}
```

For a complete list of error codes, see the [Error Codes Reference](/errors).

## Idempotency

POST, PATCH, and PUT requests support idempotency via the `idempotency-key` header. Only successful responses are cached. Within a 5-minute window:

* **Same key + same body after a success** — returns the cached response instead of creating a duplicate resource. Replayed responses carry an `Idempotency-Replayed: true` header.
* **Same key + different body, after a success or a `5xx`** — rejected with `409 IDEMPOTENCY_KEY_CONFLICT`. The key is bound to its first request body for the 5-minute window, so a modified payload never silently runs under another request's key; send a genuinely new request with a fresh key.
* **Retry after a `4xx`** — client errors commit nothing, so retrying with the same key is re-evaluated against current state, and a **corrected** body is accepted on the same key (fix a validation error and retry — no new key needed). On order execution, for example, a `422 INSUFFICIENT_FUNDS` clears on retry once the balance covers the request. Payouts additionally bind the key to a terminally failed attempt — to retry a failed payout, use a fresh key.

```bash theme={null}
curl -X POST https://api.conduit.financial/v2/onboarding \
  -H "x-api-key: YOUR_API_KEY" \
  -H "idempotency-key: unique-request-id" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
```

Use a client-generated UUID or a deterministic key derived from the operation (e.g., `onboard-{customer-external-id}`).
