Treeship
Hub API

POST /v1/hub/authorize

Complete the device flow and register hub keys with Hub.

This endpoint has two calling modes depending on whether keys are provided. The browser calls it after the user approves the device code. The CLI calls it with keys to complete hub connection creation.

Browser-only call (no keys)

When the browser confirms approval without providing Treeship/hub keys, Hub marks the challenge as approved and returns a status. No hub connection or Treeship record is created yet.

Request

POST /v1/hub/authorize
Content-Type: application/json
{
  "device_code": "dvc_a1b2c3d4"
}
FieldTypeRequiredDescription
device_codestringYesThe device code from the challenge

Response

{
  "state": "approved",
  "status": "approved"
}
FieldTypeDescription
statestringDevice-flow state. "approved" once the browser approves.
statusstringLegacy alias for state, kept for older clients.

No hub_id is returned. The browser's job is just to approve -- the CLI completes registration in the next step.

CLI call (with keys)

When the CLI calls this endpoint with Treeship and hub public keys, Hub verifies the challenge is already browser-approved, validates the nonce, atomically claims the challenge for a new dock_id, creates the Treeship record, and returns it. The claim is single-use: a concurrent or replayed finalize is rejected as already_attached.

The challenge must already be approved by the browser before the CLI calls this endpoint. The CLI must also provide the nonce that was returned by GET /v1/hub/challenge.

Request

POST /v1/hub/authorize
Content-Type: application/json
{
  "device_code": "dvc_a1b2c3d4",
  "ship_public_key": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29",
  "hub_public_key": "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
  "nonce": "e4f8a2c1b7d9306f5e1a9c4b8d7f2e0a"
}
FieldTypeRequiredDescription
device_codestringYesThe device code from the challenge
ship_public_keystringYesHex-encoded Ed25519 public key used for signing artifacts
hub_public_keystringYesHex-encoded Ed25519 public key used for DPoP authentication
noncestringYesThe nonce returned by GET /v1/hub/challenge -- must match exactly

Response

{
  "state": "attached",
  "dock_id": "hub_9f8e7d6c",
  "next_steps": [
    "treeship status",
    "treeship attest receipt --system system://<your-system> --kind <kind> --payload-file <file>",
    "treeship hub push <artifact-id>",
    "treeship verify <artifact-id>"
  ],
  "example": "treeship attest receipt --system system://zmem --kind memory.proof --payload-file proof.json"
}
FieldTypeDescription
statestring"attached" on success.
dock_idstringUnique identifier for this hub connection (format: dck_/hub_ + 32 hex chars)
next_stepsstring[]Provider-neutral command templates to surface on the success page.
examplestringOne illustrative invocation. The placeholders are real; system://zmem and memory.proof are an example customer's memory-proof workflow, not built-in behavior. --system and --kind accept any value.

The challenge is claimed, not deleted, on success. The row is retained (with its dock_id) until well past expiry so a still-polling activation page reports attached rather than a misleading not found.

Errors

Every state-bearing response carries a stable state field so the activation page can distinguish outcomes precisely.

StatusBodyStateCause
400{"error": "missing device_code"}--Request body is missing the device_code field
400{"error": "missing nonce -- required for dock finalization"}--CLI sent keys but omitted the nonce field
400{"error": "invalid ship_public_key hex"}--ship_public_key is not valid hex
400{"error": "invalid dock_public_key hex"}--dock_public_key is not valid hex
403{"state": "pending", "error": "challenge not yet approved -- complete browser activation first"}pendingKeys were sent but the browser has not approved the challenge yet
403{"error": "nonce mismatch"}--The nonce does not match the one stored with the challenge
404{"state": "invalid", "error": "device_code not found"}invalidNo challenge exists for this device code
409{"state": "already_attached", "error": "device code already used"}already_attachedThis challenge was already finalized (prevents double-registration)
410{"state": "expired", "error": "device_code expired"}expiredThe challenge has passed its 5-minute expiry window

Examples

Browser approval (no keys)

curl -X POST https://api.treeship.dev/v1/hub/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "device_code": "dvc_a1b2c3d4"
  }'

CLI registration (with keys)

curl -X POST https://api.treeship.dev/v1/hub/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "device_code": "dvc_a1b2c3d4",
    "ship_public_key": "3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29",
    "hub_public_key": "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025",
    "nonce": "e4f8a2c1b7d9306f5e1a9c4b8d7f2e0a"
  }'

Notes

  • The Treeship public key and hub public key must be different Ed25519 keys
  • The Treeship key is stored for identification only. Hub never receives any private key
  • The hub key is used to verify DPoP JWT signatures on subsequent API requests
  • Each device code can only be authorized once -- the challenge is atomically claimed (by dock_id) on success, and the row is kept so the activation page can still poll and read attached
  • If the ship_public_key already exists on Hub, the existing Treeship record is reused and a new hub connection is attached to it
  • The nonce field prevents replay attacks and binds the CLI request to the specific challenge session