# KeyContain for agents

KeyContain lets your person give you narrow access to services such as QuickBooks Online, GitHub and GitLab. You call those services through KeyContain with a KeyContain key, and KeyContain checks every request against the rules your person approved.

## 1. Pair with your person's account

1. Start a pairing, naming yourself in the KeyContain-Agent header:

       POST https://api.keycontain.com/v1/pairings
       KeyContain-Agent: setup-claude

2. Show your person the `url` and `code` from the response, for example:
   "To connect me to KeyContain, open https://app.keycontain.com/pair/KC7Q-2MVX, check that the page shows the code KC7Q-2MVX, then approve."
3. Wait for the answer: call GET on `statusUrl` with `?wait=90`, and repeat while `status` is `pending`. A pairing expires after 15 minutes.
4. When `status` is `approved`, the response contains your key, `kc_live_...`, exactly once. Store it securely. It is your KeyContain master key: you can read everything in the account, and every change you make waits for your person's approval.

## 2. Use your key

- Send it in the Authorization header: `Authorization: Bearer kc_live_...` (for git, as the password).
- Call a service by putting `https://api.keycontain.com/p/` in front of its full URL, for example `https://api.keycontain.com/p/https://gitlab.com/api/v4/projects`.
- See what your key may do: `GET https://api.keycontain.com/v1/key`.
- Set up services, keys and rules with one change set: `POST https://api.keycontain.com/v1/changes`. Your person approves it once and types any upstream secrets into KeyContain, or for QuickBooks Online connects the company with Intuit. Never ask your person to give you upstream secrets.

## 3. Requests that need your person's confirmation

- Send `KeyContain-Agent`, `KeyContain-Purpose` (what the request is for) and `KeyContain-Reason` (why it is necessary). Without them the request is refused with 400 DESCRIPTION_REQUIRED.
- KeyContain holds the request for up to 90 seconds. If your person approves in time, you get the service's normal response.
- Otherwise you get 403 CONFIRMATION_REQUIRED with `statusUrl` and `expiresAt`. Check `statusUrl` later, and when `status` is `approved`, send the identical request again before `expiresAt`. A confirmation waits 24 hours unless your person chose another time.
- 403 CONFIRMATION_DENIED means your person declined. Do not send the same request again without asking them.
- 503 TOO_MANY_WAITING: nobody was asked. If the message says your account's waiting requests hold 1 GB, ask your person to decide on the requests waiting in the KeyContain dashboard, then try again. Otherwise try again in a minute.

## QuickBooks Online

- Your person connects QuickBooks Online with Intuit in KeyContain. Never ask them for Intuit credentials, a client ID or secret, a refresh token or a company ID.
- To add a company, request a service with `services.create` in a change set, or with `POST https://api.keycontain.com/v1/services`. Send only `"type": "quickbooks"` and an optional `label`. Your person connects the company when they approve, and the new service's `baseUrl` ends with its company ID, for example `https://quickbooks.api.intuit.com/v3/company/9130354`.
- Reads can cost money. On a service connected through KeyContain's Intuit app, each successful read (a GET or HEAD, including queries and reports) costs $0.01, taken from your person's balance. Writes are free. Check `pricePerRead` on each service in `GET https://api.keycontain.com/v1/key`. A service without it has free reads.
- Attachments: `GET .../download/{id}` returns a temporary link to the file at Intuit. Fetch that link directly, without KeyContain and without your KeyContain key. KeyContain records only the request for the link, which counts as a read.
- To upload a file, send a multipart `POST .../upload`. If it waits for your person's confirmation, it can be at most 10 MiB, and your person sees the files before approving. Build the multipart body once and keep it: after approval, send the identical bytes again, with the same boundary.
- Send uploads as standard multipart/form-data without Content-Encoding: for each file, one `file_metadata_N` part with its JSON metadata and one `file_content_N` part with its bytes, and a boundary that appears only at the delimiters. KeyContain refuses an upload it can't read unambiguously with 400 UPLOAD_UNREADABLE, whose message says what to change.
- Your person may allow uploads only for some files, for example one image attached to an expense. Rules for `attachable.upload` in `GET https://api.keycontain.com/v1/key` show these as `upload.` conditions, which must hold for every file and every record it attaches to. An upload outside them waits for your person or is refused.
- 502 UPSTREAM_CREDENTIAL_FAILED on a QuickBooks service: ask your person to reconnect it. Send `PATCH https://api.keycontain.com/v1/services/{service}` with `{ "askForSecrets": true }`, and they reconnect the same company when they approve.

## Plaid

- Each Plaid service is one bank your person connected, with the accounts they shared. `GET https://api.keycontain.com/v1/key` lists them, with each account's `id` and whether Plaid has PDF statements for it (`"statements": true`).
- Call Plaid's API as Plaid documents it, at `https://api.keycontain.com/p/https://production.plaid.com/...`, and put the Plaid service's ID where Plaid expects an access token, for example `"access_token": "svc_plaid_fifththird"`. Leave out `client_id` and `secret`. KeyContain adds them.
- `/statements/list` shows which months exist for each account, `/statements/download` with a `statement_id` returns the PDF, and `/statements/refresh` with a `start_date` and `end_date` fetches newer or older months.
- KeyContain forwards only the Plaid endpoints in its catalog. Others are refused with 403 OPERATION_NOT_ALLOWED.
- Your person adds and reconnects Plaid services in the KeyContain dashboard, where they sign in to their bank. On 502 UPSTREAM_CREDENTIAL_FAILED, tell your person which bank to reconnect there.

## Google Drive

- The connection has full Drive read and write access. Your key's rules decide which native Drive requests KeyContain forwards, including requests for files shared with your person.
- Your person may limit you to My Drive paths such as `Bank statements/**`, or exact folder IDs such as `folder://1SharedA/**`. Both include descendants. The selected folder itself is outside that scope. The current ACL from `GET https://api.keycontain.com/v1/key` includes the ID for an ID-based selection. Use it in Google’s native `q` parameter: `'1SharedA' in parents`. Give every new folder or file its `name` and `parents`. Upload with `uploadType=multipart`. Resumable uploads aren't supported.
- To add a Google Drive service, request one with `"type": "googledrive"` and an optional `label`, as for QuickBooks Online. Your person connects their Google account when they approve.

## Limits

- All keys in your person's account share one pool: 10 requests a minute and 100 MB of traffic a month for free, or 50 requests a minute and 1 GB a month for each capacity unit, at $0.50 a month per unit. Your person can also give your key limits of its own. Every request you make with your key counts, including calls to KeyContain itself.
- 429 RATE_LIMITED or TRAFFIC_LIMIT_REACHED: the message says whether this key's limit or your account's limit was reached. Wait for the `Retry-After` header's number of seconds. Check your usage with `GET https://api.keycontain.com/v1/key`. If the account's limit is too small for your work, tell your person they can add capacity in the KeyContain dashboard.
- 402 PAYMENT_REQUIRED: the account's balance can't pay for what you asked, a change to the account's capacity or a QuickBooks read that costs money. Tell your person to add funds in the KeyContain dashboard. Writes and other services keep working.
- 403 IP_NOT_ALLOWED: your key can't be used from your current IP address. Tell your person which address you are calling from.

## Errors

Every KeyContain error has `error.code` and a `message` written for you. KeyContain may add fields to its JSON responses at any time, so ignore fields you do not recognise. 403 SERVICE_NOT_ATTACHED and OPERATION_NOT_ALLOWED mean your key's rules do not cover the request: check `GET https://api.keycontain.com/v1/key`.

Full reference: https://api.keycontain.com/docs
