Sandbox APIhttps://sandbox-api.nuvante.io

Guides

Issuer-side API

Stablecoin issuers sit on the other side of a transaction's burn or mint leg. This page describes the contract between Nuvanté and you as an issuer.

Who this is for

Most of these docs are written for clearing members, who start redemptions and swaps using assets that other people issue. This section is for stablecoin issuers: participants with participantKind: "stablecoin_issuer" and the manageIssuedAssets capability.

If you're a clearing member, you can skip this section. If you're an issuer, most of the clearing-member pages still apply to you (authentication, error handling, response envelopes), and the endpoints below are just for issuers.

How it fits together

Clearing members never call your API directly. Nuvanté sits in the middle. We call you to instruct a burn or mint, and we use your declared capabilities to decide how. The full field list is in Issuer capability declaration.

This section covers the two things you own:

  • declaring your capability profile
  • the instruction and webhook contract your API needs to meet, if feedbackChannel includes anything besides POLLABLE_RESOURCE

Declaring your capability profile

Code sample
PUT https://sandbox-api.nuvante.io/api/v1/issuers/{issuerId}/capabilities

This needs manageIssuedAssets. It uses the same schema as Issuer capability declaration. We repeat it here because this is the endpoint that writes the profile.

Code sample
{
  "commitPoint": "ON_INSTRUCTION",
  "preBurnTransferRequired": true,
  "custodyModel": "PER_MEMBER_ISSUER_WALLET",
  "mintDeliveryTarget": "REGISTERED_HANDLE",
  "feedbackChannel": ["SIGNED_WEBHOOK", "POLLABLE_RESOURCE"],
  "fiatLeg": "NUVANTE_SETTLES_RTGS",
  "ledger": "stellar",
  "finalityClass": "DETERMINISTIC",
  "amountPrecision": 2,
  "approvalSlaSeconds": 45,
  "apiBaseUrl": "https://issuer-api.partner-bank.test",
  "webhookUrl": "https://issuer-api.partner-bank.test/nuvante/webhooks"
}

Changing commitPoint or preBurnTransferRequired changes which mode (1, 2 or 3) Nuvanté works out for your transactions. See Compare the modes.

On this endpoint, custodyModel describes your issuer wallet structure (PER_MEMBER_ISSUER_WALLET or POOLED_ISSUER_WALLET). The participant-level custodyModel (self-custody, mpc or caas) is a separate field.

Profile updates apply to transactions started after the update. Transactions already in flight carry on with the snapshot they started with, as described for swaps in Initiate a stable-to-stable swap.

The response includes a profileVersion number. Nuvanté records which profileVersion each transaction used. Clearing members can't see it on the transaction resource, but you can look it up per transaction in the admin console when you reconcile.

Credential exchange

On top of the API keys used everywhere else, issuer-gateway integrations need:

  • An mTLS client certificate, which you submit as a CSR during onboarding. Nuvanté's issuer-gateway service presents its own client certificate when it calls your apiBaseUrl. Verify it against the CA bundle we give you at onboarding.
  • OAuth2 client credentials for our calls to you, if your apiBaseUrl needs bearer auth as well as mTLS. We share these outside the sandbox portal during onboarding.
  • A webhook signing secret, if feedbackChannel includes SIGNED_WEBHOOK. It's generated in the same way as the secrets described in Event notifications.

All of this happens during onboarding. A tenant admin with manageIssuedAssets submits the CSR and OAuth client credential details through the portal as part of the issuer onboarding flow. The rest of this page describes what your API has to do once credentials are exchanged, so you can build and test against it before that flow starts.

The instruction API you must implement

If your commitPoint is ON_INSTRUCTION (Modes 2 and 3), Nuvanté calls your apiBaseUrl to instruct burns and mints. Your API needs these routes:

Code sample
POST {apiBaseUrl}/burn-instructions
POST {apiBaseUrl}/mint-instructions
GET  {apiBaseUrl}/instructions/{id}

Here's a request to POST /burn-instructions:

Code sample
{
  "idempotencyKey": "5c9e...a1f0",
  "reference": "NUV-123e4567-e89b-42d3-a456-426614174000",
  "sourceWalletId": "wal_nuv_mem_014",
  "amount": "250000.00",
  "assetCode": "GBPC"
}

A request to POST /mint-instructions looks the same as a burn, with one extra field, targetWalletId, telling you where to send the minted asset:

Code sample
{
  "idempotencyKey": "7b2a...c3e1",
  "reference": "NUV-123e4567-e89b-42d3-a456-426614174000",
  "targetWalletId": "wal_nuv_mem_017",
  "amount": "250000.00",
  "assetCode": "GBPC"
}

The synchronous response from POST /mint-instructions:

Code sample
{ "instructionId": "your-internal-mint-id", "status": "RECEIVED" }

A couple of things to know about these fields:

  • idempotencyKey is a UUIDv5 built from (commandId, leg). If we replay a request with the same key and body, you must return the original result and avoid creating a second instruction.
  • reference is Nuvanté's transaction reference. It's at most 35 characters, matching the width of an ISO 20022 EndToEndId. Store it in your own records so you can match an instruction back to a Nuvanté transaction without a second lookup.

Your synchronous response:

Code sample
{ "instructionId": "your-internal-id", "status": "RECEIVED" }

status has to be one of the values in the table below. If your process has a separate authorisation step (Mode 2), stop at RECEIVED or PENDING_APPROVAL. Hold off executing, and wait for Nuvanté to call the execute endpoint described further down.

Status
What it means
What Nuvanté does
RECEIVED
Accepted, nothing done yet
Waits, and either polls or waits for your webhook
PENDING_APPROVAL
Waiting for your internal sign-off
Same as above, with no action on your instruction
APPROVED
You're ready to execute when asked, and nothing has moved yet
Requests the RTGS earmark, then calls execute
EXECUTING
In progress
Waits
COMPLETED
Finished successfully
Records the proof and moves on to settlement
REJECTED
A clear no, before the operation completed
Unwinds automatically: cancels the earmark and returns the locked asset
FAILED
Finished, but you can't confirm that nothing moved
Parks the transaction for an operator. We never take this as proof that nothing happened.

This table mirrors the normalised events from the issuer's side. Please take care to get REJECTED and FAILED right. REJECTED triggers a safe, automatic unwind. FAILED parks the transaction, because Nuvanté has no way to check that your side rolled back. Please only use FAILED for that genuinely uncertain case, since every FAILED sends the transaction to a person to sort out.

If your profile is two-phase (Mode 2), you also need to implement:

Code sample
POST {apiBaseUrl}/instructions/{id}/execute

We only call this after confirming the RTGS earmark, and once it's called there's no going back. Your API should treat this call as the signal to actually move the asset. The earlier burn-instruction call only sets things up.

Sending status by webhook

If feedbackChannel includes SIGNED_WEBHOOK, POST status changes to the webhookUrl you declared. This works like Event notifications in reverse: here you're sending to Nuvanté, where a clearing member would be receiving.

Code sample
{
  "instructionId": "your-internal-id",
  "status": "COMPLETED",
  "onchain": { "txHash": "0x...", "ledgerSequence": 48213904 },
  "finalizedAt": "2026-08-04T09:04:33.000Z"
}

Sign it the same way Nuvanté signs webhooks to clearing members. Use an HMAC over {timestamp}.{body} with the shared secret from onboarding, and send it in a Nuvante-Signature header in the format described in Verifying signatures. We verify your signature when it arrives. If a delivery is unsigned or the signature is wrong, we drop it, log it and don't retry. Test your signing against the sandbox harness before you go live.

We treat each webhook from you as a prompt to check, and we don't act on its contents directly. After every delivery, Nuvanté calls GET {apiBaseUrl}/instructions/{id} before doing anything. So please make your GET handler at least as reliable as your webhook delivery. If one of them is down, the other is how we'll find out what happened.

Mode 3 refund endpoint

Every Mode 3 issuer implements a refund endpoint as well as the burn and mint instructions. Nuvanté calls it when an instruction expires after the tokens have already been transferred.

Code sample
POST https://{issuer-api}/treasury/refunds

{
  "transactionId": "e7a1c3f5-9b2d-4e6f-8a0c-2b4d6f8a1c3e",
  "reason": "instruction_expired",
  "refundToWalletId": "wallet_source_original"
}
Code sample
200 OK

{
  "transactionId": "e7a1c3f5-9b2d-4e6f-8a0c-2b4d6f8a1c3e",
  "status": "refunded",
  "refundTxHash": "5f2c8a9e...",
  "refundedAt": "2026-09-01T10:04:12.000Z"
}

Participants and issuers can follow recovery progress through GET /api/v1/transactions/{id}/recovery-status on the Nuvanté API.

Conformance testing

Before you go live, your instruction API has to pass the conformance suite from the issuer capability framework. It covers:

  • the happy path for each instruction type you support
  • an explicit rejection, checking that the earmark is cancelled and the asset is returned
  • silence beyond your declared approvalSlaSeconds, checking that the transaction parks
  • replaying a request with the same idempotency key
  • verifying callback signatures

Sandbox has a harness for this:

Code sample
POST https://sandbox-api.nuvante.io/api/v1/issuers/{issuerId}/conformance/run
GET  https://sandbox-api.nuvante.io/api/v1/issuers/{issuerId}/conformance/results

It sends synthetic instructions to your declared apiBaseUrl in sandbox and records a pass or fail for each scenario. Your profile can only be promoted from sandbox to production once every mandatory scenario passes. Sandbox trial lists the scenarios as they run against clearing-member flows. The issuer-side scenarios have the same shape, run from the other direction.