---
updatedAt: 2026-09-30T15:54:20.000Z
---

Fetch the complete documentation index at: https://docs.avvio.xyz/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Authentication

Every request carries one header, `x-api-key`, and the key in it is the whole
credential.

```bash
curl https://api.avvio.xyz/business/api/v1/payments/organizations/{orgId}/balance \
  -H "x-api-key: avvio_live_…"
```

A key is `avvio_live_` or `avvio_test_`, a 32-character lowercase hex public id,
`_`, and a 43-character base64url secret. Copy the whole value. The prefix is
part of the credential, so an `avvio_test_` key cannot move real money. There
is nothing to sign.

## Treat the key like a database password

- Store it in a secrets vault, or in an environment variable if you have no vault.
- Never commit it, ship it in a client-side bundle or mobile app, or paste it
  into a screenshot or support ticket.
- Call the API from your server only, with test and live keys alike. The API
  reference's Try It console is the one place to paste a test key.

> [!CAUTION]
> **A leaked key spends your balance**
>
> If a key is exposed, revoke it in the dashboard; revocation is immediate.
> Then issue a successor and tell us at security@avvio.xyz. To replace a
> healthy key without downtime, rotate it instead: the successor is live
> before the predecessor stops.

## Getting a key

Issue keys in the dashboard. A key is shown once. We store only a hash and
cannot show it again, so copy it when it appears.

In the API reference, paste the complete `avvio_test_*` value as the API key
and use **Try It** on any operation. Put your normal organization id in `orgId`;
the test key routes the request to your sandbox. Never paste a live key there.

These settings, chosen at issue and enforced on every request, limit what a
leaked copy can do:

| Setting                                     | What it buys                                                        |
| ------------------------------------------- | ------------------------------------------------------------------- |
| Read-only (the `read` scope)                | The key can reconcile but cannot spend                              |
| Crypto payouts (the `crypto_payouts` scope) | The key may pay crypto recipients in USDC from your organization's own wallet. Off unless you grant it, and only alongside `write`. A key without it gets `CRYPTO_PAYOUTS_DISABLED` |
| Refunds (the `refunds` scope)               | The key may refund checkout payments to buyers. Off unless granted, only alongside `write`, and only an owner or admin can grant it. A key without it gets `INSUFFICIENT_SCOPE` on the refund route |
| IP pinning (allowlisted source addresses)   | Requests from any other address are refused                         |
| Expiry                                      | One year by default, two at most                                    |

[Security](/security/#limiting-what-a-leaked-key-can-do) covers what each setting enforces and what rotation
carries forward.

Give a read-only key to anything that only observes, such as a reconciliation
job, a monitoring probe or a dashboard. Keys spread most in those places.

## One key per environment

A test key addresses the same organization id as your live key and resolves,
server-side, to that organization's sandbox. Staging and production differ by
one variable.

Issue a separate key per service too, so you can revoke one without taking down
the others.

## Expiry, rotation, and pinning

The dashboard shows each key's expiry from the day it is issued.

Rotating a key (the dashboard's **Rotate**, which calls
`POST /organizations/{orgId}/api-keys/{apiKeyId}/rotate` from a signed-in
session; an API key cannot call it) issues a successor and gives the
predecessor a deadline, 24 hours by default. Deploy the new key, confirm traffic has moved,
and let the old one lapse.

Pin keys to source addresses where you can. A payout service usually runs from
a few known egress addresses, and a pinned key is useless outside your network.

## Retries

Every money-moving request takes an `Idempotency-Key`. The same key with the
same body replays the stored response instead of paying twice. See
[Idempotency](/idempotency/).

## Errors

| `type`               | Meaning                                                  |
| -------------------- | -------------------------------------------------------- |
| `UNAUTHORIZED`       | Missing, malformed, unknown or revoked key               |
| `KEY_EXPIRED`        | The key has expired. Issue a new one; an expired key cannot be rotated |
| `KEY_IP_NOT_ALLOWED` | This key is pinned, and this request came from elsewhere |
| `FORBIDDEN`          | Valid key, wrong organization                            |
| `INSUFFICIENT_SCOPE` | A read-only key attempting to spend or to read full account details, or a key without the `refunds` consent attempting a refund |
| `LIVE_KEY_ORG_NOT_APPROVED` | A live key writing before your business verification is approved. Reads work |
| `ACCOUNT_BLOCKED`    | Your organization is suspended. Contact us |

An unknown, revoked or wrong key all get the same answer, because telling them
apart would only help someone guessing. [Errors](/errors/) lists every error type
with whether your money moved and whether retrying is safe.

## What a key cannot do

A key cannot accept provider terms, manage your team, issue further keys,
approve or reject a held payout, or register, pause, re-sign or replay a webhook
endpoint. Those stay human actions in the dashboard. A key may read the approval
queue (`GET .../payouts/approvals`) and the audit trail
(`GET .../audit-events`). The [API reference](/reference/) lists every route a
key may call.
