# API

A REST API under /api, authenticated with a bearer token. Start runs, read results, and manage inventory from CI or a script.

Source: https://opafra.com/docs/reference/api

---

Everything the interface does is an API call. The common reasons to use it directly are
starting a run from CI, pulling run results into another system, and registering hosts
from whatever already knows about them.

All endpoints are under `/api`.

## Authentication

Create a token in **Settings**, then **API tokens**. Send it as a bearer token:

```bash
curl -H "Authorization: Bearer opf_..." \
  https://your-opafra-host/api/plans
```

Tokens start with `opf_` and are shown once, at creation. Only a hash is stored, so
Opafra cannot show you the token again, and a lost token is replaced rather than
recovered.

A token can carry an expiry, and can be revoked at any time. The interface shows its
prefix and when it was last used, which is enough to identify one without exposing it.

> **Important**
>
> A token acts as the user who created it and carries that user's role. A token made by an
> Admin can do Admin things. Create tokens from an account with the narrowest role that
> does the job.

Token creation and revocation are both recorded in the [audit log](/docs/governance/audit-log).

## Starting a run

```bash title="start a run"
curl -X POST https://your-opafra-host/api/executions \
  -H "Authorization: Bearer opf_..." \
  -H "Content-Type: application/json" \
  -d '{
    "planId": "…",
    "execution_mode": "dry_run",
    "variables": { "app_version": "1.4.2" }
  }'
```

`execution_mode` is one of `auto`, `semi_assisted`, `interactive` or `dry_run`. The
response carries the execution id.

> **Note**
>
> Starting a run through the API is subject to exactly the same rules as starting one in
> the interface. A protected environment still pauses the run for approval, and the
> concurrency guard still refuses an overlapping run. There is no back door.

## Reading a run

```bash
curl -H "Authorization: Bearer opf_..." \
  https://your-opafra-host/api/executions/{id}
```

Returns the status, per-step and per-host results, and the approval record. Poll it, or
listen to the live stream where you want output as it happens.

`GET /api/executions` lists runs, and supports filtering for the usual questions: this
plan, this environment, this status.

## The main resources

| Path | Holds |
|---|---|
| `/api/plans` | Plans and their steps |
| `/api/executions` | Runs, live and historical |
| `/api/inventory/servers` | Registered hosts |
| `/api/environments` | Environments, their variables and grants |
| `/api/ssh-keys` | Managed SSH keys |
| `/api/templates` | Config templates |
| `/api/scripts` | Scripts a plan can run |
| `/api/git-sources` | Synced repositories |
| `/api/collections` | Plan folders |
| `/api/audit-logs` | The audit trail, Admin only |

## Rules that apply everywhere

**Tenancy.** Every response is scoped to the token owner's tenant. Something you may not
see returns 404, not 403, so an id cannot be probed.

**Roles.** The same role requirements as the interface. Most write endpoints need
Operator; membership, grants and the audit log need Admin.

**Unknown fields are rejected.** The request body is validated strictly, and an
unrecognised field is a 400 rather than being ignored. A typo fails loudly instead of
silently doing the wrong thing.

**Secrets are never returned.** No endpoint returns a private key, a password, or a
resolved secret value. A generated SSH private key appears in exactly one response, when
it is created.

## Using it from CI

The typical shape is: start a run in `dry_run`, read the result, and start a real run only
if the preview is what you expected.

Remember the gate. If the target environment requires approval, the real run will pause
and your job should treat `PAUSED` as a normal outcome rather than a timeout, or target an
environment that does not require approval.

## Next steps

- [Running a plan](/docs/running/executing) for modes and statuses
- [Organisations and roles](/docs/access/organisations-and-roles) for what a token can do
