Skip to content

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.

  1. RequestPOST /integration/payment-requests

    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"
      }'
  2. Responsestatus: active

    { "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": "…" }
  3. Webhookpayment_request.paid

    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"}}

An owner or an admin issues the key in the app, under Client credits, then Keys.

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.

  • paymentsMint 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.
  • invoicesCreate 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.
  • contactsCreate a contact.
  • accountsCreate and list the accounts money lands on.
  • transactionsFiles 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.
  • chatsMint 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 keyGenerous for a sync job, far below anything that hurts. Past it, 429 and a retry after a moment.
  • Idempotency-KeyA 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 key takes money inMoney goes out only inside a signed mandate, never on a key. No scope a key can carry sends a payment.
  • A rolloutOn 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.

What comes back

  • urlThe 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_receivedactive, 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_atWhen 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.
  • resourceThe 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.

  1. RequestPOST /integration/charges

    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" }'
  2. Paidpaid: true

    { "paid": true, "client_ref": "acme-42", "balance_usd": "4.99800000",
      "charged_usd": "0.002", "entry_id": "…", "top_up": null, "balance_url": null }
  3. Refusedpaid: false

    { "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

  • paidThe 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 onThe 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 overdraftA 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_urlA 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_refYour 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.

Events

  • payment_request.paidA 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.paidAn 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_paidThe 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_requestedThe client asked for more time. data: invoice_id, contact_id, days, due (the date proposed), client, number, total, currency.
  • client.questionThe client wrote to the bot. data: contact_id, client, text (up to the chat's limit, never run through a model).
  • client.fileThe 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 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.

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. With no bank or imported row linked (tx_id) and past the book's line, it answers 409 invoice.reduction_awaits_second_person and waits for the owner or an admin
POST /integration/invoices/{id}/voidinvoicesVoid it; a sent invoice past the book's line answers 409 and waits the same way
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; Solo and up
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

  • EndpointsEvery resource you charged for is a card under Client credits: address, call count, last payment, and who paid each day.
  • The catalogueTick 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 checkoutProve 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 facilitatorAlready 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.
  • A sandbox to build againstThe books of a made-up studio, three months deep. Keys, MCP and your webhooks work as anywhere else, no money moves, and Reset brings the same figures back so a test passes again.

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.
Balances too
Send balances beside the rows, or alone, each with account_key, currency, balance and as_of. The account then shows the balance you stated instead of the sum of the rows you sent, and an older reading never replaces a newer one.
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

  • AgentsAn 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 shellorla fetch <url> in the Orla CLI does the same with an agent key from the environment: the resource on stdout, the receipt on stderr.
  • In the payer's browserWhere a request folds the transfer away or sits in your checkout, the page hands the payer's agent a few WebMCP tools. They return only what the page shows, your memo marked as yours.

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

  • 401The 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.
  • 403The key lacks the scope for this route, or its person is no longer a member of the book (Not a member of this space).
  • 404The rollout is off for this book, or the request or resource is not yours.
  • 422The body: a missing account, an amount the account cannot take, a webhook address that is not public https.
  • 429Over the minute's calls. Wait and retry with the same Idempotency-Key.

Write your first mandate
on a thirty minute call

On your own books: one position, one rule, and the first bill waiting for your button.