> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runledger.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runledger.co/_mcp/server.

# Access, actors and audit

A coding agent that configures billing needs enough access to do the job and not enough to cause damage. Runledger gives it scoped credentials, records every change with who made it, and keeps a log a person can read.

## Keys and scopes

A **secret key** (`rl_sk_...`) has full access to its environment. A **restricted key** (`rl_rk_...`) carries a list of `scopes`, an optional `expires_at`, and an `agent` flag that marks a key issued to an agent. OAuth sessions from the [MCP server](/agents/mcp) carry the same scopes.

| Scope                                     | Allows                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config:read`                             | Read credit types, outcome definitions, plans, endpoints, providers, routing and notification rules, and the presets. Also the provider price catalog, cost models, rate cards, price rules and margin guardrails, and running a simulation                                                                                        |
| `config:write`                            | Apply configuration in the sandbox. Never given to an agent in live mode                                                                                                                                                                                                                                                           |
| `config:propose`                          | Propose live configuration for a person to approve                                                                                                                                                                                                                                                                                 |
| `events:read`, `events:write`             | Read events, preview and send them                                                                                                                                                                                                                                                                                                 |
| `customers:read`, `customers:write`       | Read customers. Write is for creating test customers in the sandbox                                                                                                                                                                                                                                                                |
| `webhooks:write`                          | Create and update endpoints, and send test events in the sandbox                                                                                                                                                                                                                                                                   |
| `providers:read`, `providers:connect`     | Read provider connections. Start one: test mode connects directly in the sandbox, and connecting in live isn't open yet. Also remove a sandbox one. Only an Admin removes a live provider, and an agent key is refused. When live connections open, an Admin confirms the first live provider in the dashboard, and an agent can't |
| `test_helpers:write`                      | Test clocks, scenarios, simulated payments and the sandbox reset, in the sandbox                                                                                                                                                                                                                                                   |
| `audit:read`                              | Read the audit log, the request logs and the webhook event log                                                                                                                                                                                                                                                                     |
| `charges:read`, `disputes:read`           | Read charges and their reversals, and read disputes                                                                                                                                                                                                                                                                                |
| `usage:read`                              | Read a customer's [usage summary](/guides/usage-summary)                                                                                                                                                                                                                                                                           |
| `spend_limits:read`, `spend_limits:write` | Read spend limits. Write sets a limit for one customer, the way your backend or your customer would. Operator limits are configuration and need `config:write` or a proposal                                                                                                                                                       |
| `auto_top_up:write`                       | Set, change or remove a customer's [auto top-up](/guides/auto-top-up-and-alerts)                                                                                                                                                                                                                                                   |
| `customer_sessions:write`                 | Open a [customer view](/guides/customer-view) for a customer. Granted one key at a time                                                                                                                                                                                                                                            |
| `charges:write`, `disputes:resolve`       | Confirm or reverse a charge, and open or decide a dispute. Never in a preset: an Admin grants them one at a time                                                                                                                                                                                                                   |

An agent key that holds `spend_limits:write` or `auto_top_up:write` can create a customer's limit or lower it in live mode, and is refused with `permission_denied` when it tries to raise or remove one. Agent keys can't create customer sessions. These are the only live data writes an agent key can hold, and neither is in a preset.

A call outside a key's scopes returns `403` with `code: insufficient_scope` and `required_scope` naming what it needed. An MCP client can answer that by asking the person to widen the grant, which is the step-up flow the MCP authorization spec describes.

`GET /v1/api_keys` lists keys, masked: name, type, prefix, scopes, `agent`, expiry and last use, never the secret. It lets an agent see what it holds. **Agents never create, roll or revoke keys, and never receive live provider credentials.** People do that in the dashboard: an Admin for live keys, and an Admin or a Collaborator for sandbox keys.

## Who did it

Every change records an `actor`: `{type, id, name, on_behalf_of, client}`.

| `type`   | Means                                                                                                                                              |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user`   | A signed-in person, in the dashboard. A person at Runledger support acting in your account is a `user` whose `name` is `Runledger support`         |
| `agent`  | An agent, through an OAuth session or an agent key. `client` names the software, such as the MCP client, and `on_behalf_of` the person it acts for |
| `key`    | Any other API key                                                                                                                                  |
| `rule`   | A reversal rule                                                                                                                                    |
| `system` | Runledger itself, such as a clock closing, or a customer's own change on the customer view, named `customer_session` with the session's ID         |

Runledger fills the actor in from the credential, and a client can't claim to be someone else. An API key always acts as itself, a secret key included, and the dashboard calls the API as the signed-in person, so approving a proposal or removing a live provider needs an Admin signed in to the dashboard. A request that sends a `Runledger-Actor` header is refused with `400 parameter_unknown`. `actor` appears on request logs, reversals, dispute decisions, credit grants and voids, outcome definition versions, plans, webhook endpoints, payment providers, routing and notification rules, and config proposals. Request logs filter by `actor_type`, `actor` and `key`, so you can list everything an agent did.

## Request logs

`GET /v1/request_logs` lists the requests made with your keys in the last 30 days, with the status, the error code, the idempotency key, the actor and the bodies. Secrets are redacted before a request is stored: a webhook signing secret and every link that grants access (a checkout page, a customer view link, an export download, an invoice's payment page, a provider's connect page) read `[redacted]`. Other fields are kept as sent. A configuration write sent with `Runledger-Dry-Run: true` is logged like any other request, and appears tagged **Dry run**, so you can tell a check from a change. The dry run writes nothing. When the same change is later made for real, that request is logged on its own, and the [audit event](#the-audit-log) it creates has `dry_run_preceded: true`.

## The audit log

`GET /v1/audit_events` records what changed, who changed it, and through which surface (`dashboard`, `api` or `mcp`). Each event has the `action`, the `resource` and its `version`, the field `changes`, the `request_id`, and `dry_run_preceded`, which is `true` when the same change was dry-run or validated first. The dry run itself is in the [request logs](#request-logs), tagged **Dry run**. A change made by approving a [config proposal](/api-reference/how-configuration-works#proposals-for-live) also carries `approved_by`, the signed-in Admin who approved it and when. `actor` stays the one who made the request, such as the agent or the Collaborator that proposed it. An Admin can approve any proposal, including one for notification or routing rules. Events are kept for at least 13 months, and the log needs the `audit:read` scope.

Besides configuration changes, these write an audit event with the `actor`:

| Action                      | Written when                                                                                                                                                                                                                                                                                                                                    |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `catalog.price_change_held` | A person holds a [catalog price change](/guides/price-changes-and-simulation#decide-what-happens-to-your-prices) for a price rule in the dashboard review, so customer prices stay where they are. `actor` is who held it, and the `resource` is the price change (`catp_...`)                                                                  |
| `auto_top_up.created`       | A customer's auto top-up rule is set for the first time, by the customer, an operator or an agent                                                                                                                                                                                                                                               |
| `auto_top_up.updated`       | An existing rule changes: its `threshold`, `target_balance`, `monthly_limit` or payment method                                                                                                                                                                                                                                                  |
| `auto_top_up.turned_off`    | A rule is turned off by the customer, an operator or an agent, which removes it: `DELETE /v1/customers/{customer_id}/auto_top_up`, after which a retrieve is `404`. A rule that stops by itself after three failed payments stays, with `status: disabled`, sends the `auto_top_up.disabled` webhook and is recorded with the system as `actor` |

The `changes` on an auto top-up event show the fields before and after, so you can see who raised a `monthly_limit`. Holding a price change writes no configuration, which is why it has an event of its own. Choosing *follow* instead, or editing the rule, is an ordinary price rule change and is recorded as one. Filter by `actor_type`, `actor`, `resource`, `source` and time.

## See also

* [Configuration](/api-reference/how-configuration-works) covers proposals for live changes.
* [Set up with a coding agent](/agents/mcp) lists the scope each tool needs.
* [The dashboard](/dashboard#roles) lists the roles, including who approves proposals and who changes a customer's limits.