Core concepts
Every value-moving call in the MYRT API creates an order, and every order has the same shape. This page explains that shape and the rules the operation guides rely on. Read it once before you mint, redeem or transfer.
Orders
A mint, a redeem and a transfer are all orders. They are created by different routes, but they serialise to one object. You receive it as the 201 body of every create call, as the 200 body of every read, and as each entry of transactions[] on GET /v1/transactions (planned). Webhook payloads carry a subset of the same fields under data.
{
"ok": true,
"referenceId": "acme:mint:2026-09-10:00417",
"status": "payment_pending",
"phase": "PAYMENT_PENDING",
"direction": "MINT",
"chainId": 1,
"network": "Ethereum",
"tokenAddress": "0x...",
"walletAddress": "0xAbC0000000000000000000000000000000000001",
"amounts": {
"myr": "1000.00",
"myrt": "1000.00",
"feeMyr": "1.00",
"amountRaw": "1000000000",
"decimals": 6
},
"blockchain": { "txHash": null, "blockNumber": null },
"timestamps": { "createdAt": "2026-09-10T04:12:33.918Z", "updatedAt": null }
}| Field | Type | Description |
|---|---|---|
referenceId | string | The handle you chose on creation. See identifiers |
status | string | Business state. Drive your logic from it. See order lifecycle |
phase | string | Operational detail for progress display and support tooling |
direction | string | MINT, REDEEM or TRANSFER. See direction |
chainId | number | The chain the order settles on |
network | string | Display name of that chain |
tokenAddress | string | The MYRT contract on that chain |
walletAddress | string | The wallet involved. Its meaning depends on direction |
amounts | object | Decimal strings, never numbers. See amounts |
blockchain.txHash | string or null | Transaction hash. null until broadcast |
blockchain.blockNumber | string or null | Block that included the transaction. null until broadcast |
timestamps.createdAt | string | ISO 8601 UTC. See timestamps |
timestamps.updatedAt | string or null | null until the first update |
Every response body also carries ok. On success it is true and the order fields sit beside it. On failure it is false and the body carries error instead. Codes are listed on the errors page.
Order lifecycle
status is the business truth. phase is operational detail. Branch on status. Show phase to operators and in progress displays, and nowhere else.
Status
status | Meaning | Terminal |
|---|---|---|
payment_pending | Mint recorded, fiat not yet confirmed | no |
pending | Accepted, awaiting settlement | no |
processing | Submitted, awaiting on-chain confirmation | no |
completed | Confirmed on chain. Final | yes |
failed | No value moved. Final | yes |
cancelled | Cancelled before settlement | yes |
No status values will be added in v1. Changing the meaning of a status value is a breaking change and happens only in a new major version. See versioning.
Phase
phase values currently emitted:
ORDER_CREATEDPAYMENT_PENDINGPENDING_APPROVALAML_SCREENINGBROADCASTINGON_CHAIN_CONFIRMEDCOMPLETEDFAILED
A cancelled order reports phase: "FAILED" while its status is cancelled. Read status to tell the two apart.
Handle unknown phases
New phase values may be added without notice. Never fail an order, or a parser, on a phase you do not recognise. status is the field that stays fixed.
A mint, from creation to completion
- You
POST /v1/mintswithfiatConfirmed: false. The order is recorded withstatus: "payment_pending"andphase: "PAYMENT_PENDING". No MYRT is issued. - Your MYR settles. You post the same
referenceIdagain withfiatConfirmed: true, on a key holdingmint:execute. The order moves topending: accepted, awaiting settlement. - The order is submitted for settlement and moves to
processing.blockchain.txHashandblockchain.blockNumberstaynulluntil the transaction is broadcast. - The chain reaches its confirmation count and the order moves to
completed. Sandbox waits for 3 confirmations, live for 15. Read the count fromconfirmationson/v1/config.
A txHash alone is not final. Credit nothing until status is completed.
A mint created with fiatConfirmed: true is submitted for settlement straight away. Redeems and transfers are submitted on creation, so their 201 response already reports status: "pending" and phase: "ORDER_CREATED". payment_pending applies to mints only.
A mint that has not yet been submitted for settlement can be cancelled (planned) and then reports cancelled. One that has already been submitted returns 409 ORDER_NOT_CANCELLABLE. See the mint guide.
Poll the read endpoint at a sensible interval, or use webhooks once they ship. Either way, read the order back before you credit anyone.
Direction
direction says which way value moves. It decides what walletAddress and the amounts mean, and which route returns the order.
direction | What happens | walletAddress | Create | Read |
|---|---|---|---|---|
MINT | MYR in, MYRT issued to a wallet | The recipient | POST /v1/mints | GET /v1/mints/{referenceId} |
REDEEM | MYRT burned from a wallet, MYR paid to the customer's bank account | The wallet MYRT is burned from | POST /v1/redeems | GET /v1/redeems/{referenceId} |
TRANSFER | MYRT moved to a recipient on the customer's behalf | The recipient | POST /v1/transfers | GET /v1/transfers/{referenceId} |
Each read route returns only its own direction. GET /v1/transactions/{referenceId} returns an order of any direction and needs transaction:read. An order that exists but has the wrong direction for the route, or that another integration created, returns 404 NOT_FOUND, the same as an order that does not exist.
Amounts
Amounts on requests and on the order are decimal strings, never JSON numbers. decimals is always 6.
| Field | Meaning |
|---|---|
myr | The MYR side of the order. Depends on direction. null on a transfer |
myrt | The MYRT side of the order |
feeMyr | The fee recorded on the order, in MYR |
amountRaw | myrt scaled by 10^6, as an integer string. The exact on-chain value |
decimals | Always 6 |
What myr, myrt and feeMyr mean per direction:
direction | myr | myrt | feeMyr |
|---|---|---|---|
MINT | The MYR you funded | Equals myr. Issuance is 1:1 | The payment rail charge recorded on the order. It does not reduce myrt |
REDEEM | The net MYR paid out after feeMyr | The MYRT burned | The charge myr is net of |
TRANSFER | null | The MYRT moved | "0" |
Fees are quoted server-side and returned on the order. Partners do not compute fees.
A request amount (amountMyr on a mint, amountMyrt on a redeem or transfer) must be a positive decimal string with at most 6 decimal places. Anything else returns 400 INVALID_AMOUNT.
Amount strings are not zero-padded. 1000 and 1000.00 are the same value, and either can appear. A balance read returns "balance": "212.4" for the same amount that an error meta reports as "212.40". Never compare amount strings. Parse them with a decimal library, or compare amountRaw as integers. To match an order against an ERC-20 Transfer event, compare amountRaw with the event's value.
Do not use floating point for money
JSON numbers, and the default number type in most languages, are IEEE-754 doubles. A double cannot represent most decimal fractions exactly, and the error compounds across additions and comparisons. Keep amounts as strings until you hand them to a decimal type, or do integer arithmetic on amountRaw. Never parse an amount as a float, and never sum floats to reconcile.
Customers and corporates
customerId is an opaque identifier that MYRT assigns when a customer is linked to your integration, for example cus_9f2a3b4c. It is not a MYRT account id, an email address, or anything a customer could be recognised by. Linking happens during onboarding with your account manager, not through the API.
Every value-moving call carries customerId: mint, redeem and transfer.
| Condition | Result |
|---|---|
customerId is not linked to your integration, or does not exist | 404 NOT_FOUND. The two cases are indistinguishable |
| The customer has not cleared verification | 403 CUSTOMER_NOT_VERIFIED on mint, redeem and transfer |
Corporates use corporateId, for example cor_71bd5e29. A corporateId appears only on GET /v1/corporates/{corporateId}.
Reading verification status for a customer or a corporate needs account:read, which is granted only under a signed data processing agreement. Without one the call returns 403 SCOPE_DENIED, even if the key was issued with the scope. Both account endpoints are documented ahead of release. Neither returns names, contact details, identity document numbers or addresses; the customerId is the only handle you hold. See accounts and corporates and the accounts reference.
Identifiers
Four identifiers appear across the API. Different parties choose them and they serve different purposes.
| Identifier | Chosen by | Format | Where it is used |
|---|---|---|---|
referenceId | You | 8 to 100 characters: letters, digits, ., _, -, :. Unique within your integration | The body of every create call. The path of every read. Reconciliation. data.referenceId on webhook events |
Idempotency-Key | You | 1 to 255 characters. One UUID per logical operation | A request header on every POST. Not part of the order object |
Event id | MYRT | For example evt_01J8ZQK4M7XN2R | Every webhook payload. Deduplicate deliveries on it |
txHash | The chain | 0x... | blockchain.txHash on the order and on webhook payloads, once the transaction is broadcast |
The difference matters when a call is retried. Sending the same Idempotency-Key with the same body replays the original status and body with X-MYRT-Idempotent-Replay: true, and nothing is created twice. The same key with a different body returns 409 IDEMPOTENCY_CONFLICT. Generate one UUID per logical operation, not per HTTP attempt, and reuse it on a timeout.
A referenceId outlives the request. For a mint or a transfer, posting an existing referenceId again returns the existing order. For a redeem, reusing a referenceId with different details returns 409 IDEMPOTENCY_CONFLICT. A referenceId that fails the format rule returns 400 INVALID_REFERENCE_ID, on reads as well. Store the referenceId before you send the request, so a timeout never leaves you with an order you cannot find. The full rules are under idempotency and reference IDs.
Two more values are safe to quote to support: the key id, the 16 hexadecimal characters in the third segment of your key, and meta.correlationId from a 500 SERVER_ERROR.
Timestamps
timestamps.createdAt and timestamps.updatedAt are ISO 8601 in UTC, for example 2026-09-10T04:12:33.918Z. createdAt is set once. updatedAt is null until the order changes for the first time, so treat null as "never updated", not as an error.
The same format is used for createdAt on webhook events, for verifiedAt on account reads (null unless verified), and for the createdAfter and createdBefore filters on GET /v1/transactions.
Two values are Unix time in seconds rather than ISO 8601: the X-RateLimit-Reset response header and the t component of the X-MYRT-Signature webhook header.
Your clock matters. Your webhook verifier should reject an event whose t is more than 300 seconds from your own time, so a skewed clock rejects valid events. GET /v1/system/time takes no key and returns serverTime with timezone: "UTC". Use it to detect skew. See server time.
Environments
- Sandbox,
https://sandbox-api.myrt.money/v1, keys start withmyrt_sandbox_: a testnet deployment with test fiat rails. No real money moves. - Live,
https://api.myrt.money/v1, keys start withmyrt_live_: issues on Ethereum mainnet.
A key works only in the environment it was issued for. A sandbox key on the live host, or the reverse, returns 401 UNAUTHORIZED. The two environments settle on different chains, so never hardcode a chain id or a token address. Read them from /v1/config and /v1/tokens on the host you are calling. Base URLs and the setup sequence are on getting started. Chain details are on network configuration.
