HydraCore
API by router

Bootstrap

Download Artifact

Stream encrypted artifact bundle.

Requires:

  • mTLS client cert matching instance
  • X-Download-Token header (from /secrets)

Download token allows 3 attempts, consumed on successful transfer.

Args: instance_id: Instance UUID request: FastAPI request (for mTLS cert extraction) db: Database session x_download_token: Download token from /secrets (64 hex chars)

Returns: StreamingResponse with encrypted bundle (application/octet-stream)

Errors: 401: Invalid mTLS cert or download token 404: Instance or artifact not found 429: Too many download attempts

GET
/internal/bootstrap/artifacts/{instance_id}

Path Parameters

instance_idInstance Id
Formatuuid

Header Parameters

x-download-token?X-Download-Token

Response Body

curl -X GET "https://loading/internal/bootstrap/artifacts/497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "x-download-token: string"
null
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Download Website Bundle

Download website bundle for an instance (unencrypted static assets).

Same auth as agent artifact: mTLS cert + download token. Returns the website tarball from R2 if one was provisioned. No-op 404 if no website was deployed with this instance.

"It's just a flesh wound." — The Black Knight, Monty Python (websites are optional — no wound if missing)

GET
/internal/bootstrap/artifacts/{instance_id}/website

Path Parameters

instance_idInstance Id
Formatuuid

Header Parameters

x-download-token?X-Download-Token

Response Body

curl -X GET "https://loading/internal/bootstrap/artifacts/497f6eca-6276-4993-bfeb-53cbbbba6f08/website" \  -H "x-download-token: string"
null
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Receive Callback

Receive bootstrap callback from agent.

Validates callback JWT and enforces strict monotonic sequence. Sequence: installing(1) → configuring(2) → securing(3) → ready(4) Error can occur at seq 2 or 3 only.

Args: request: Stage + seq + optional message db: Database session authorization: Bearer {callback_jwt}

Returns: status: "ok" message: Confirmation message

Errors: 401: Invalid or expired JWT 400: Invalid stage/seq or sequence violation 404: Instance or callback state not found

POST
/internal/bootstrap/callback

Header Parameters

authorization?Authorization
message?Message
seqSeq
Range1 <= value <= 4
stageStage
Match^(installing|configuring|securing|ready|error)$

Response Body

curl -X POST "https://loading/internal/bootstrap/callback" \  -H "authorization: string" \  -H "Content-Type: application/json" \  -d '{    "seq": 1,    "stage": "string"  }'
{
  "message": "string",
  "status": "string"
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Issue Certificate

Issue mTLS certificate from bootstrap token and CSR.

Two-step bootstrap: This is step 1 (bootstrap token → mTLS cert). Step 2 is /secrets (mTLS → secrets + callback JWT).

NOTE: Bootstrap token is NOT consumed here (consumed in /secrets after mTLS).

Args: request: Bootstrap token + CSR PEM db: Database session

Returns: cert_pem: PEM-encoded mTLS certificate session_id: Bootstrap session UUID for /secrets call

Errors: 401: Invalid, expired, or consumed token 400: Invalid CSR 500: CA error

POST
/internal/bootstrap/cert
bootstrap_tokenBootstrap Token
Match^[0-9a-f]{64}$
Length64 <= length <= 64
csr_pemCsr Pem
Length100 <= length <= 10000

Response Body

curl -X POST "https://loading/internal/bootstrap/cert" \  -H "Content-Type: application/json" \  -d '{    "bootstrap_token": "stringstringstringstringstringstringstringstringstringstringstri",    "csr_pem": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstri"  }'
{
  "cert_pem": "string",
  "session_id": "string"
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Receive Diagnostic

Free-form trace breadcrumbs from bootstrap-agent.sh.

Same JWT auth as /callback (the callback_jwt issued at /secrets is reused). No monotonic-sequence enforcement — every accepted request appends a row to provision_logs with the given label as stage and the message body as message. Designed for shipping "where am I" traces during fault diagnosis without disturbing the production callback state machine.

Why a separate endpoint: The strict /callback handler rejects out-of-sequence stage+ seq combos with 400, which the shell helper's || true silently swallows — so trace breadcrumbs aimed at /callback never reached provision_logs. This endpoint accepts anything JWT-valid + label + message.

POST
/internal/bootstrap/diagnostic

Header Parameters

authorization?Authorization
labelLabel
Length1 <= length <= 80
level?Level
Default"info"
Match^(info|warn|error)$
messageMessage
Lengthlength <= 2000

Response Body

curl -X POST "https://loading/internal/bootstrap/diagnostic" \  -H "authorization: string" \  -H "Content-Type: application/json" \  -d '{    "label": "string",    "message": "string"  }'
{
  "message": "string",
  "status": "string"
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Receive Early Diagnostic

Pre-mTLS bootstrap trace logging.

The strict /callback endpoint and the JWT-protected /diagnostic endpoint both require artefacts that bootstrap-agent.sh only has AFTER Phase 1 (cert exchange) + Phase 2 (secrets fetch + JWT). When bootstrap hangs in Phase 0/1/2 we get nothing back.

This endpoint authenticates against the instance's bootstrap_token (a one-shot per-instance secret already known to the script from cloud-init Jinja templating). Token is hashed and compared in constant time against the stored bootstrap_tokens row; we do NOT consume it (consumption stays bound to /secrets).

Writes the message to provision_logs. Same effect as /diagnostic but available before mTLS is up.

POST
/internal/bootstrap/early-diagnostic
bootstrap_tokenBootstrap Token
Length10 <= length <= 200
instance_idInstance Id
Length1 <= length <= 80
labelLabel
Length1 <= length <= 80
level?Level
Default"info"
Match^(info|warn|error)$
messageMessage
Lengthlength <= 2000

Response Body

curl -X POST "https://loading/internal/bootstrap/early-diagnostic" \  -H "Content-Type: application/json" \  -d '{    "bootstrap_token": "stringstri",    "instance_id": "string",    "label": "string",    "message": "string"  }'
{
  "message": "string",
  "status": "string"
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}

Get Secrets

Provide decryption secrets after mTLS verification.

Two-step bootstrap: This is step 2 (mTLS → secrets + callback JWT). Requires client cert from step 1 (/cert).

Bootstrap token is consumed HERE (after mTLS verification succeeds). Callback JWT is created here. Download token is created here (one-time use, cannot retry /secrets).

Args: request_body: Session ID from /cert request: FastAPI request (for mTLS cert extraction) db: Database session

Returns: callback_jwt: JWT for /callback endpoint artifact_download_token: Token for /artifacts (raw hex) decryption_key_hex: Bundle decryption key (64 hex chars) artifact_cipher_hash: Expected ciphertext hash (sha256:...) bundle_plain_hash: Expected plaintext hash (sha256:...) agent_config: Agent configuration (non-secret) agent_secrets: Agent secrets (API keys, etc.)

Errors: 401: Invalid mTLS cert or session 404: Session not found 410: Session expired or already complete

POST
/internal/bootstrap/secrets
session_idSession Id

Response Body

curl -X POST "https://loading/internal/bootstrap/secrets" \  -H "Content-Type: application/json" \  -d '{    "session_id": "string"  }'
{
  "agent_config": {},
  "agent_secrets": {},
  "artifact_cipher_hash": "string",
  "artifact_download_token": "string",
  "bundle_plain_hash": "string",
  "callback_jwt": "string",
  "decryption_key_hex": "string",
  "skill_lockfile": {}
}
{
  "detail": [
    {
      "loc": [
        "string"
      ],
      "msg": "string",
      "type": "string"
    }
  ]
}