pakx

docs · api reference

Registry HTTP API

Stable HTTP surface at https://registry.pakx.dev. JSON everywhere; bearer tokens for write paths; session cookies for dashboard CRUD. Every response carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset headers (and Retry-After on 429s).

Authentication

Two flavours:

  • Bearer token for CLI workflows. Issue from https://registry.pakx.dev/dashboard/tokens. Pass as Authorization: Bearer pakx_v1_…. Plaintext shown once; we store a sha256 hash.
  • Session cookie for dashboard routes. Set by Auth.js after GitHub OAuth. Strictly necessary; never used for tracking.

Error shape

Every non-2xx body is JSON: { "error": "<kebab-case>" }. Common codes: unauthorized, forbidden, not-found, invalid, too-many-requests, user-deleted.

Endpoints

GET/api/v1/health

public

Liveness + DB readiness probe. 200 if process up AND DB reachable, 503 otherwise. Used by /status on pakx.dev and external uptime checkers.

rate: 120 burst / 2 req per sec per IP

Response body
{
  "status": "ok" | "degraded",
  "uptimeSec": 4203,
  "version": "abc1234",
  "checks": {
    "db": { "ok": true, "latencyMs": 12 }
  }
}

GET/api/v1/packages?q=<query>&limit=<n>

public

Search the registry. Empty `q` returns the first page sorted by recency.

rate: 60 burst / 1 req per sec per IP

Response body
{
  "packages": [
    {
      "id": "owner/name",
      "kind": "skill" | "mcp" | "subagent" | ...,
      "description": "...",
      "latestVersion": "1.2.3"
    }
  ]
}

GET/api/v1/packages/{owner}/{name}

public

Full package metadata and every non-deprecated version (newest first).

rate: 120 burst / 2 req per sec per IP

Response body
{
  "id": "owner/name",
  "kind": "skill",
  "description": "...",
  "createdAt": "2026-05-21T...",
  "versions": [
    { "version": "1.2.3", "sha256": "...", "sizeBytes": 4096, "publishedAt": "..." }
  ]
}

POST/api/v1/packages

bearer token

Register (or look up) a package. Step 1 of the publish flow; tarball upload follows on the version route.

rate: 20 burst / 0.1 req per sec per authed user

Request body
{
  "name": "kebab-or-snake-or-dot-case",
  "kind": "skill" | "mcp" | "subagent" | "prompt" | "command" | "hook",
  "description": "(optional, max 1024 chars)"
}
Response body
{
  "id": "<nanoid>",
  "owner": "<github-login>",
  "name": "...",
  "kind": "...",
  "created": true | false
}

PUT/api/v1/packages/{owner}/{name}/{version}

bearer token

Upload the version tarball. Server validates Content-Length, content type, sha256, then stores in Vercel Blob and inserts a `versions` row.

Request body
gzipped tar (application/gzip)
Response body
{
  "id": "<version-nanoid>",
  "version": "1.2.3",
  "sha256": "...",
  "sizeBytes": 1234,
  "tarballKey": "..."
}

DELETE/api/v1/packages/{owner}/{name}/{version}

bearer token

Soft-delete a version. Sets `deprecated_at`; the row stays so that `pakx install` pins on existing manifests keep resolving. Hard delete only via direct DB intervention.

Response body
{ "deprecated": true }

GET/api/v1/whoami

bearer token

Identify the user owning the bearer token. The CLI hits this on `pakx login` and `pakx whoami`.

rate: 30 burst / 0.5 req per sec per IP (pre-auth); 120 burst / 2 req per sec per user (post-auth)

Response body
{
  "id": "<user-nanoid>",
  "login": "<github-login>",
  "email": "user@example.com"
}

GET/api/v1/me/export

bearer token

GDPR-style data export. JSON download of every row tied to the caller — user row, hashed tokens, owned packages with versions, Stripe pointer. Plaintext tokens are NOT returned (we don't keep them). Tarball contents are NOT inlined; metadata + blob key only.

rate: 5 burst / 1 req per 30 sec per user

Response body
{
  "schema":     "https://registry.pakx.dev/schemas/me-export-v1.json",
  "exportedAt": "2026-05-21T...",
  "user":       { "id": "...", "githubLogin": "...", "email": "...", ... },
  "tokens":     [{ "id": "...", "label": "...", "hashedKey": "<sha256>", ... }],
  "packages":   [{ "id": "...", "kind": "...", "versions": [{ "version": "...", "sha256": "..." }] }],
  "stripe":     { "stripeAccountId": "..." } | null
}

POST/api/v1/me/delete

bearer token

Right-to-erasure request. Revokes every active token immediately (including the one used for this request) and writes a deletion marker. Hard delete happens after a 30-day grace window — email security@pakx.dev to undo within that window.

rate: 3 burst / 3 req per day per user

Request body
{ "confirm": "<your-github-login>" }
Response body
{
  "requested": true,
  "user": "<github-login>",
  "tokensRevoked": true,
  "graceDays": 30,
  "scheduledFor": "2026-06-20T...",
  "hint": "Contact security@pakx.dev within the grace window to undo."
}

GET/api/v1/tokens

session cookie

List API tokens issued by the signed-in user. Hashes only — plaintext is shown once at issue and never persisted.

Response body
{
  "tokens": [
    { "id": "...", "label": "laptop", "lastUsedAt": "...", "createdAt": "..." }
  ]
}

POST/api/v1/tokens

session cookie

Issue a new API token. The response body is the only time the plaintext key is returned.

Request body
{ "label": "laptop" }
Response body
{
  "id": "...",
  "label": "laptop",
  "key": "pakx_v1_<base62>",
  "createdAt": "..."
}

DELETE/api/v1/tokens/{id}

session cookie

Revoke an API token. Subsequent requests presenting the plaintext key fail with 401.

Response body
{ "revoked": true }

See also: the federated reader covering MCP Registry + Smithery + this registry lives in crates/pakx-registry-client; the equivalent TS port that powers /explore lives at src/lib/registry.ts. Both stay in sync with the API documented above.