Endpoint
Create a payment instruction
/v1/orders/{id}/instructionsFixes a quote for one asset and returns the address to pay. An order has at most one live instruction: creating another while one is waiting fails with instruction_live unless the live one is on the same asset, in which case that one is returned. A payer may get a fresh instruction each time the 15-minute window lapses, up to 20 per order (instruction_limit_reached), because each one on the relay rail allocates a real record at the provider. If the create call loses its reply the answer is 504 outcome_unknown: show the payer nothing and retry with the same idempotency key; a second address is never allocated while the first is unknown. If the rail is not available the call fails closed with rail_unavailable and the reason; it never substitutes another rail. On the orrerie_app rail no address is returned: the app pays from the payer's own wallet.
- Scope a restricted key needs
orders:write- Publishable key
- Not allowed: secret or restricted key only
- Idempotency-Key
- Recommended. It makes a retry safe.
Parameters
| Parameter | Type and place | Meaning |
|---|---|---|
idrequired | stringpath | The id of the order. |
Orrerie-Version | stringheader | API version date. Default: the version the key was created with. Example 2026-10-11. |
Idempotency-Key | stringheader | Makes a retry safe. Kept 24 hours. 1 to 255 characters. |
Request body
A JSON object. The body is required. A field the call does not list is refused with parameter_unknown.
| Field | Type | Meaning |
|---|---|---|
assetrequired | string | The asset the payer will send, for example ethereum:usdc. |
rail | string | Default: relay where the asset is covered, else direct. Naming a rail the asset does not have fails with rail_not_allowed. One of relay, direct. |
payer_refund_address | string | Recorded for the merchant to use if it later refunds. It is never given to the provider: a provider refund always goes to the merchant's own wallet. |
Example request
curl
curl https://api.orrerie.com/v1/orders/ord_0zymngngvf12b8b2cra7tb8e/instructions \
-H "Authorization: Bearer $ORRERIE_SECRET_KEY" \
-H "Idempotency-Key: 8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a" \
-H "Content-Type: application/json" \
-d '{
"asset": "ethereum:usdc"
}'JavaScript
const response = await fetch("https://api.orrerie.com/v1/orders/ord_0zymngngvf12b8b2cra7tb8e/instructions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ORRERIE_SECRET_KEY}`,
"Idempotency-Key": "8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a",
"Content-Type": "application/json",
},
body: JSON.stringify({
"asset": "ethereum:usdc"
}),
});
const result = await response.json();
if (!response.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result);Python
import os
import requests
response = requests.post(
"https://api.orrerie.com/v1/orders/ord_0zymngngvf12b8b2cra7tb8e/instructions",
headers={"Authorization": f"Bearer {os.environ['ORRERIE_SECRET_KEY']}", "Idempotency-Key": "8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a"},
json={
"asset": "ethereum:usdc",
},
timeout=30,
)
result = response.json()
if not response.ok:
raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}")
print(result)PHP
<?php
$request = curl_init('https://api.orrerie.com/v1/orders/ord_0zymngngvf12b8b2cra7tb8e/instructions');
curl_setopt_array($request, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('ORRERIE_SECRET_KEY'),
'Idempotency-Key: 8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'asset' => 'ethereum:usdc',
]),
]);
$body = curl_exec($request);
if ($body === false) {
throw new RuntimeException(curl_error($request));
}
$result = json_decode($body, true);
if (curl_getinfo($request, CURLINFO_HTTP_CODE) >= 400) {
throw new RuntimeException($result['error']['code'] . ': ' . $result['error']['message']);
}
print_r($result);Responses
201 Created
The answer is the payment_instruction object. Headers: Orrerie-Request-Id.
Example response, 201
{
"id": "pin_h73hkjr8dyp4zymaet5e2gbv",
"object": "payment_instruction",
"livemode": true,
"created": "2026-10-11T14:04:10.902Z",
"order": "ord_0zymngngvf12b8b2cra7tb8e",
"status": "waiting",
"rail": "relay",
"rail_reason": "default",
"provider": "relay",
"provider_request_id": "0x6b1f3c0e9a2d47f58c1b0e7d2a9f4c3b5e8d1a0f7c6b9e2d4a3f1c0b8e7d6a5f",
"recipient_kind": "wallet",
"recipient": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"refund_to": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"refund_wallet_protected": true,
"pay_asset": "ethereum:usdc",
"pay_amount": "22.410000",
"amount_mode": "exact",
"address": {
"id": "addr_x76q8chyjk6j34he4jrcbb7v",
"address": "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"network": "ethereum",
"owner": "provider",
"solana_pay_reference": null,
"derivation": null
},
"payment_uri": "ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48@1/transfer?address=0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359&uint256=22410000",
"quote": {
"id": "qt_0fymhdf3w7nem8m9f6rfjtgw",
"price_amount": "21.99",
"price_currency": "USD",
"rate": "1.000200",
"route_fee": {
"amount": "0.42",
"currency": "USD",
"bearer": "payer"
},
"created": "2026-10-11T14:04:10.902Z",
"expires_at": "2026-10-11T14:19:10.902Z"
},
"settle_asset": "base:usdc",
"settle_amount_expected": "21.990000",
"confirmations_required": 12,
"in_transit_notice": "While a payment is being routed it is held by Relay, a separate company. Orrerie never holds it.",
"wrong_asset_warning": "Send only USDC on Ethereum to this address. Anything else may be lost.",
"expires_at": "2026-10-11T14:19:10.902Z",
"late_until": "2026-11-10T14:19:10.902Z",
"failure_code": null,
"test_simulation_url": null
}Any other status: an error
A status of 400 or above answers with the error object. Branch on its code, never on its message.
Errors
Beside the errors every call can answer with (a bad key, a missing scope, the rate limit), this call answers with:
| Code | HTTP | Meaning |
|---|---|---|
order_not_open | 409 | The order is not open (or underpaid, for a top-up instruction). |
instruction_live | 409 | The order already has a live instruction on another asset; error.instruction is its id. |
instruction_limit_reached | 409 | The order has had 20 payment instructions, the most allowed. Create a new order. |
asset_unsupported | 400 | Not an asset id from GET /v1/assets, or not usable for this purpose. |
asset_not_enabled | 400 | The asset exists but is not switched on for this merchant. |
rail_unavailable | 503 | The rail cannot be used now. error.reason is the exact refusal: relay_unavailable, relay_no_route, relay_amount_too_low, relay_low_liquidity, relay_paused, route_fee_over_cap, provider_route_mismatch, provider_, refund_, recipient_not_attested, or asset_not_covered; error.available_assets lists what can be used instead. Nothing was created at the provider and nothing was switched. |
rail_not_allowed | 400 | The rail is off for this order, merchant or asset (for example relay for Litecoin). |
price_unavailable | 503 | No fresh price for the asset or currency. A stale price is never used for a quote. |
amount_below_minimum | 400 | Below the minimum: US$1.00 for an order payable on the relay rail, the network dust limit on the direct rail or for a payout. |
outcome_unknown | 504 | The request may or may not have taken effect (the engine's instruction_unknown for a payment instruction). Retry with the same idempotency key; never with a new one. For an instruction: show the payer nothing until it resolves. |