> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tilion.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Credentials, connect links, verifications and approvals

The credential vault stores your organisation's site logins (passwords and authenticator seeds) encrypted, so an agent can log in with a `vault://` reference and never see the secret. The REST surface below is read-and-status only: no route ever returns a secret, a code, or a connect token, not even to the caller that created it. Credentials are created, edited and deleted from the dashboard, or captured from the person who owns the login through a connect link. Second-factor codes come in through verifications, and a credential whose policy is `approval_required` pauses the login until a member approves it in the dashboard.

## GET /v1/credentials

List the credentials stored for your organisation, metadata only.

Auth: Bearer API key. No body.

Response:

| Field | Type | Meaning |
| - | - | - |
| `credentials[]` | object\[] | newest first |
| `credentials[].id` | string | the credential id |
| `credentials[].kind` | string | `password`, `totp`, or `cookie` |
| `credentials[].name` | string | the name chosen at creation (a connect link uses the host with dots replaced by dashes) |
| `credentials[].host` | string or null | the site it is pinned to |
| `credentials[].path` | string | `<kind>/<name>`; the `vault://` reference is `vault://` plus this path |
| `credentials[].policy` | string | `auto` or `approval_required` |
| `credentials[].grant_scope` | string | `once`, `session` or `ttl`: how long an approval or a resolve stays valid |
| `credentials[].ttl_seconds` | integer or null | the grant lifetime for `ttl` |
| `credentials[].risk_tier` | string or null | the tier assigned at creation |
| `credentials[].status` | string | `active`, `revoked`, or `needs_reauth` |
| `credentials[].created_at`, `updated_at` | string | ISO timestamps |

With nothing stored:

```json theme={"dark"}
{"credentials": []}
```

Errors: 401; 502 when the credential store did not answer.

Free. Ask this before attempting a `login` so an agent knows whether a credential for the host already exists instead of asking a person for a password.

## GET /v1/credentials/\{cred\_id}

One credential's metadata (the fields listed above).

Auth: Bearer API key.

Errors: 404 `credential not found` for an unknown or malformed id, or another organisation's (not distinguished); 401.

## DELETE /v1/credentials/\{cred\_id}

Revoke a credential everywhere: the row is deleted first, then every live session that used it is signed out and the cookies captured from its logins are forgotten.

Auth: Bearer API key.

Response: `{"ok": true, "id": "<cred_id>", ...}` plus counts of what the revocation reached (sessions touched, cookie jars deleted).

Errors: 404 `credential not found`; 401.

## POST /v1/credentials/connect

Mint a one-time capture link for a host. You post the link to the person who owns the login; they open it, enter the username and password (or an authenticator key) on a page served by Tilion, and the credential lands in your organisation's vault pinned to that host. The token is in the URL only: it is returned once and never stored or shown again.

Auth: Bearer API key. The key must belong to an organisation account (one created through the dashboard sign-up).

Request:

| Field | Type | Default | Meaning |
| - | - | - | - |
| `host` | string | required | the site the credential is for, for example `github.com` |
| `user_id` | string | null | bind the link to one dashboard user; anyone else who opens it voids it |
| `session_id` | string | null | the session that hit the login, recorded on the link |
| `kinds` | string\[] | `["password"]` | what the page asks for: `password`, `totp` |
| `reason` | string | null | shown on the capture page |

Response: `connect_id`, `connect_url` (the link to post), `host`, `expires_at`. A link lives 15 minutes and completes once.

Errors:

| Status | `detail` | When |
| - | - | - |
| 400 | `host is required` | no `host` |
| 400 | `connect links need an org account: this key is not owned by an org` | the key has no organisation to bind the credential to; sign up on the dashboard and mint a key there |
| 400 | other messages | `host` or `kinds` not accepted |

Free. The capture page itself (`GET /connect/{token}`) needs no API key; it is plain HTML, never echoes what was typed, and answers 404 for a link that is unknown, used, expired or voided. A link minted for one dashboard user and opened by another is voided (403).

## GET /v1/credentials/connect/\{connect\_id}

Poll a link you minted. Status only.

Auth: Bearer API key (the same organisation that minted it).

Response: `connect_id`, `status` (`pending`, `completed`, `expired`, `voided`), `host`, `credential_id` (set once completed; use it with `GET /v1/credentials` to find the `path`), `expires_at`, `session_id`.

Errors: 404 `connect link not found` for an unknown id or another organisation's link (the two are not distinguished).

## GET /v1/verifications

The pending second-factor verifications for your organisation, so an agent or a dashboard can show the inbox.

Auth: Bearer API key.

Response: `verifications[]`, each with the fields of `GET /v1/verifications/{id}` below. Expired rows are dropped before the list is answered.

```json theme={"dark"}
{"verifications": []}
```

## GET /v1/verifications/\{verification\_id}

Poll one verification. The code is never returned.

Auth: Bearer API key.

Response:

| Field | Type | Meaning |
| - | - | - |
| `id` | string | the verification id |
| `status` | string | `pending`, `resolved`, `expired`, `failed` |
| `host` | string | the site that asked for the code |
| `rung` | string or null | which channel produced the outcome (a stored authenticator seed, or a code forwarded through the API) |
| `expires_at` | string | when a pending verification lapses |
| `session_id` | string or null | the session whose login is waiting |

Errors: 404 `verification not found` for an unknown id or another organisation's row.

## POST /v1/verifications/\{verification\_id}/code

Submit the one-time code for a pending verification. A login that stopped at a second factor claims the code exactly once and types it; the code is stored encrypted and never returned.

Auth: Bearer API key.

Request:

| Field | Type | Default | Meaning |
| - | - | - | - |
| `code` | string | required | the one-time code |
| `channel` | string | `api` | where the code came from, for your audit trail |

Response: `{"id": "<verification_id>", "status": "resolved"}`.

Errors:

| Status | `detail` | When |
| - | - | - |
| 404 | `verification not found` | unknown id or not yours |
| 409 | `verification not pending: <why>` | the verification exists but is already resolved, expired or failed |
| 422 | `code is required` | empty or missing `code` |

## GET /v1/approvals/\{approval\_id}

Poll a login approval. When a `login` task uses a credential whose policy is `approval_required`, the task answers at once with `ok: false`, `status: "awaiting_approval"` and an `approval_id` (in `async_mode`, the same fields appear in the job's `result`). A member of your organisation approves or denies it in the dashboard, which can show the paused session through `live_view_url`. You poll here, and when the status is `approved` you call `POST /v1/agent` again with the same `login` parameters plus `approval_id`; the approval is spent by that call, is bound to the credential and session it was granted for, and expires if unused.

Auth: Bearer API key (the organisation that owns the approval). Deciding is a dashboard action.

Response:

| Field | Type | Meaning |
| - | - | - |
| `id` | string | the approval id |
| `status` | string | `pending`, `approved`, `denied`, `expired`, `consumed` (already used to resume a login) |
| `expires_at` | string | when a pending approval lapses |
| `job_id` | string or null | the paused job, for a login started in `async_mode` |
| `live_view_url` | string or null | a live view of the paused session for the approver; view tokens allow input |

Errors: 404 `approval not found` for an unknown or malformed id, or another organisation's approval. A resume with an approval that is not `approved`, is for another credential or session, or has expired is refused by `POST /v1/agent` with 409 `approval not usable: <why>`.

Free. Two concurrent resumes with the same approval cannot both succeed; the second sees `consumed`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.