API tokens
Long-lived tokens used by CLI tools, MCP clients, and any non-browser
integration. The op_ prefix is what makes the server
recognize them on the Authorization header.
op_GgFnE3R8…) is shown only at creation time. The server
stores the SHA-256 hash plus a 12-character display prefix, so
administrators can identify a token without being able to recover it.
POST /api/tokens
Create a token. Authenticate with a JWT (cookie or bearer) — you cannot
mint new tokens using another API token's session because the dashboard is
the issuance surface, but in practice get_current_user will
also accept an op_ token here.
Request
POST /api/tokens
Content-Type: application/json
Authorization: Bearer eyJhbGc...
{ "name": "Laptop CLI" }
Response 200
{
"id": 7,
"name": "Laptop CLI",
"token_prefix": "op_GgFnE3R8",
"status": "active",
"created_at": "2026-05-03T17:30:00",
"last_used_at": null,
"token": "op_GgFnE3R8X4kP9bH...A2"
}
token field is
returned only on creation. Subsequent GET /api/tokens
responses include the prefix only.
cURL
curl -X POST https://outpost.click/api/tokens \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{"name":"Laptop CLI"}'
Validation
name— 1 to 100 characters.
GET /api/tokens
List all tokens for the current user, newest first. The full token value is never returned — only the display prefix.
Response 200
[
{
"id": 7,
"name": "Laptop CLI",
"token_prefix": "op_GgFnE3R8",
"status": "active",
"created_at": "2026-05-03T17:30:00",
"last_used_at": "2026-05-03T18:12:55"
},
{
"id": 4,
"name": "Old token",
"token_prefix": "op_8Hk2ksv1",
"status": "active",
"created_at": "2026-04-12T11:01:22",
"last_used_at": null
}
]
cURL
curl https://outpost.click/api/tokens \
-H "Authorization: Bearer eyJhbGc..."
PUT /api/tokens/{id}
Rename a token. Only the name can be modified; the secret
itself is immutable (delete and recreate to rotate).
Request
PUT /api/tokens/7
Content-Type: application/json
Authorization: Bearer eyJhbGc...
{ "name": "Laptop CLI (mac mini)" }
Response 200
{
"id": 7,
"name": "Laptop CLI (mac mini)",
"token_prefix": "op_GgFnE3R8",
"status": "active",
"created_at": "2026-05-03T17:30:00",
"last_used_at": "2026-05-03T18:12:55"
}
cURL
curl -X PUT https://outpost.click/api/tokens/7 \
-H "Authorization: Bearer eyJhbGc..." \
-H "Content-Type: application/json" \
-d '{"name":"Laptop CLI (mac mini)"}'
Errors
404 Token not found(also returned when the token belongs to another user).
DELETE /api/tokens/{id}
Permanently revoke a token. After deletion any request bearing it will
get 401 Invalid API token.
curl -X DELETE https://outpost.click/api/tokens/7 \
-H "Authorization: Bearer eyJhbGc..."
200 OK
{ "deleted": 7 }
Token format
Tokens are generated by:
op_ + secrets.token_urlsafe(32)
which yields ~43 base64url characters of entropy after the prefix. The
storage shape (in api_tokens):
| Column | Description |
|---|---|
token_hash | SHA-256 of the full token, hex. |
token_prefix | First 12 characters of the full token (e.g. op_GgFnE3R8). |
status | Currently always "active"; only active tokens are accepted on auth. |
last_used_at | Updated on every successful auth via the token. |
Using an API token
Every endpoint that accepts JWTs also accepts an API token in the same
Authorization: Bearer header.
Bash function
outpost() {
curl -sS -H "Authorization: Bearer $OUTPOST_TOKEN" "$@"
}
outpost https://outpost.click/auth/me
outpost https://outpost.click/pages
Python helper
import os, requests
BASE = "https://outpost.click"
SESSION = requests.Session()
SESSION.headers["Authorization"] = f"Bearer {os.environ['OUTPOST_TOKEN']}"
def list_pages():
r = SESSION.get(f"{BASE}/pages")
r.raise_for_status()
return r.json()
def upload(name, zip_path, visibility="public"):
with open(zip_path, "rb") as fh:
r = SESSION.post(
f"{BASE}/api/pages",
data={"name": name, "visibility": visibility},
files={"file": fh},
)
r.raise_for_status()
return r.json()
Node.js (fetch)
import fs from "node:fs";
import FormData from "form-data";
import fetch from "node-fetch";
const TOKEN = process.env.OUTPOST_TOKEN;
const fd = new FormData();
fd.append("name", "My Site");
fd.append("visibility", "public");
fd.append("file", fs.createReadStream("./site.zip"));
const res = await fetch("https://outpost.click/api/pages", {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
body: fd,
});
console.log(await res.json());