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"
}| Field | Type | Required | Description |
|---|---|---|---|
device_code | string | Yes | The device code from the challenge |
Response
{
"status": "approved"
}| Field | Type | Description |
|---|---|---|
status | string | Approval status. Always "approved" on success. |
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 consumes the challenge, creates the Treeship record (if new), and returns a hub_id.
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"
}| Field | Type | Required | Description |
|---|---|---|---|
device_code | string | Yes | The device code from the challenge |
ship_public_key | string | Yes | Hex-encoded Ed25519 public key used for signing artifacts |
hub_public_key | string | Yes | Hex-encoded Ed25519 public key used for DPoP authentication |
nonce | string | Yes | The nonce returned by GET /v1/hub/challenge -- must match exactly |
Response
{
"hub_id": "hub_9f8e7d6c"
}| Field | Type | Description |
|---|---|---|
hub_id | string | Unique identifier for this hub connection (format: hub_ + 32 hex chars) |
Errors
| Status | Body | Cause |
|---|---|---|
400 | {"error": "missing device_code"} | Request body is missing the device_code field |
400 | {"error": "missing nonce -- required for hub 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 hub_public_key hex"} | hub_public_key is not valid hex |
403 | {"error": "challenge not yet approved -- complete browser activation first"} | Keys 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 | {"error": "device_code not found"} | No challenge exists for this device code |
409 | {"error": "challenge already consumed"} | Another request already consumed this challenge (prevents double-registration) |
410 | {"error": "device_code expired"} | The 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 nonce is atomically consumed on success
- If the
ship_public_keyalready exists on Hub, the existing Treeship record is reused and a new hub connection is attached to it - The
noncefield prevents replay attacks and binds the CLI request to the specific challenge session