API: Minting

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

1{
2 "_brand_id": "uuid",
3 "_product_id": "uuid",
4 "_product_batch_id": "uuid",
5 "_requested_quantity": 100,
6 "_identity_mode": "batch",
7 "_activation_mode": "first_scan",
8 "_lots": [{"lot_code": "LOT-A", "qty": 60}, {"lot_code": "LOT-B", "qty": 40}],
9 "_idempotency_key": "client-generated-uuid"
10}

Response (success)

1{
2 "ok": true,
3 "mint_job_id": "uuid",
4 "mint_batch_id": "uuid",
5 "status": "QUEUED",
6 "is_duplicate": false
7}

Response (blocked)

1{
2 "ok": false,
3 "blocking_reasons": [
4 {"code": "COMMERCIAL_STATUS_NOT_ACTIVE", "message": "Product commercial status must be ACTIVE"},
5 {"code": "MINT_READINESS_NOT_READY", "message": "Product mint readiness status must be READY"}
6 ],
7 "product_id": "uuid",
8 "product_batch_id": "uuid"
9}

Response (idempotent hit)

1{
2 "ok": true,
3 "mint_job_id": "uuid",
4 "mint_batch_id": "uuid",
5 "status": "QUEUED",
6 "is_duplicate": true
7}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_product_iduuidYesMust belong to the brand
_product_batch_iduuidNoProduction batch (ERP linkage). Optional — if null, ERP checks are bypassed and only the platform quantity limit applies.
_requested_quantityintegerYesMust be > 0 and ≤ 1,000,000
_identity_modetextYesbatch or serial (lowercase). Maps to mint_jobs.scope_type.
_activation_modeenumYesactive_at_mint or first_scan
_lotsjsonbNoArray of {lot_code, qty}. Batch mode only. Max 100. Sum must equal quantity.
_serialsjsonbNoReserved for future use
_idempotency_keytextNoClient-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.

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

Request

1{
2 "_brand_id": "uuid",
3 "_product_id": "uuid",
4 "_batch_id": "uuid",
5 "_requested_quantity": 100,
6 "_idempotency_key": "optional-string"
7}

Response (pass)

1{
2 "id": "uuid",
3 "status": "preflight_pass",
4 "preflight_result": "pass",
5 "blocking_reasons": [],
6 "scope_type": "serial",
7 "requested_quantity": 100,
8 "created_at": "2026-01-15T12:00:00Z"
9}

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)

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: draftmintedexportedprintedappliedvoided


list_mint_batches

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

Request

1{
2 "_brand_id": "uuid",
3 "_limit": 20,
4 "_cursor_created_at": "2026-02-25T12:00:00Z",
5 "_cursor_id": "uuid"
6}

Response

1{
2 "ok": true,
3 "batches": [...],
4 "has_more": true,
5 "next_cursor_created_at": "2026-02-25T11:00:00Z",
6 "next_cursor_id": "uuid"
7}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_limitintegerNoDefault 20
_cursor_created_attimestamptzNoCursor timestamp (both cursor fields required for pagination)
_cursor_iduuidNoCursor 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

1{
2 "_brand_id": "uuid",
3 "_mint_batch_id": "uuid",
4 "_limit": 50,
5 "_cursor_created_at": "2026-02-25T12:00:00Z",
6 "_cursor_id": "uuid"
7}

Response

1{
2 "ok": true,
3 "units": [...],
4 "has_more": false,
5 "next_cursor_created_at": null,
6 "next_cursor_id": null
7}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_mint_batch_iduuidNoFilter to specific batch
_limitintegerNoDefault 50
_cursor_created_attimestamptzNoCursor timestamp (both cursor fields required for pagination)
_cursor_iduuidNoCursor 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

1{
2 "_brand_id": "uuid",
3 "_limit": 50,
4 "_cursor_occurred_at": "2026-02-25T12:00:00Z",
5 "_cursor_id": "uuid",
6 "_event_type": "mint_job_completed",
7 "_entity_type": "mint_batch",
8 "_entity_id": "uuid-string"
9}

Response

1{
2 "ok": true,
3 "events": [...],
4 "has_more": false,
5 "next_cursor_occurred_at": null,
6 "next_cursor_id": null
7}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_limitintegerNoDefault 50
_cursor_occurred_attimestamptzNoCursor timestamp (both cursor fields required for pagination)
_cursor_iduuidNoCursor ID tie-break
_event_typetextNoFilter by event type
_entity_typetextNoFilter by entity type
_entity_idtextNoFilter 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

1{
2 "_brand_id": "uuid",
3 "_job_id": "uuid",
4 "_max_units": 2000
5}

Response (in-progress)

1{
2 "ok": true,
3 "done": false,
4 "inserted_count": 2000,
5 "minted_quantity": 2000,
6 "requested_quantity": 10000
7}

Response (completed)

1{
2 "ok": true,
3 "done": true,
4 "inserted_count": 0,
5 "minted_quantity": 10000,
6 "requested_quantity": 10000
7}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_job_iduuidYesMint job to process
_max_unitsintegerNoMax units per invocation (default 2000)

Mode behaviour

ModeBehaviour
SerialCreates 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: QUEUEDRESERVEDPROCESSINGCOMPLETED (or FAILED)

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

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.

PrincipleDetail
AuthenticationAll mutation RPCs require a valid authenticated session
Public accessLimited to resolver endpoints only
IdempotencySupported via client-provided _idempotency_key on submit_mint_request
PUBLIC executionExplicitly revoked on all minting functions

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

1{
2 "_brand_id": "uuid"
3}

Response

1{
2 "ok": true,
3 "total_tokens_minted": 5000,
4 "active_tokens": 120,
5 "batches_created": 12,
6 "exported_printed": 0
7}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace

Calculation rules

MetricDescription
total_tokens_mintedSum of minted quantities across all batches
active_tokensCount of tokens currently in active status
batches_createdTotal number of mint batches
exported_printedPlaceholder — 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

1{
2 "_brand_id": "uuid",
3 "_mint_batch_id": "uuid"
4}

Response (success)

1{
2 "ok": true,
3 "batch": {
4 "id": "uuid",
5 "status": "minted",
6 "identity_mode": "serial",
7 "activation_mode": "first_scan",
8 "requested_quantity": 100,
9 "minted_quantity": 100,
10 "resolver_base_url": "https://brand.tieback.io",
11 "created_at": "2026-02-25T12:00:00Z",
12 "product_id": "uuid",
13 "product_name": "Widget Pro",
14 "product_sku": "WGT-001"
15 }
16}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_mint_batch_iduuidYesTarget 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

1{
2 "_brand_id": "uuid",
3 "_limit": 20,
4 "_cursor_created_at": "2026-02-25T12:00:00Z",
5 "_cursor_id": "uuid"
6}

Response

1{
2 "ok": true,
3 "packs": [
4 {
5 "id": "uuid",
6 "brand_id": "uuid",
7 "mint_batch_id": "uuid",
8 "status": "pending",
9 "storage_path": "exports/brand-id/batch-id/pack.zip",
10 "created_at": "2026-02-25T12:00:00Z",
11 "updated_at": "2026-02-25T12:00:00Z"
12 }
13 ],
14 "has_more": false,
15 "next_cursor_created_at": null,
16 "next_cursor_id": null
17}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_limitintegerNoDefault 20, max 100
_cursor_created_attimestamptzNoCursor timestamp (both cursor fields required for pagination)
_cursor_iduuidNoCursor 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

1// Generate a signed download URL (valid for 5 minutes)
2const { data, error } = await supabase.storage
3 .from('payload-packs')
4 .createSignedUrl(pack.storage_path, 300);

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


Search (MVP)

search_brand_objects_v1

A brand-scoped global search RPC that returns a unified list of tokens, batches, exports, and products matching a query string.

Request

1{
2 "_brand_id": "uuid",
3 "_query": "SER-00",
4 "_types": ["token", "batch", "product", "export"],
5 "_limit": 20,
6 "_cursor_created_at": null,
7 "_cursor_id": null
8}

Response

1{
2 "ok": true,
3 "items": [
4 {
5 "type": "token",
6 "id": "uuid",
7 "primary_label": "SER-001234",
8 "secondary_label": null,
9 "status": "minted",
10 "route": "/mint/batch/uuid",
11 "created_at": "2026-03-01T12:00:00Z"
12 }
13 ],
14 "has_more": false,
15 "next_cursor_created_at": null,
16 "next_cursor_id": null
17}

Parameters

ParameterTypeRequiredNotes
_brand_iduuidYesBrand workspace
_querytextYesMinimum 2 characters
_typestext[]NoFilter by object type: token, batch, product, export. Default: all.
_limitintegerNoDefault 20, max 100
_cursor_created_attimestamptzNoCursor timestamp (both cursor fields required for pagination)
_cursor_iduuidNoCursor ID tie-break

Matching rules

TypeMatch
TokenSerial prefix match
BatchID prefix match
ExportExact UUID match on ID or group ID
ProductSKU prefix or exact GTIN

Notes

  • Products are returned on the first page only (when no cursor is provided) and do not participate in cursor pagination.
  • Product name search is not included in the MVP. Full-text or substring search will be added in a future release.
  • Cursor-based pagination is index-backed for optimal read performance at enterprise scale.

Global Search (MVP)

The platform exposes a brand-scoped global search surface powered by the search_brand_objects_v1 RPC.

Behaviour

  • Prefix-only matching — all queries use prefix search (query%). No fuzzy matching, no full-text search, and no leading-wildcard (%query%) patterns.
  • Top 20 results — the MVP returns at most 20 results per query. No pagination is exposed in the UI.
  • Brand-scoped security — every search request is scoped to the authenticated user’s current brand via has_brand_access. Cross-brand search is not possible.
  • Object types — tokens, batches, exports, and products are searchable. Product results appear on the first page only.
  • Minimum query length — queries shorter than 2 characters return an empty result set.

Mint API v1 (Submit + Kick)

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

Endpoint

POST /functions/v1/mint-api-v1

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

1{
2 "brand_id": "uuid",
3 "product_id": "uuid",
4 "requested_quantity": 500,
5 "identity_mode": "batch",
6 "activation_mode": "first_scan",
7 "idempotency_key": "uuid",
8 "product_batch_id": "uuid (optional)",
9 "lots": [{"lot_code": "LOT-A", "qty": 300}, {"lot_code": "LOT-B", "qty": 200}]
10}

Success Response (200)

1{
2 "ok": true,
3 "brand_id": "uuid",
4 "mint_job_id": "uuid",
5 "mint_batch_id": "uuid",
6 "kicked": true,
7 "is_duplicate": false,
8 "status": "QUEUED",
9 "note": null
10}

Error Response (4xx/5xx)

1{
2 "ok": false,
3 "brand_id": null,
4 "mint_job_id": null,
5 "mint_batch_id": null,
6 "kicked": false,
7 "error_code": "invalid_request",
8 "error_message": "brand_id (uuid) required"
9}

Error Codes

CodeStatusDescription
missing_auth401No Authorization header
invalid_token401JWT invalid or expired
forbidden403Caller lacks owner/admin role
invalid_request400Missing or invalid required field
submit_failed400Mint request validation failed
submit_blocked200Preflight checks blocked the request (see blocking_reasons)

Kick Behaviour

After a successful submit, the function 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.