# API reference

Build hardened server images from a spec you write, check how each image was built, and install it on your own servers.

The API is plain HTTPS with JSON in and JSON out. Every operation in this reference is also an [MCP](/mcp) tool with the same name, and the dashboard uses the same operations. The machine-readable description of the whole API is at [`GET /v1`](/api/describe).

## Base URL {#base-url}

```text
https://api.tarsana.io
```

Every path starts with `/v1`. Send JSON request bodies with `Content-Type: application/json`.

## Authentication {#authentication}

Send exactly one credential with every request that needs one:

| Header | Credential | Where you get it |
|---|---|---|
| `X-Tarsana-Access-Token` | An API access token. Lasts 90 days. Use it in scripts, CI jobs and MCP clients. | [`issue-access-token`](/api/issue-access-token) |
| `X-Tarsana-Session` | A session. Lasts one hour. Needed to create, list and revoke access tokens. | [`login`](/api/login) |

Requests that read or change something in your tenant also send your tenant ID in the `X-Tarsana-Tenant` header, and the same ID in the address. Tarsana checks that your credential belongs to that tenant.

```sh
curl "https://api.tarsana.io/v1/tenants/acme/audit" \
  -H "X-Tarsana-Access-Token: $ACCESS_TOKEN" \
  -H "X-Tarsana-Tenant: acme"
```

A request with no credential, or one Tarsana does not recognise, gets [`unauthenticated`](/api/errors#unauthenticated). A credential whose role does not allow the operation gets [`forbidden`](/api/errors#forbidden), with the roles that do. Each operation's page says which roles can call it:

| Role | What it can do |
|---|---|
| `viewer` | Can read your submissions and download what your builds produced. Cannot submit specs or change servers. Give it to dashboards, status checks and auditors. |
| `builder` | Can do everything a viewer can, and submit and check specs. Cannot change servers. Give it to CI jobs that build images. |
| `deployer` | Can do everything a builder can, and install images on your servers, manage your servers and store your provider credentials. The widest role a tenant can hold. |

Signing up, signing in and reading the API description need no credential.

## Errors {#errors}

A request to the API that Tarsana refuses, including one whose body is not valid JSON, gets an HTTP status of 400 or higher and an `error` object with a `code`, a `message` for people, sometimes a `detail` object, and a `doc_url` that links to the code's entry in the [error reference](/api/errors):

```json
{
  "error": {
    "code": "rate_limited",
    "message": "One sentence that says what went wrong.",
    "detail": {"retry_after_seconds": 30},
    "doc_url": "https://docs.tarsana.io/api/errors#rate_limited"
  }
}
```

Act on the `code`, never on the `message`. The [error reference](/api/errors) lists every code with what happened and what to do.

## Retries and idempotency {#idempotency}

You can repeat these requests safely:

| Operation | What happens when you repeat it |
|---|---|
| [`submit`](/api/submit) | The same spec returns the same submission; `created` is `false` and no second build starts. |
| [`create-server`](/api/create-server) | The same `request_id` for the same submission returns the server the first request created. A new `request_id` creates a new server. |
| [`decommission-server`](/api/decommission-server) | Continues from the step where an earlier request stopped. |
| [`complete-signup`](/api/complete-signup) | Returns the same login, with `secret` set to `null`. |
| [`remove-provider-credential`](/api/remove-provider-credential), [`revoke-access-token`](/api/revoke-access-token) | Answer `removed: false` or `revoked: false` when there is nothing left to remove. |

Every operation with the **Read** badge changes nothing and can be repeated at any time.

## Rate limits {#rate-limits}

When you send too many requests in a short time you get [`rate_limited`](/api/errors#rate_limited) with HTTP status 429. The `Retry-After` header and `detail.retry_after_seconds` say how many seconds to wait.

## Request IDs {#request-ids}

Answers do not carry a request ID yet. If you contact Tarsana support about a request, include the time you sent it, the operation, your tenant ID and the error `code`.

## Operations {#operations}

### Create your account

Sign up, then sign in.

| Operation | | What it does |
|---|---|---|
| [`POST /v1/signups`](/api/signup) | Change | Create your Tarsana account |
| [`POST /v1/signups/complete`](/api/complete-signup) | Change | Finish creating your account |
| [`POST /v1/sessions`](/api/login) | Session | Sign in and start a session |
| [`DELETE /v1/sessions/current`](/api/logout) | Session | End your session |

### Set up access

Create API access tokens for your scripts and MCP clients, and store the API token of your cloud provider.

| Operation | | What it does |
|---|---|---|
| [`POST /v1/access-tokens`](/api/issue-access-token) | Change | Create an API access token |
| [`GET /v1/access-tokens`](/api/list-access-tokens) | Read | List your API access tokens |
| [`DELETE /v1/access-tokens/{token_id}`](/api/revoke-access-token) | Destructive | Revoke an API access token |
| [`POST /v1/tenants/{tenant}/providers/{provider_name}/credential`](/api/set-provider-credential) | Change | Store or replace your provider credential |
| [`GET /v1/tenants/{tenant}/providers/{provider_name}/credential`](/api/provider-credential) | Read | Check whether your provider credential is stored |
| [`DELETE /v1/tenants/{tenant}/providers/{provider_name}/credential`](/api/remove-provider-credential) | Destructive | Remove your provider credential |

### Build an image

Check your spec, submit it, follow the build and verify what it produced.

| Operation | | What it does |
|---|---|---|
| [`POST /v1/tenants/{tenant}/specs/validate`](/api/validate) | Read | Check a spec without submitting it |
| [`POST /v1/tenants/{tenant}/specs`](/api/submit) | Change | Submit a spec to build an image |
| [`GET /v1/tenants/{tenant}/submissions/{submission}`](/api/status) | Read | See the status of a submission |
| [`GET /v1/tenants/{tenant}/submissions/{submission}/{artifact}`](/api/fetch) | Read | Download a record of a submission |
| [`GET /v1/tenants/{tenant}/submissions/{submission}/attestation`](/api/attestation) | Read | Get what you need to verify an image yourself |
| [`GET /v1/tenants/{tenant}/audit`](/api/audit) | Read | Read your audit trail |

### Run it on your servers

Create a server from your spec or install your image on one, update it, review the servers in your cloud account, and remove the ones you no longer need.

| Operation | | What it does |
|---|---|---|
| [`POST /v1/tenants/{tenant}/submissions/{submission}/create-server`](/api/create-server) | Change | Create a server from your spec |
| [`POST /v1/tenants/{tenant}/servers/{server_name}/install-command`](/api/install-command) | Change | Get the one-line command that installs Tarsana on a server you have |
| [`POST /v1/tenants/{tenant}/submissions/{submission}/provision`](/api/provision) | Change | Install a built image on one of your servers |
| [`POST /v1/tenants/{tenant}/servers/{server_name}/update`](/api/request-update) | Change | Update a server to the release your channel carries |
| [`GET /v1/tenants/{tenant}/servers/{server_name}/update`](/api/update-status) | Read | See how a server's update went |
| [`GET /v1/tenants/{tenant}/providers/{provider_name}/servers`](/api/servers) | Read | List the servers in your cloud account that Tarsana did not create |
| [`POST /v1/tenants/{tenant}/providers/{provider_name}/servers/{server_id}/decommission`](/api/decommission-server) | Destructive | Decommission a server that Tarsana created |
| [`POST /v1/tenants/{tenant}/providers/{provider_name}/servers/{server_id}/destroy`](/api/destroy-server) | Destructive | Delete a server that Tarsana did not create |

### Reference

The machine-readable description of this API.

| Operation | | What it does |
|---|---|---|
| [`GET /v1`](/api/describe) | Read | Get the full API description as JSON |
