Ask for an invitation
Menu

Endpoint

Create a payment instruction

POST/v1/orders/{id}/instructions

Fixes 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

Parameters of Create a payment instruction
ParameterType and placeMeaning
idrequiredstringpathThe id of the order.
Orrerie-VersionstringheaderAPI version date. Default: the version the key was created with. Example 2026-10-11.
Idempotency-KeystringheaderMakes 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.

Request body of Create a payment instruction
FieldTypeMeaning
assetrequiredstringThe asset the payer will send, for example ethereum:usdc.
railstringDefault: 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_addressstringRecorded 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:

Errors of Create a payment instruction
CodeHTTPMeaning
order_not_open409The order is not open (or underpaid, for a top-up instruction).
instruction_live409The order already has a live instruction on another asset; error.instruction is its id.
instruction_limit_reached409The order has had 20 payment instructions, the most allowed. Create a new order.
asset_unsupported400Not an asset id from GET /v1/assets, or not usable for this purpose.
asset_not_enabled400The asset exists but is not switched on for this merchant.
rail_unavailable503The 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_deposit_mismatch, refund_wallet_unavailable, 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_allowed400The rail is off for this order, merchant or asset (for example relay for Litecoin).
price_unavailable503No fresh price for the asset or currency. A stale price is never used for a quote.
amount_below_minimum400Below 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_unknown504The 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.