Skip to content

Developers

Take money for what your server does, with one key.

An integration key lets your own server ask Orla to price a call, charge a client's prepaid balance, mirror an invoice into a book and hear back, signed, when any of it is paid. The payer can be a person on a page or a machine answering a 402; your server does not care which.

Base URL: https://app.orla.finance/api

In the app: Client credits, then Keys. An owner or an admin issues the key, picks what it may do, and copies it once.

Paying rather than selling? The agent API is the other door. The events of the whole book are on webhooks.

The key

A bearer token that acts as the person who issued it, in one book.

There is no separate machine identity to manage. A key is minted by a person, does what that person may do in that one book, and dies with them: remove the person from the book, or demote them to a viewer, and every key they issued stops on the next call. Orla keeps the hash; the plaintext is shown once.

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.
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
The key door is on for listed books while it is new, and the payment routes have a switch of their own. Off, they answer 404 and nothing minted before stops being payable.

Price a call

Your server names a price. The payer gets a page that answers a person and a machine alike.

One call mints a payment request: an amount on one of your accounts, a memo, and the endpoint it is for. What comes back is a URL. Your server hands that URL to whoever should pay and reads the request back, or waits 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
Fetches the URL with Accept: application/json and gets a 402 with an x402 quote: USDC on Base, Ethereum or Polygon, exact amount, a pay-to address the book has proven it controls. It signs an EIP-3009 authorization, sends it back, and Orla puts the transfer on the chain and pays the gas. The rail has a floor of one dollar; under it, charge a prepaid balance instead.
One row a day
A metered endpoint sells thousands of calls; the book does not want thousands of legs. All of an endpoint's payments of a day are one income row that grows, and the detail under it lists every payment with who paid it.

Charge a call

Under a dollar, charge a prepaid balance instead of the chain.

A transfer Orla relays cannot be smaller than a dollar, because Orla pays its gas. A metered endpoint sells calls for a tenth of a cent. The bridge is a prepaid balance per client: the client pays 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 an ordinary payment request that fills the balance: hand its url to the client as your 402 or your link, and retry the call once it is paid. top_up_amount in your request sets the sum; left out, it is the price of this call or the rail's floor of one dollar, whichever is larger.
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 the address on the key.

Give the key a webhook address when you issue it and your server hears the moment a request it minted, or an invoice it mirrored, is paid, and whenever a client acts in their chat with the bot. Same signature as every Orla webhook: sha256= and the HMAC-SHA256 of the raw body under the secret shown once beside the key.

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, including the top-up a refused charge asked for, settled on the chain. data: payment_request_id, token, resource_id (null when nothing was filed), amount, asset, network, tx_hash, payer, settled_at, memo. A request a person paid on its page is not pushed; read it back.
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, all with the bearer, each behind one scope. Bodies are JSON; identifiers are UUIDs; amounts are decimal strings.

EndpointScopeWhat it does
GET /integration/pinganyWhich book and which scopes this key has
POST /integration/payment-requestspaymentsPrice one call; comes back with the page's URL
GET /integration/payment-requests/{token}paymentsWas it paid, and how much of it
POST /integration/chargespaymentsCharge one call to a client's prepaid balance, or get the request that tops it up
POST /integration/invoicesinvoicesMirror an invoice into the book
POST /integration/invoices/{id}/sellerinvoicesIssuer requisites for one invoice, when the bridge issues for more than one entity
POST /integration/invoices/{id}/clientinvoicesThe client of one invoice
GET /integration/invoices/{id}invoicesThe invoice as the book has it
POST /integration/invoices/{id}/sendinvoicesSend it to the client
POST /integration/invoices/{id}/paymentsinvoicesRecord a payment against it
POST /integration/invoices/{id}/voidinvoicesVoid it
POST /integration/invoices/{id}/remindinvoicesRemind the client
POST /integration/contactscontactsCreate a contact
POST /integration/contacts/{id}/telegram-linkchatsThe /linkclient code that connects the client's Telegram group to the bot; Pro
GET /integration/contacts/{id}/chatschatsWhether that Telegram group is connected, and its title
POST /integration/accountsaccountsCreate an account
GET /integration/accountsaccountsList 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 the payments of any day with who paid. A payer of an endpoint becomes a contact on your press; a pay link or an invoice paid by a machine names its payer by itself.
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.

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.

The agent API · The catalogue · The CLI

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.