Skip to content

API Reference Appendices

This appendix describes the key API endpoints used by the web interface, CLI, and join scripts.

Use it when you need endpoint-level details for automation, troubleshooting, or custom integrations.

For term definitions, see the Glossary. For step-by-step workflows, see:

API Surface Map

graph LR
    A[CLI and Join Scripts] --> B[OAuth Device Authorization]
    B --> C[Token Endpoint with DPoP]
    C --> D[Join API or Signing API]
    D --> E[Policy Evaluation and CA Signing]
    F[Web UI] --> G[Session Auth and Admin APIs]

How authentication works on each surface:

  • Browser UI uses session-based login and role-guarded routes.
  • CLI and join scripts use OAuth device authorization with DPoP-bound tokens.
  • Join and signing operations require both the right token scope and a valid request proof.

Device Authorization Endpoints

These endpoints support CLI and script authorization flows.

POST /oauth2/device_authorization

Starts device authorization and returns the information needed to approve the request in a browser.

Request parameters (application/x-www-form-urlencoded):

Parameter Required Type Description
client_id Yes string OAuth client identifier, for example sshteam-cli.
scope No string Requested scope: signing or join. Defaults to join.
device_name No string Optional display name for signing-scope device records.

Example success response (200):

{
    "device_code": "dvc_2f9f6f3d7f224d3d9b9e",
    "user_code": "ABCD-EFGH",
    "verification_uri": "https://<hostname>/device/verify",
    "verification_uri_complete": "https://<hostname>/device/verify?user_code=ABCD-EFGH&device_code=dvc_2f9f6f3d7f224d3d9b9e",
    "expires_in": 600,
    "interval": 5
}

POST /oauth2/token

Exchanges an approved device code for tokens, or refreshes an existing token.

Request parameters (application/x-www-form-urlencoded):

Parameter Required Type Description
grant_type Yes string Device flow: urn:ietf:params:oauth:grant-type:device_code; refresh flow: refresh_token.
device_code Conditional string Required for device-code exchange.
refresh_token Conditional string Required for refresh-token exchange.
client_id Conditional string Required for device-code exchange; not used by refresh-token exchange.
DPoP (header) Conditional string Required for signing scope token exchange and refresh grant handling.

Example success response (200):

{
    "access_token": "eyJ...",
    "refresh_token": "eyJ...",
    "token_type": "DPoP or Bearer",
    "expires_in": 86400,
    "scope": "signing or join"
}

Response notes:

  • token_type is DPoP for signing-scope flows.
  • token_type is Bearer for join-scope flows.

Example pending response (400):

{
    "error": "authorization_pending",
    "error_description": "Authorization request is still pending"
}

Join and Signing Endpoints

POST /api/v1/ssh/join

Registers a server and returns CA trust material the server can use to validate user certificates.

Headers:

Header Required Description
Authorization Yes Bearer <access-token> with join scope.
Content-Type Yes application/json.

Request body fields:

Field Required Type Description
hostname Yes string Target server hostname or address.
port No number SSH service port. Defaults to 22 if omitted.
osFamily Yes string Target OS family, for example linux or windows.
osVersion Yes string Target OS version string.
sshFingerprints No array Host-key fingerprints collected from the target server.
name No string Friendly name shown in the administration UI.
tags No array Tags used for policy targeting.
timezone No string Optional server timezone.
serverId No string Existing server ID for update-path joins.

Example success response (200, text/plain):

# TrustedUserCAKeys
ssh-rsa AAAA...
ssh-ed25519 AAAA...
# sshteam-server-id: srv_a18c0c2e

POST /api/v1/ssh/sign

Submits a certificate signing request from CLI flows (sign, issue, agent).

Headers:

Header Required Description
Authorization Yes DPoP <access-token> with signing scope.
DPoP Yes Proof JWT bound to token and request URL/method.
Content-Type Yes application/json.

Request body fields:

Field Required Type Description
principal Yes string UNIX account requested in the certificate.
server_fingerprint Yes string Target server host-key fingerprint.
public_key Yes string OpenSSH-formatted public key.
certificate_type No string ED25519 or RSA. Defaults to ED25519.
timezone No string IANA timezone override for policy window evaluation.

Example success response (200):

{
    "certificate": "ssh-ed25519-cert-v01@openssh.com AAAA..."
}

Example deny response (403):

{
    "error": "No certificate policy permits this request"
}

GET /api/v1/devices

Lists registered devices for the current user.

Headers:

Header Required Description
Session cookie Yes Authenticated browser session for current user.

Example success response (200):

[
    {
        "id": "dev_77f7a",
        "displayName": "macbook-pro",
        "createdAt": "2026-07-15T21:03:11Z",
        "lastSeenAt": "2026-07-16T05:44:42Z",
        "revoked": false
    }
]

DELETE /api/v1/devices/{deviceId}

Revokes an existing device registration.

  • Revocation takes effect immediately.
  • Repeated revoke requests are treated idempotently.

Path and headers:

Name Required Type Description
deviceId (path) Yes string Device identifier returned by the device list endpoint.
Session cookie Yes string Authenticated browser session for current user.

Example success response (204, no response body).

Error Codes and Expected HTTP Statuses

Use this table as a baseline. Exact payload fields may vary by endpoint.

Scenario Typical HTTP status Notes
Successful device authorization start 200 Returns device/user codes and verification URI
Device polling pending approval 400 OAuth error body, often authorization_pending
Device polling denied 400 OAuth error body, often access_denied
Device code expired 400 OAuth error body, often expired_token
Missing or invalid session for protected UI route 302 or 401/403 Redirect to /login or auth failure
Invalid token or DPoP proof on protected API 401 Token invalid, expired, revoked, or proof mismatch
Authenticated but not authorized for action 403 Role or policy boundary denies action
Policy denial on sign request 403 Request valid but does not match allow policy
Resource conflict on optimistic lock update 409 Typical for concurrent policy updates
Validation error in request payload 400 Field format, required fields, or logical constraints fail

How to read common status codes:

  • 401 means an authentication or proof problem.
  • 403 means a permission or policy boundary problem.
  • 409 means a concurrency or version conflict.

Example Payloads and Curl Snippets

These snippets are for reference and controlled testing.

Start device authorization:

curl -sS -X POST "https://<hostname>/oauth2/device_authorization" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data "client_id=sshteam-cli&scope=signing"

Poll the token endpoint:

curl -sS -X POST "https://<hostname>/oauth2/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data "grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<device_code>&client_id=sshteam-cli"

Sign request with DPoP headers (illustrative):

curl -sS -X POST "https://<hostname>/api/v1/ssh/sign" \
    -H "Authorization: DPoP <access_token>" \
    -H "DPoP: <proof_jwt>" \
    -H "Content-Type: application/json" \
    -d '{
        "principal": "devops",
        "server_fingerprint": "SHA256:example",
        "public_key": "ssh-ed25519 AAAA...",
        "certificate_type": "ED25519"
    }'

Revoke a device:

curl -sS -X DELETE "https://<hostname>/api/v1/devices/<deviceId>" \
    -H "Cookie: JSESSIONID=<session_id>"

Treat these examples as contract guidance. Field names and optional attributes may vary with your deployed version, so check before building automation around them.

Pro Tip and Common Gotcha

Pro tip:

  • For everyday work, use the sshteam CLI commands first. Reach for these API calls only when you need deeper diagnostics or custom integrations.

Common gotcha:

  • Join and signing endpoints use different auth models (Bearer vs DPoP). Mixing those headers produces confusing failures that look like generic auth errors.