Developers
Take money for what your server does, with one key
Your server asks Orla to price a call, charge a prepaid balance or mirror an invoice, and hears back, signed, when it is paid. The payer can be a person or a machine answering a 402.
Base URL: https://app.orla.finance/api · Paying rather than selling? The agent API is the other door.
The key
A key that acts as its issuer
A key does what its issuer may do in one book, and stops on the next call once that person is removed or made a viewer. Orla keeps only the hash.
curl https://app.orla.finance/api/integration/ping \
-H "Authorization: Bearer oit_..."Shape
- oit_ and 43 characters
- Sent as Authorization: Bearer on every call to /integration. Nothing else in the API accepts it, so a leaked key reaches exactly these endpoints, inside one book, within its scopes.
- Shown once
- At issue, with the webhook secret beside it whether or not an address was given. Orla keeps the key as a hash and the secret encrypted, for signing. A lost key is revoked and reissued; there is no second look.
- Up to ten a book
- Enough for a billing server, a bridge and a staging copy. Revoke from the same screen; the key stops on the next call.
Scopes
A scope names a family of routes, not a permission: inside the book the issuing person's own rights still apply on top.
- payments
- Mint a payment request, read whether it was paid, charge a client's prepaid balance. The narrowest scope and the one a seller comes here for.
- invoices
- Create an invoice, set its seller and client, send it, record a payment, void it, remind. The bridge for a system of record that keeps its own invoices.
- contacts
- Create a contact.
- accounts
- Create and list the accounts money lands on.
- transactions
- Files rows from a system Orla has no connector for into one push source, and touches nothing else. It ends by itself after 30, 90 or 180 days or a year.
- chats
- Mint the code that connects a client's Telegram group to the bot, and read whether it is connected. What the client then says arrives as webhook events, not by a read.
Limits
- 240 calls a minute per key
- Generous for a sync job, far below anything that hurts. Past it, 429 and a retry after a moment.
- Idempotency-Key
- A header on every write that costs somebody money: a request minted twice is two requests, a charge retried without the key is two charges. Reuse the key on a retry and the answer is the first one.
- A rollout
- On for listed books while it is new, and the payment routes have a switch of their own. Off, they answer 404; nothing minted before stops being payable.
Price a call
You name a price, the payer gets a page
One call mints a payment request and returns a URL. Hand it to whoever should pay, a person or a machine, then read it back or wait for the webhook.
curl -X POST https://app.orla.finance/api/integration/payment-requests \
-H "Authorization: Bearer oit_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"account_id": "3a7b76ee-7acd-414e-9066-af408c2b74b7",
"amount": "2.50",
"resource": "https://api.example.com/v1/summarize",
"memo": "summarize, 1 call"
}'
{ "id": "…", "token": "…", "url": "https://app.orla.finance/pay/…",
"amount": "2.50000000", "currency": "USD", "status": "active",
"memo": "summarize, 1 call", "created_at": "2026-09-18T13:14:20+00:00",
"expires_at": "2026-09-19T13:14:20+00:00", "amount_received": null,
"resource_id": "…" }What comes back
- url
- The whole point of the answer. Put it in your 402 for a machine, or in a link for a person: the same page serves both.
- status and amount_received
- active, paid or cancelled (a request that lapsed is cancelled, there is no fourth word), and how much has landed on a request paid in parts. Read it back with GET /integration/payment-requests/{token}, scoped to your own book.
- expires_at
- When the request lapses: the deadline of the account's watch, or a day from minting on an account nothing tracks. Never null, so your server always knows how long to wait.
- resource
- The endpoint being charged for. It becomes a card on your Endpoints screen with a call count, and a whole day of its payments lands in the book as one income row.
Who pays and how
- A person
- Opens the URL and pays the way the page offers: a card, a bank transfer, a stablecoin to an address the book controls.
- A machine
- Gets a 402 with an x402 quote in USDC on Base, Ethereum or Polygon, signs an EIP-3009 authorization, and Orla settles it on chain and pays the gas. The floor is one dollar.
- One row a day
- An endpoint's payments of a day are one income row that grows, with every payment and its payer under it. A chain reorganisation shrinks the row; the invoice it closed stays paid.
Charge a call
Under a dollar, charge a prepaid balance
Orla relays nothing under a dollar, because it pays the gas. So a client tops up once above the floor, and every call after that is a line in a ledger.
curl -X POST https://app.orla.finance/api/integration/charges \
-H "Authorization: Bearer oit_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "client_ref": "acme-42", "account_id": "3a7b…", "amount": "0.002",
"resource": "https://api.example.com/v1/summarize" }'
{ "paid": true, "client_ref": "acme-42", "balance_usd": "4.99800000",
"charged_usd": "0.002", "entry_id": "…", "top_up": null, "balance_url": null }
{ "paid": false, "client_ref": "acme-42", "balance_usd": "0.00000000",
"charged_usd": null, "entry_id": null,
"top_up": { "url": "https://app.orla.finance/pay/…", "amount": "1.00000000", … },
"balance_url": "https://app.orla.finance/credit/…" }Two answers and no third
- paid
- The balance carried the call. charged_usd is what this call cost, spelled as you sent it; balance_usd is what is left, at the ledger's eight decimals. Serve the call.
- refused, with a way on
- The balance could not carry it. top_up is a payment request that fills it: hand its url to the client, and retry once paid. top_up_amount sets the sum.
- Never an overdraft
- A negative balance is a loan, and a prepaid balance does not lend. The money is yours from the day a top-up lands; what is left is service you owe.
The client's side
- balance_url
- A page for the client: what is left, what was paid in, every call and top-up. Put it beside the payment link in your answer; it needs no account and names you by your public payment handle only.
- client_ref
- Your own identifier for the client. Orla does not parse it; it keys the balance, and the client appears under Client credits with it until you name the contact.
Hear about it
payment_request.paid, signed, to your address
Give the key a webhook address and your server hears when a request or a mirrored invoice is paid, or a client acts in their chat. Signed like every Orla webhook.
POST /your/address HTTP/1.1
Content-Type: application/json
X-Orla-Event: payment_request.paid
X-Orla-Signature: sha256=5c8456449f0e...
{"event": "payment_request.paid", "space_id": "…",
"at": "2026-09-18T13:14:21.417208+00:00",
"data": {"payment_request_id": "…", "token": "…", "resource_id": "…",
"amount": "2.5", "asset": "USDC", "network": "base",
"tx_hash": "0x…", "payer": "0x…",
"settled_at": "2026-09-18T13:14:20+00:00",
"memo": "summarize, 1 call"}}The body
- event, space_id, at
- The event name, the same string as the X-Orla-Event header; the book it happened in; when it was sent, ISO 8601 with an offset. There is no version field on these events.
- data
- The fields of the event, listed below. Amounts are decimal strings without trailing zeros; identifiers are UUIDs.
Events
- payment_request.paid
- A request your server minted settled on the chain. data: payment_request_id, token, resource_id, amount, asset, network, tx_hash, payer, settled_at, memo.
- invoice.paid
- An invoice your bridge mirrored is settled in full, by any road. data: invoice_id, number (your document number), currency, total, total_paid, via (manual, settlement, card or write_off).
- invoice.claimed_paid
- The client pressed "I paid" in their chat. data: invoice_id, contact_id, client (their name), number, total, currency. Nothing is booked until a person on your side confirms.
- invoice.deferral_requested
- The client asked for more time. data: invoice_id, contact_id, days, due (the date proposed), client, number, total, currency.
- client.question
- The client wrote to the bot. data: contact_id, client, text (up to the chat's limit, never run through a model).
- client.file
- The client dropped a file, filed under their contact. data: contact_id, client, name (the filename).
Delivery
- Three attempts within a minute
- At once, after five seconds, after thirty. A machine payment is a live exchange, so the schedule is short; a server that was down for longer reads the request back with GET.
- Any 2xx within ten seconds
- Counts as delivered. Do the work after you answer, not before.
- The address
- Public https, resolved again at every send. A private range, a loopback or a metadata address is refused whatever the hostname says that day.
The events of the whole book (money in and out, payouts, approvals) are a separate subscription, described on the webhooks page. Webhooks.
Every endpoint
The whole surface, one line each
All under /integration, with the bearer, one scope each. JSON bodies, UUID identifiers, amounts as decimal strings. For a machine: orla.finance/openapi.json.
| Endpoint | Scope | What it does |
|---|---|---|
GET / | any | Which book and which scopes this key has |
POST / | payments | Price one call; comes back with the page's URL |
GET / | payments | Was it paid, and how much of it |
POST / | payments | Charge one call to a client's prepaid balance, or get the request that tops it up |
POST / | invoices | Mirror an invoice into the book |
POST / | invoices | Issuer requisites for one invoice, when the bridge issues for more than one entity |
POST / | invoices | The client of one invoice |
GET / | invoices | The invoice as the book has it |
POST / | invoices | Send it to the client |
POST / | invoices | Record a payment against it |
POST / | invoices | Void it |
POST / | invoices | Remind the client |
POST / | contacts | Create a contact |
POST / | chats | The /linkclient code that connects the client's Telegram group to the bot; Pro |
GET / | chats | Whether that Telegram group is connected, and its title |
POST / | transactions | File up to 500 rows into the push source this key is bound to; one call is one batch |
POST / | accounts | Create an account |
GET / | accounts | List the accounts |
Around the key
What the rest of Orla adds to a paid endpoint
On your side
- Endpoints
- Every resource you charged for is a card under Client credits: address, call count, last payment, and who paid each day.
- The catalogue
- Tick Findable on a card and it appears in Orla's public list of paid services, the x402 discovery list agents read. Address and name only, never volumes.
- Your own checkout
- Prove you control a site and a payment request may be shown inside your page, in a frame. Everything else Orla serves refuses to be framed.
- The facilitator
- Already running an x402 middleware? Point its facilitator URL at Orla: it checks the payer's signature, puts the transfer on the chain, pays the gas, and files the money under the endpoint.
Rows in, from any other system
The direction is reversed: Orla holds no key of yours and calls nobody. Your automation, script or scheduled job sends the rows, and Orla files them.
- POST /integration/transactions
- rows, each with external_id, account_key, currency, occurred_on and amount; payee and note if you have them. The key names the source, the body never does. The answer counts booked, duplicates, skipped_closed and rejected, and carries a batch_id.
- Sent twice, filed once
- A row is known by your own external_id, so a retry or an overlapping run adds nothing.
- One call is one batch
- A wrong run is undone whole from the source's list. Rows dated inside a closed period are skipped and counted, never filed quietly.
- Filed as imported
- Pushed rows reach automations and event subscriptions with origin import, never as money arriving on a rail, so a leaked key cannot ship goods on a payment nobody made.
- A key with an end
- A week before it runs out the person who issued it is reminded. New key issues a fresh one, and the old one works until it is revoked or runs out, so the automation does not stop during the swap.
- What it counts against
- The rows it files come out of the space's synced rows for the month, the same allowance bank and exchange sync use.
On the payer's side
- Agents
- An agent with an Orla wallet pays your 402 by itself, inside the ceilings its owner set: which hosts, how much a request, how much a day. The agent API is its own page.
- From a shell
- orla fetch <url> in the Orla CLI does the same with an agent key from the environment: the resource on stdout, the receipt on stderr.
Errors
A code you can branch on, not prose
Every refusal is one JSON body: error, a stable code; detail, a sentence for a person; details, structured fields when there are any.
What you will meet
- 401
- The key is unknown, revoked, or its person was demoted to a viewer. One message for all three on purpose: a leaked key must not be able to map the system.
- 403
- The key lacks the scope for this route, or its person is no longer a member of the book (Not a member of this space).
- 404
- The rollout is off for this book, or the request or resource is not yours.
- 422
- The body: a missing account, an amount the account cannot take, a webhook address that is not public https.
- 429
- Over the minute's calls. Wait and retry with the same Idempotency-Key.
See it on your own books
Thirty minutes: we connect an account, drop a real bill in, and close a month together.