Skip to navigation

Conventions

IDs, money, time, lists, versions and keys.

The same rules apply to every endpoint.

Requests and responses

  • JSON bodies, snake_case fields, HTTPS only.
  • One API host for both environments, https://api.runledger.co. The key decides whether a request runs in the sandbox or live, and every object carries livemode. The dashboard is at https://app.runledger.co, the developer MCP server at https://mcp.runledger.co/mcp, and the hosted customer view at https://billing.runledger.co/s/{session}.
  • Updates use PATCH and change only the fields you send. Send null to clear a field. The one exception is PUT /v1/config, which takes a whole configuration document.
  • Config writes accept Runledger-Dry-Run: true to show what they would do without doing it. See Configuration. Every check() response carries an affordability block, with no header needed. See Checks and holds.
  • Fields with no value are left out of responses, never sent as null, except the ones the reference documents as nullable, such as the figures in affordability and margin on a run.
  • State changes are explicit actions on the object, such as POST /v1/holds/{id}/release or POST /v1/outcome_charges/{id}/reverse. Status fields can’t be written directly.

IDs

Every object ID is a string with a type prefix, so you can tell what an ID refers to by looking at it. Treat the part after the prefix as opaque; its length may change.

PrefixObjectPrefixObject
cus_Customerplan_Plan
ct_Credit typesub_Subscription
cg_Credit grantinv_Invoice
le_Ledger entryil_Invoice line
hold_Holdcs_Checkout session
ev_Eventcost_Cost
od_Outcome definitionexp_Export
oc_Outcome chargerec_Reconciliation
rev_Reversalwe_Webhook endpoint
dsp_Disputewhe_Webhook event
clock_Test clockreq_Request
acct_Accountkey_API key
pp_Payment providerpay_Payment
rr_Routing rulenr_Notification rule
cprop_Config proposalae_Audit event
ic_Integration checkcm_Cost model
rc_Rate cardpr_Price rule
mg_Margin guardrailsim_Simulation
catm_Catalog modelcatp_Catalog price version
sl_Spend limitatu_Auto top-up
csess_Customer session

Pools and allowances are named by a key you choose inside a plan, such as included or weekly, and have no ID of their own. member_id, team_id and agent_id are your own IDs, up to 128 characters, that you send on calls to attribute usage. See Members, teams and agents.

Customers also take your own ID as external_id. Calls made on a customer’s behalf, such as check(), track() and checkout(), accept either customer_id or external_customer_id. URL paths and list filters take Runledger’s cus_ ID, which you can look up from yours with GET /v1/customers?external_id=. Credit types, plans and outcome definitions have a key you choose, such as chat_resolution, and requests accept the key or the ID.

Money, credits and quantities

  • Money is an integer in the currency’s minor unit with a lowercase ISO 4217 code: {"amount": 1500, "currency": "usd"} is US$15.00. Zero-decimal currencies follow ISO 4217, so {"amount": 1500, "currency": "jpy"} is ¥1,500.
  • Unit prices in money can be fractions of a minor unit, so they are decimal strings in minor units: unit_amount_decimal: "95" is US$0.95, and "0.042" is 0.042 US cents.
  • Credits and quantities are decimal strings, such as "12.5", with up to the credit type’s decimal_places.
  • Rounding happens once, on each invoice line, half up to the minor unit. Ledger amounts are never rounded.

Time

  • Timestamps are RFC 3339 strings in UTC: 2026-11-02T09:00:00Z.
  • Durations are ISO 8601: PT15M, PT24H, P30D.
  • Ledger entries carry both effective_at (when it happened in business terms) and posted_at (when Runledger recorded it).

Lists

List endpoints return the newest objects first:

{ "object": "list", "data": [ ... ], "has_more": true, "next_cursor": "c_9f2..." }

Pass next_cursor back as cursor for the next page. limit is 1 to 100, default 20. Cursors are opaque and don’t grant access to anything. The SDKs page automatically:

for await (const charge of await runledger.outcomeCharges.list({ status: "disputed" })) {
console.log(charge.id);
}

Lists don’t return a total count unless you ask. Add include=total_count to the main lists (events, outcome charges, customers, disputes, reversals, request logs and webhook events) and the response carries total_count, capped, with total_count_approximate: true when the real number is higher. For counts and amounts over time, use GET /v1/reports/outcomes instead of adding up pages. Reads of a single object always reflect your writes; lists may lag by a few seconds, so don’t use a list to confirm a write you just made.

Routing rules are the one list that isn’t newest first: they come in order, first match first.

Metadata

Most objects take metadata: up to 50 keys of your own, keys up to 40 characters and values up to 500. Don’t put personal data or secrets in it.

Versioning

Send Runledger-Version: 2026-10-18 on every request; the SDKs do this for you. Without it, a request uses the version your account is pinned to, which you can read from GET /v1/account.

Within a version, changes are additive only: new endpoints, new optional fields, new enum values, new error and event types. Handle values you don’t recognize, and ignore fields you don’t use. A change that removes or renames anything, adds a required field, or changes a type ships as a new dated version, and old versions get advance notice through Deprecation and Sunset headers.

Rate limits

A request over the limit gets 429 with a Retry-After header in seconds. The SDKs wait and retry. Limits are counted per account, per environment and per endpoint, in one-second windows. check(), track(), the event batch and trace() have a higher limit than the rest, and live’s limits are above the sandbox’s: 100 requests a second for most endpoints and 1,000 for those four in live, 25 and 100 in the sandbox. A person’s calls from the dashboard and the customer view’s are not counted.

Keys

PrefixKey
rl_sk_test_, rl_sk_live_Secret key: full access to its environment
rl_rk_test_, rl_rk_live_Restricted key: only the scopes you grant. It can carry an expiry and an agent flag

Keys end in a checksum so secret scanners can recognize a leaked key, and a leaked key reported to us is revoked. People create, roll and revoke keys in the dashboard; when you roll a key, the old one keeps working for up to seven days. GET /v1/api_keys lists them, masked, with their scopes, so an agent can see what it holds. Agents never create or roll keys. Never use a key in a browser or mobile app.

A restricted key’s scopes are checked on every call. One it lacks returns 403 with insufficient_scope and required_scope.

Actors

Every change records who made it, as an actor with a type of user, agent, key, rule or system. Runledger fills it in from the credential. See Access, actors and audit.

Request IDs

Every response has a Request-Id header, and every error body has a request_id. Look a request up with GET /v1/request_logs/{request_id}, or quote it to support.

SDKs and tools

Runledger has SDKs for TypeScript and Python, generated from the OpenAPI spec. Both set the Runledger-Version header, wait and retry on 429, and expose check(), track(), trace() and checkout(). trace() records the model, provider and units you pass it; there are no wrappers that record costs from a model client automatically. Webhook signatures are verified with any Standard Webhooks library. There is no CLI. Keys are created, rolled and revoked in the dashboard, not through the API.

See also