# A typed v2 API built for real integrations

Source: https://docs.settlemint.com/docs/changelog/dalp-3-0/v2-api
The v2 REST API at /api/v2 adds JSON:API filtering and sorting, offset pagination, idempotency keys for safe retries, and headers that control identity, wallet routing, and response timing.



**The v2 REST API is the surface real integrations needed. Filter and sort every list, page through results, and retry mutations safely with an idempotency key. Transactions run asynchronously first, so a write survives a slow or stuck chain. The v2 API is served at `/api/v2`, and the v1 API stays frozen at `/api/v1`.**

<Video youtube="https://youtu.be/GJ00I5gH80c" title="A typed v2 API built for real integrations: DALP 3.0" description="JSON:API filtering and sorting, offset pagination, idempotency keys, and response-timing headers on a typed REST surface at /api/v2." />

<Spotlight
  eyebrow="v2 API"
  title="Query it like a database, retry it like it's safe."
  aside="<FactList items={[
  { label: &#x22;Base path&#x22;, value: &#x22;/api/v2&#x22; },
  { label: &#x22;Query conventions&#x22;, value: &#x22;JSON:API&#x22; },
  { label: &#x22;Safe retries&#x22;, value: &#x22;Idempotency-Key&#x22; },
  { label: &#x22;v1&#x22;, value: &#x22;Frozen at /api/v1&#x22; },
]} />"
>
  Every list endpoint accepts structured filters, a sort field and direction, and offset pagination. The JSON:API query conventions keep the syntax the same across resources. Mutations accept an idempotency key so a retried request after an uncertain response runs once, not twice. The interactive reference and OpenAPI specification are served live at `/api/v2`.
</Spotlight>

The v1 API answered a simple need: call an endpoint, get a result. Real integrations needed more.

They needed to ask for the 50 most recent holders of one token, retry a mint after a dropped connection without minting twice, and decide whether a call waits for on-chain settlement or returns immediately. The v2 API is built for that, on a typed contract the SDK generates a client from.

## Filter and sort every list [#filter-and-sort-every-list]

List endpoints accept structured filters using JSON:API bracket notation. A filter with no operator uses the field's default; an explicit operator goes in a second bracket. Supported operators are `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `iLike` (case-insensitive match), `inArray`, `notInArray`, `isEmpty`, and `isNotEmpty`. An `inArray` filter accepts up to 100 values.

```http
GET /api/v2/tokens?filter[status][eq]=active&filter[name][iLike]=acme&sortBy=createdAt&sortDirection=desc&limit=50&offset=0
```

Sorting takes a `sortBy` column and a `sortDirection` of `asc` or `desc`, defaulting to ascending by creation time. Each resource restricts `sortBy` to its own typed set of columns, so a sort request that an endpoint cannot honor is rejected at the contract rather than silently ignored.

## Page through results predictably [#page-through-results-predictably]

Pagination is offset based. Pass `limit` (default 50, maximum 200) and `offset`, and every collection response returns the links you need to walk the set without computing offsets yourself.

```json
{
  "data": [ /* ... */ ],
  "links": {
    "self": "/api/v2/tokens?limit=50&offset=0",
    "first": "/api/v2/tokens?limit=50&offset=0",
    "prev": null,
    "next": "/api/v2/tokens?limit=50&offset=50",
    "last": "/api/v2/tokens?limit=50&offset=200"
  }
}
```

## Asynchronous first, synchronous when you ask [#asynchronous-first-synchronous-when-you-ask]

A blockchain write takes real time to settle. The platform submits it, waits for it to be included in a block, and confirms it, and that takes seconds, or longer when the network is busy. An API that blocks the caller's connection until the chain confirms is fragile: a dropped request loses the result, a slow block ties up the client, and a stuck transaction hangs the call. So transaction routes on the v2 API are asynchronous first.

When you submit a transaction, the platform accepts it, queues it durably, and returns `202 Accepted` with a status URL straight away. The instruction then runs in the background on the durable transaction engine, so it survives a dropped connection, a restart, or a slow chain. You poll the status URL until the operation reaches a terminal state.

```http
POST /api/v2/tokens/{address}/mints
Prefer: respond-async

202 Accepted
{ "statusUrl": "/api/v2/transactions/tx_01J...", "status": "pending" }
```

### Switch to synchronous when you want the result inline [#switch-to-synchronous-when-you-want-the-result-inline]

For a short, interactive flow where you need the result before continuing, ask the platform to wait. Send `Prefer: wait=N` to hold the response for up to `N` seconds, clamped to between 5 and 99, while the operation settles. A synchronous wait never changes the outcome. It only changes whether the platform returns the result inline or hands back a status URL to poll.

* If the operation settles within the budget, the platform returns `200 OK` with the settled response.
* If the budget elapses first, it degrades to the same `202 Accepted` status URL, and you poll from there.

The platform echoes the directives it honored in the `Preference-Applied` response header.

```sh
curl -X POST https://your-platform.example.com/api/v2/tokens/0x1234.../mints \
  -H "x-api-key: YOUR_DALP_API_KEY" \
  -H "Idempotency-Key: mint-2026-05-17-001" \
  -H "Prefer: wait=30" \
  -H "Content-Type: application/json" \
  -d '{ "recipients": ["0x1111..."], "amounts": ["1000"] }'
```

### The SDK waits by default [#the-sdk-waits-by-default]

The TypeScript SDK sends `Prefer: wait=99` on mutations unless you set your own preference, so SDK calls return the settled result directly in most cases rather than a handle to poll. Opt back into the asynchronous handle with `Prefer: respond-async`, set on the client or on the individual call.

| Goal                                                     | What to send                       |
| -------------------------------------------------------- | ---------------------------------- |
| Get the settled result inline for a short operation      | `Prefer: wait=N` (5 to 99 seconds) |
| Accept the request now and track the status URL yourself | `Prefer: respond-async`            |
| Match the SDK default over raw HTTP                      | `Prefer: wait=99`                  |

Use a synchronous wait for short interactive flows. Use the asynchronous path for long-running operations, batch jobs, or any flow that already tracks transaction status and events. Pair `Prefer` with `Idempotency-Key` so a retry after a degraded `202` attaches to the original instruction instead of submitting a new one.

## Retry mutations safely [#retry-mutations-safely]

The hardest part of any write API is the uncertain response: the request left, but the reply never arrived. Send an `Idempotency-Key` header with a mutation and a retry is safe. The platform recognizes the key, runs the operation once, and returns the original result on the retry instead of executing it again. The key is scoped to your organization. Generate a unique key per instruction, store it next to your own job ID, and reuse it only when retrying the same request. Change the payload, route, or method and you create a new instruction with a new key.

```sh
curl -X POST https://your-platform.example.com/api/v2/tokens/mint \
  -H "x-api-key: YOUR_DALP_API_KEY" \
  -H "Idempotency-Key: mint-2026-05-17-001" \
  -H "Content-Type: application/json" \
  -d '{ "token": "...", "to": "...", "amount": "1000" }'
```

## Control identity and wallet per request [#control-identity-and-wallet-per-request]

A few headers let one API key act precisely without a separate credential per actor.

<LabelCards>
  <LabelCard label="X-Participant">
    Selects the participant the request acts as, in canonical `pp_<uuid>` form. Defaults to the authenticated session participant.
  </LabelCard>

  <LabelCard label="X-Executor">
    Selects the wallet that signs: a direct signing address or a smart wallet. Defaults to the organization's configured executor routing policy.
  </LabelCard>

  <LabelCard label="Prefer">
    Controls response timing for transactions: wait for settlement and return the result inline, or take the asynchronous handle. See the section above.
  </LabelCard>

  <LabelCard label="Idempotency-Key">
    Makes a mutation safe to retry: the same key runs the instruction once and replays the original result.
  </LabelCard>
</LabelCards>

## Errors your code can branch on [#errors-your-code-can-branch-on]

Every v2 response carries the same structured error envelope as the SDK and CLI. The envelope includes a stable `DALP-NNNN` id, a `category` that classifies the failure, a `retryable` boolean, a `message` for display, a `why` explaining the cause, and a `fix` describing the next step. When the platform knows how long to wait, it includes `retryAfterSeconds`. Calling code branches on the category and retryability, never on a parsed prose string. See the [error code reference](/docs/api-reference/errors/error-code-reference) and [error handling](/docs/api-reference/errors/error-handling).

## v1 stays available [#v1-stays-available]

The v2 API is the default, and `/api` redirects to `/api/v2`. The v1 API is not removed. It stays frozen at `/api/v1` with its request and response shapes unchanged, so existing integrations keep working without a migration deadline. Its specification remains available at `/api/v1/spec.json`. Move to v2 when you want filtering, sorting, idempotency keys, and response-timing control; stay on v1 until then.

<BeforeAfter
  before="{
  title: &#x22;v1: call and get a result&#x22;,
  points: [
    &#x22;List endpoints returned a set, with no structured filtering or sorting.&#x22;,
    &#x22;No standard idempotency, so a retried write risked running twice.&#x22;,
    &#x22;No per-request control over synchronous or asynchronous settlement.&#x22;,
    &#x22;Frozen and supported at /api/v1, with unchanged shapes.&#x22;,
  ],
}"
  after="{
  title: &#x22;v2: built for integrations&#x22;,
  points: [
    &#x22;JSON:API filtering, sorting, and offset pagination on every list.&#x22;,
    &#x22;Idempotency keys make mutations safe to retry after an uncertain response.&#x22;,
    &#x22;The Prefer header chooses synchronous or asynchronous settlement per call.&#x22;,
    &#x22;A typed contract the SDK generates a client from, served at /api/v2.&#x22;,
  ],
}"
/>

[Get started with the API →](/docs/api-reference/reference/getting-started) · [Request headers →](/docs/api-reference/reference/request-headers)
