Skip to content

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.

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/transactionstransactionsFile up to 500 rows into the push source this key is bound to; one call is one batch
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 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.

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.