> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://tieback.io/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://tieback.io/docs/_mcp/server.

# API: Minting

> Submit minting requests and manage Digital Product Passport issuance, covering batch lifecycle, unit generation, activation mode and export tracking.

> **Programmatic access — early access**
>
> The
>
> **tie*back***
>
> API is available to selected workspaces during early access. Endpoints in production today are
> authenticated with an authenticated session. Standalone API-key access is being onboarded per
> workspace. Contact
>
> **tie*back***
>
> to enable API credentials for your workspace.

## What It Does

The Minting API provides a single atomic gateway for submitting minting requests, along with lifecycle management for mint batches and units.

## Authentication & Permissions

All RPCs require an authenticated session. Minting operations require a role with sufficient permissions for the target brand (typically admin or owner).

## RPCs

### submit\_mint\_request

The primary entry point for minting. Performs preflight validation, enforces ERP batch linkage, and atomically creates a mint job + mint batch.

**Request**

```json
{
  "_brand_id": "uuid",
  "_product_id": "uuid",
  "_product_batch_id": "uuid",
  "_requested_quantity": 100,
  "_identity_mode": "batch",
  "_activation_mode": "active_at_mint",
  "_lots": [
    { "lot_code": "LOT-A", "qty": 60 },
    { "lot_code": "LOT-B", "qty": 40 }
  ],
  "_idempotency_key": "client-generated-uuid"
}
```

**Response (success)**

```json
{
  "ok": true,
  "mint_job_id": "uuid",
  "mint_batch_id": "uuid",
  "status": "QUEUED",
  "is_duplicate": false
}
```

**Response (blocked)**

```json
{
  "ok": false,
  "blocking_reasons": [
    {
      "code": "COMMERCIAL_STATUS_NOT_ACTIVE",
      "message": "Product commercial status must be ACTIVE"
    },
    { "code": "MINT_READINESS_NOT_READY", "message": "Product mint readiness status must be READY" }
  ],
  "product_id": "uuid",
  "product_batch_id": "uuid"
}
```

**Response (idempotent hit)**

```json
{
  "ok": true,
  "mint_job_id": "uuid",
  "mint_batch_id": "uuid",
  "status": "QUEUED",
  "is_duplicate": true
}
```

**Parameters**

| Parameter             | Type    | Required | Notes                                                                                                                                                    |
| --------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `_brand_id`           | uuid    | Yes      | Brand workspace                                                                                                                                          |
| `_product_id`         | uuid    | Yes      | Must belong to the brand                                                                                                                                 |
| `_product_batch_id`   | uuid    | No       | Production batch (ERP linkage). Optional, if null, ERP checks are bypassed and only the platform quantity limit applies.                                 |
| `_requested_quantity` | integer | Yes      | Must be > 0 and ≤ 1,000,000                                                                                                                              |
| `_identity_mode`      | text    | Yes      | `batch` or `serial` (lowercase). Maps to `mint_jobs.scope_type`.                                                                                         |
| `_activation_mode`    | enum    | Yes      | `active_at_mint` makes the unit live at issuance. `first_scan` is the legacy wire value for manual activation, where the brand activates the unit later. |
| `_lots`               | jsonb   | No       | Array of `{lot_code, qty}`. Batch mode only. Max 100. Sum must equal quantity.                                                                           |
| `_serials`            | jsonb   | No       | Reserved for future use                                                                                                                                  |
| `_idempotency_key`    | text    | No       | Client-generated UUID for retry safety                                                                                                                   |

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.
* Invalid input, request is malformed or cannot be processed.

**Notes**

* Product must have `commercial_status = ACTIVE` and `mint_readiness_status = READY`.
* If `_product_batch_id` is provided:
  * Production batch must exist and belong to the brand/product.
  * Production batch must have `status = READY`.
  * If the production batch has a quantity set, the requested quantity must not exceed it.
* If `_product_batch_id` is null:
  * ERP checks are bypassed entirely.
  * Only the platform safety limit (1,000,000 units) is enforced.
* In serial mode, `_lots` must not be provided.
* No mint units are created in this step, the async executor handles unit generation in the next slice.

---

### preflight\_mint\_batch

Runs a preflight check against a product and batch to determine minting eligibility.

> **Warning**
>
> `preflight_mint_batch` is a standalone check. For submitting actual mint requests, use
> `submit_mint_request` which absorbs preflight validation.

**Request**

```json
{
  "_brand_id": "uuid",
  "_product_id": "uuid",
  "_batch_id": "uuid",
  "_requested_quantity": 100,
  "_idempotency_key": "optional-string"
}
```

**Response (pass)**

```json
{
  "id": "uuid",
  "status": "preflight_pass",
  "preflight_result": "pass",
  "blocking_reasons": [],
  "scope_type": "serial",
  "requested_quantity": 100,
  "created_at": "2026-01-15T12:00:00Z"
}
```

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.
* Invalid input, request is malformed or cannot be processed.

---

### create\_mint\_batch\_from\_job (DEPRECATED)

> **Warning**
>
> **Deprecated.** Use `submit_mint_request` instead. This RPC is retained for backward compatibility
> only.

Creates a mint batch linked 1:1 to a completed preflight mint job. Gate check patched to accept `QUEUED` and `PREFLIGHTED` statuses.

---

### set\_mint\_batch\_status (planned, not yet exposed via public API)

Transitions a mint batch to a new lifecycle status with audit trail.

**Status values**: `draft` → `minted` → `exported` → `printed` → `applied` → `voided`

---

### list\_mint\_batches

Paginated listing of mint batches for a brand with deterministic cursor-based pagination.

**Request**

```json
{
  "_brand_id": "uuid",
  "_limit": 20,
  "_cursor_created_at": "2026-02-25T12:00:00Z",
  "_cursor_id": "uuid"
}
```

**Response**

```json
{
  "ok": true,
  "batches": [...],
  "has_more": true,
  "next_cursor_created_at": "2026-02-25T11:00:00Z",
  "next_cursor_id": "uuid"
}
```

**Parameters**

| Parameter            | Type        | Required | Notes                                                         |
| -------------------- | ----------- | -------- | ------------------------------------------------------------- |
| `_brand_id`          | uuid        | Yes      | Brand workspace                                               |
| `_limit`             | integer     | No       | Default 20                                                    |
| `_cursor_created_at` | timestamptz | No       | Cursor timestamp (both cursor fields required for pagination) |
| `_cursor_id`         | uuid        | No       | Cursor ID tie-break                                           |

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.

---

### list\_mint\_units

Paginated listing of mint units for a brand, with optional batch filter and deterministic cursor-based pagination.

**Request**

```json
{
  "_brand_id": "uuid",
  "_mint_batch_id": "uuid",
  "_limit": 50,
  "_cursor_created_at": "2026-02-25T12:00:00Z",
  "_cursor_id": "uuid"
}
```

**Response**

```json
{
  "ok": true,
  "units": [...],
  "has_more": false,
  "next_cursor_created_at": null,
  "next_cursor_id": null
}
```

**Parameters**

| Parameter            | Type        | Required | Notes                                                         |
| -------------------- | ----------- | -------- | ------------------------------------------------------------- |
| `_brand_id`          | uuid        | Yes      | Brand workspace                                               |
| `_mint_batch_id`     | uuid        | No       | Filter to specific batch                                      |
| `_limit`             | integer     | No       | Default 50                                                    |
| `_cursor_created_at` | timestamptz | No       | Cursor timestamp (both cursor fields required for pagination) |
| `_cursor_id`         | uuid        | No       | Cursor ID tie-break                                           |

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.

---

### list\_mint\_events

Paginated listing of mint events (activity/audit) for a brand with deterministic cursor-based pagination and optional filters.

**Request**

```json
{
  "_brand_id": "uuid",
  "_limit": 50,
  "_cursor_occurred_at": "2026-02-25T12:00:00Z",
  "_cursor_id": "uuid",
  "_event_type": "mint_job_completed",
  "_entity_type": "mint_batch",
  "_entity_id": "uuid-string"
}
```

**Response**

```json
{
  "ok": true,
  "events": [...],
  "has_more": false,
  "next_cursor_occurred_at": null,
  "next_cursor_id": null
}
```

**Parameters**

| Parameter             | Type        | Required | Notes                                                         |
| --------------------- | ----------- | -------- | ------------------------------------------------------------- |
| `_brand_id`           | uuid        | Yes      | Brand workspace                                               |
| `_limit`              | integer     | No       | Default 50                                                    |
| `_cursor_occurred_at` | timestamptz | No       | Cursor timestamp (both cursor fields required for pagination) |
| `_cursor_id`          | uuid        | No       | Cursor ID tie-break                                           |
| `_event_type`         | text        | No       | Filter by event type                                          |
| `_entity_type`        | text        | No       | Filter by entity type                                         |
| `_entity_id`          | text        | No       | Filter by entity ID (e.g. mint\_batch UUID cast to text)      |

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.

---

### record\_scan\_event (planned, not yet exposed via public API)

Records a scan telemetry event. Will be called by the edge resolver in a future slice.

---

### process\_mint\_job\_batch

The async mint executor database engine. Claims a queued mint job, generates identities in bounded chunks, and advances statuses deterministically.

**Request**

```json
{
  "_brand_id": "uuid",
  "_job_id": "uuid",
  "_max_units": 2000
}
```

**Response (in-progress)**

```json
{
  "ok": true,
  "done": false,
  "inserted_count": 2000,
  "minted_quantity": 2000,
  "requested_quantity": 10000
}
```

**Response (completed)**

```json
{
  "ok": true,
  "done": true,
  "inserted_count": 0,
  "minted_quantity": 10000,
  "requested_quantity": 10000
}
```

**Parameters**

| Parameter    | Type    | Required | Notes                                   |
| ------------ | ------- | -------- | --------------------------------------- |
| `_brand_id`  | uuid    | Yes      | Brand workspace                         |
| `_job_id`    | uuid    | Yes      | Mint job to process                     |
| `_max_units` | integer | No       | Max units per invocation (default 2000) |

**Mode behaviour**

| Mode                          | Behaviour                                                                      |
| ----------------------------- | ------------------------------------------------------------------------------ |
| Serial                        | Creates individual tokens in chunks. Resolver URL is written at creation time. |
| Lot (batch + lots)            | Validates existing lots. No individual tokens created. Finalises immediately.  |
| Product-only (batch, no lots) | No identities created. Finalises immediately.                                  |

**Status transitions**: `QUEUED` → `RESERVED` → `PROCESSING` → `COMPLETED` (or `FAILED`)

**Events emitted**: `mint_job_started`, `mint_units_created`, `mint_lots_confirmed`, `mint_job_completed`, `mint_job_failed`

> **Info**
>
> This RPC is the database engine only. The background runner that invokes it in a loop is handled
> by the platform's job scheduler.

## API Exposure Policy

All minting mutation RPCs require an **authenticated JWT**. Anonymous or public execution is explicitly disallowed for the minting control plane.

| Principle            | Detail                                                                    |
| -------------------- | ------------------------------------------------------------------------- |
| **Authentication**   | All mutation RPCs require a valid authenticated session                   |
| **Public access**    | Limited to resolver endpoints only                                        |
| **Idempotency**      | Supported via client-provided `_idempotency_key` on `submit_mint_request` |
| **PUBLIC execution** | Explicitly revoked on all minting functions                               |

> **Info**
>
> Resolver endpoints are public and callable without authentication. They include internal tenant
> validation. No minting control-plane RPC is anonymously callable.

---

### get\_mint\_kpis

Returns aggregate KPI values for the minting dashboard.

**Request**

```json
{
  "_brand_id": "uuid"
}
```

**Response**

```json
{
  "ok": true,
  "total_tokens_minted": 5000,
  "active_tokens": 120,
  "batches_created": 12,
  "exported_printed": 0
}
```

**Parameters**

| Parameter   | Type | Required | Notes           |
| ----------- | ---- | -------- | --------------- |
| `_brand_id` | uuid | Yes      | Brand workspace |

**Calculation rules**

| Metric                | Description                                                      |
| --------------------- | ---------------------------------------------------------------- |
| `total_tokens_minted` | Sum of minted quantities across all batches                      |
| `active_tokens`       | Count of tokens currently in active status                       |
| `batches_created`     | Total number of mint batches                                     |
| `exported_printed`    | Placeholder, export job tracking is planned for a future release |

**Notes**

* `list_mint_batches` returns a wrapped object with the canonical key `batches`: `{ "ok": true, "batches": [...], "has_more": bool, "next_cursor": string|null }`.
* Both `list_mint_batches` and `list_mint_units` include `product_sku` per record, avoiding N+1 queries. If the product is missing, `product_sku` is `null`.

---

### get\_mint\_batch\_by\_id

Returns a single mint batch by ID, including joined product metadata (`product_sku`, `product_name`).

**Request**

```json
{
  "_brand_id": "uuid",
  "_mint_batch_id": "uuid"
}
```

**Response (success)**

```json
{
  "ok": true,
  "batch": {
    "id": "uuid",
    "status": "minted",
    "identity_mode": "serial",
    "activation_mode": "active_at_mint",
    "requested_quantity": 100,
    "minted_quantity": 100,
    "resolver_base_url": "https://brand.tieback.io",
    "created_at": "2026-02-25T12:00:00Z",
    "product_id": "uuid",
    "product_name": "Widget Pro",
    "product_sku": "WGT-001"
  }
}
```

**Parameters**

| Parameter        | Type | Required | Notes             |
| ---------------- | ---- | -------- | ----------------- |
| `_brand_id`      | uuid | Yes      | Brand workspace   |
| `_mint_batch_id` | uuid | Yes      | Target mint batch |

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.
* Invalid input, request is malformed or cannot be processed.

**Notes**

* Access is enforced at the server level to prevent cross-brand access (BOLA/IDOR safe).
* Includes `product_name` and `product_sku` in the response, avoiding N+1 queries.
* `list_mint_units` response uses canonical key `units`: `{ "ok": true, "units": [...] }`.

---

### list\_payload\_packs

Paginated listing of payload packs (export artifacts) for a brand with deterministic cursor-based pagination.

**Request**

```json
{
  "_brand_id": "uuid",
  "_limit": 20,
  "_cursor_created_at": "2026-02-25T12:00:00Z",
  "_cursor_id": "uuid"
}
```

**Response**

```json
{
  "ok": true,
  "packs": [
    {
      "id": "uuid",
      "brand_id": "uuid",
      "mint_batch_id": "uuid",
      "status": "pending",
      "storage_path": "exports/brand-id/batch-id/pack.zip",
      "created_at": "2026-02-25T12:00:00Z",
      "updated_at": "2026-02-25T12:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor_created_at": null,
  "next_cursor_id": null
}
```

**Parameters**

| Parameter            | Type        | Required | Notes                                                         |
| -------------------- | ----------- | -------- | ------------------------------------------------------------- |
| `_brand_id`          | uuid        | Yes      | Brand workspace                                               |
| `_limit`             | integer     | No       | Default 20, max 100                                           |
| `_cursor_created_at` | timestamptz | No       | Cursor timestamp (both cursor fields required for pagination) |
| `_cursor_id`         | uuid        | No       | Cursor ID tie-break                                           |

**Errors**

* Not authenticated, request has no valid JWT.
* Forbidden, caller does not have sufficient access.
* Invalid input, request is malformed or cannot be processed.

**Notes**

* Access is enforced at the server level to prevent cross-brand access (BOLA/IDOR safe).
* `storage_path` is returned as a relative path. No signed URLs are generated by the list endpoint.
* `list_payload_packs` response uses canonical key `packs`: `{ "ok": true, "packs": [...] }`.

---

## Payload Pack Downloads

Payload pack artifacts are stored in a private storage bucket. Downloads require authenticated access and use time-limited signed URLs.

### Download Flow

Request a signed download URL for the pack's `storage_path`. Signed URLs are time limited, typically valid for five minutes, and must be requested from an authenticated session.

> **Warning**
>
> `storage_path` may be `null` for packs in `pending` status. Always guard against null before
> requesting a signed URL.

---

## Search

Global search is documented in [API: Search](/api-reference/api-search).

---

## Mint API v1 (Submit + Kick)

A Backend-For-Frontend operation that combines minting submission and immediate execution into a single call.

### Authentication

Requires a valid JWT in the `Authorization: Bearer <token>` header. The caller must have an `owner` or `admin` role for the target brand.

### Request Body

```json
{
  "brand_id": "uuid",
  "product_id": "uuid",
  "requested_quantity": 500,
  "identity_mode": "batch",
  "activation_mode": "active_at_mint",
  "idempotency_key": "uuid",
  "product_batch_id": "uuid (optional)",
  "lots": [
    { "lot_code": "LOT-A", "qty": 300 },
    { "lot_code": "LOT-B", "qty": 200 }
  ]
}
```

### Success Response (200)

```json
{
  "ok": true,
  "brand_id": "uuid",
  "mint_job_id": "uuid",
  "mint_batch_id": "uuid",
  "kicked": true,
  "is_duplicate": false,
  "status": "QUEUED",
  "note": null
}
```

### Error Response (4xx/5xx)

```json
{
  "ok": false,
  "brand_id": null,
  "mint_job_id": null,
  "mint_batch_id": null,
  "kicked": false,
  "error_code": "invalid_request",
  "error_message": "brand_id (uuid) required"
}
```

### Error Codes

| Code              | Status | Description                                                   |
| ----------------- | ------ | ------------------------------------------------------------- |
| `missing_auth`    | 401    | No Authorization header                                       |
| `invalid_token`   | 401    | JWT invalid or expired                                        |
| `forbidden`       | 403    | Caller lacks owner/admin role                                 |
| `invalid_request` | 400    | Missing or invalid required field                             |
| `submit_failed`   | 400    | Mint request validation failed                                |
| `submit_blocked`  | 200    | Preflight checks blocked the request (see `blocking_reasons`) |

### Kick Behaviour

After a successful submit, the platform performs exactly **one** bounded processing cycle (up to 20,000 units). The `kicked` field indicates whether this synchronous processing step succeeded.

**`kicked=false` does not mean the job is lost.** The platform's background job scheduler runs on a regular schedule and will pick up any queued jobs. The synchronous kick is a latency optimisation, not a requirement for job completion.

## Related Docs

* [Minting Overview](/minting/overview)
* [Minting Lifecycle](/minting/lifecycle)
* [API: Resolver](/api-reference/api-resolver)
* [Exports and Print Jobs](/minting/exports-print-jobs)