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

URL: https://orla.finance/en/developers
Markdown twin of that page. Append `.md` to any Orla page URL to get one.

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.

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

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

#### 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.

#### 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.

#### 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.

#### 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.

#### 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.

- GET /integration/ping (any): Which book and which scopes this key has
- POST /integration/payment-requests (payments): Price one call; comes back with the page's URL
- GET /integration/payment-requests/{token} (payments): Was it paid, and how much of it
- POST /integration/charges (payments): Charge one call to a client's prepaid balance, or get the request that tops it up
- POST /integration/invoices (invoices): Mirror an invoice into the book
- POST /integration/invoices/{id}/seller (invoices): Issuer requisites for one invoice, when the bridge issues for more than one entity
- POST /integration/invoices/{id}/client (invoices): The client of one invoice
- GET /integration/invoices/{id} (invoices): The invoice as the book has it
- POST /integration/invoices/{id}/send (invoices): Send it to the client
- POST /integration/invoices/{id}/payments (invoices): Record a payment against it
- POST /integration/invoices/{id}/void (invoices): Void it
- POST /integration/invoices/{id}/remind (invoices): Remind the client
- POST /integration/contacts (contacts): Create a contact
- POST /integration/contacts/{id}/telegram-link (chats): The /linkclient code that connects the client's Telegram group to the bot; Pro
- GET /integration/contacts/{id}/chats (chats): Whether that Telegram group is connected, and its title
- POST /integration/accounts (accounts): Create an account
- GET /integration/accounts (accounts): List the accounts

#### 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.

#### 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.
