SPB Git

Repository Public MIT

api.md 10.3 KB


# title: API reference description: REST API overview — authentication, scopes, errors, pagination, caching, idempotency, rate limits and the upload flow. order: 31 section: Reference

# API reference

Base URL: https://skills.spboucher.ai/api/v1

The complete, machine-readable description is the OpenAPI 3.1 document, generated from the server's route table:

/api/v1/openapi.json

Reading needs no account. Responses are JSON unless noted.

# Discovery

bash
B=https://skills.spboucher.ai/api/v1
curl -s "$B/artifacts?type=skill&compatibility=claude-code&sort=updated&per_page=10"
curl -s "$B/search?q=citation+check"
curl -s "$B/categories"                      # types with live counts, licences, keywords, totals
curl -s "$B/collections"
curl -s "$B/artifacts/<ns>/<slug>"            # metadata + latest release
curl -s "$B/artifacts/<ns>/<slug>/versions"
curl -s "$B/artifacts/<ns>/<slug>/versions/1.2.0"                 # manifest, files, dependencies, compatibility, scan
curl -s "$B/artifacts/<ns>/<slug>/versions/1.2.0/files?path=SKILL.md"
curl -s "$B/artifacts/<ns>/<slug>/versions/1.2.0/install?client=claude-code"
curl -s "$B/artifacts/<ns>/<slug>/versions/1.2.0/checksum"        # text: "<sha256>  <slug>-1.2.0.tar.gz"
curl -fsSLO "$B/artifacts/<ns>/<slug>/versions/1.2.0/download"    # application/gzip
curl -s -o shot.png "$B/artifacts/<ns>/<slug>/versions/1.2.0/media/examples/shot.png"   # a screenshot listed in the manifest `examples`
curl -s "$B/artifacts/<ns>/<slug>/history"

latest is accepted in place of a version.

Every release object carries examples: the screenshots the publisher listed in the manifest, each with the url the registry serves it from (release detail adds width, height, sizeBytes and mime). The /media/ route serves only those listed paths, as image/png, image/jpeg or image/webp, with X-Content-Type-Options: nosniff and Content-Security-Policy: default-src 'none'; any other path of the archive answers 404, and a revoked release answers 410.

Filters on /artifacts: q, type, keyword, compatibility, license, namespace, capability, sort (relevance, updated, created, name, downloads), page, per_page.

Search uses PostgreSQL full-text search with prefix matching, plus trigram matching as a typo fallback for queries of five characters or more. There is no semantic search.

# Pagination

List responses have this shape:

json
{ "items": [], "page": 1, "perPage": 24, "total": 0, "totalPages": 1 }

page starts at 1. per_page is 1–100, default 24. Ordering is stable: every sort ends with a unique tie-breaker.

# Authentication

http
Authorization: Bearer skr_<redacted>

Tokens are created in the dashboard, with the CLI, or by the operator. A token carries scopes, a list of namespaces, an expiry and optionally an IP allowlist. It is shown once and cannot be retrieved later.

Never put a token in a URL. Authenticated responses are sent with Cache-Control: private, no-store.

The dashboard uses a session cookie instead; cookie-authenticated writes need a same-origin request and a CSRF token, so scripts should use bearer tokens.

# Scopes

Scope Allows
artifacts:read Read artifacts in allowed namespaces, including non-public ones
artifacts:create Create artifacts in allowed namespaces
artifacts:update Update summary, description, links, keywords, visibility
artifacts:deprecate Deprecate, undeprecate, archive
uploads:create Begin uploads and send archive bytes
releases:publish Finalize an upload into an immutable release
releases:update Edit release notes only
releases:revoke Revoke a release
tokens:read List your tokens (metadata only)
tokens:write Create or revoke tokens, never broader than the caller
audit:read Read your audit trail
admin:moderate, admin:settings Elevated; platform administrators only, tokens limited to 24 hours

A write also requires that you own the namespace and, for a token, that the namespace is on the token.

# Errors

json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This token lacks the \"artifacts:create\" scope.",
    "details": { "required": "artifacts:create" },
    "requestId": "0b9c…"
  }
}

Every response carries X-Request-Id. Quote it when reporting a problem.

Status Typical codes
400 bad_request
401 unauthorized, invalid_token, token_expired, token_revoked, invalid_credentials
403 insufficient_scope, namespace_forbidden, reauth_required, admin_required, csrf
404 not_found (also returned for private artifacts you cannot see)
405 method_not_allowed
409 version_exists, artifact_exists, release_immutable, upload_state, conflict
410 release_revoked, taken_down, upload_expired
413 payload_too_large
415 unsupported_media_type, not_previewable
422 release_rejected (with details.findings), checksum_mismatch, idempotency_key_reused
428 confirmation_required
429 rate_limited (with Retry-After)
500 internal_error (no internals are disclosed; use the request id)

# Caching and ETags

  • Anonymous JSON reads: Cache-Control: public, max-age=30 and a weak ETag. Send If-None-Match to get 304.
  • Downloads of public releases: Cache-Control: public, max-age=31536000, immutable, ETag: "sha256-<hex>", X-Checksum-Sha256, Repr-Digest.
  • Cross-origin GET, HEAD and OPTIONS are allowed from any origin. Writes are not available cross-origin from browsers.

# Rate limits

Limits are per token, else per signed-in user, else per IP address. Responses include RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset.

Kind of request Limit
Anonymous reads 240 per minute
Example images (/media/) 600 per minute
Search 90 per minute
Downloads 120 per minute
Authenticated calls 120 per minute
Upload begin / upload bytes 30 per hour
Finalize (publish) 30 per hour
Abuse reports 5 per hour
Sign-in attempts 8 per 15 minutes

Twenty failed bearer authentications from one address within ten minutes block further bearer authentication from that address until the window ends.

# Idempotency

POST /artifacts, POST /artifacts/{id}/releases and POST /uploads/{id}/finalize accept an Idempotency-Key header (8–128 characters: letters, digits, _ . : -).

  • Repeating a request with the same key and body returns the stored response with Idempotent-Replayed: true.
  • Reusing a key with a different body returns 422 idempotency_key_reused.
  • Finalize is also naturally idempotent: finalizing an already published upload returns the same release with status 200.

# Publishing flow

Three steps after the artifact exists. The archive is a deterministic .tar.gz (the CLI's skills pack produces it).

bash
B=https://skills.spboucher.ai/api/v1
AUTH="Authorization: Bearer $SKILLS_TOKEN"        # set in this shell only
F=citation-check-1.0.0.tar.gz
SHA=$(shasum -a 256 "$F" | cut -d' ' -f1); SIZE=$(wc -c < "$F" | tr -d ' ')

# 0. create the artifact (once). Private by default; public needs confirmPublic.
curl -s -X POST "$B/artifacts" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"namespace":"yourns","slug":"citation-check","type":"skill","summary":"Checks that every citation resolves to a reference.","visibility":"public","confirmPublic":true}'
ID=$(curl -s "$B/artifacts/yourns/citation-check" -H "$AUTH" | jq -r .id)

# 1. begin: declare version, checksum and size
UP=$(curl -s -X POST "$B/artifacts/$ID/releases" -H "$AUTH" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: begin-$SHA" \
  -d "{\"version\":\"1.0.0\",\"sha256\":\"$SHA\",\"sizeBytes\":$SIZE,\"releaseNotes\":\"Initial release.\"}" | jq -r .uploadId)

# 2. send the bytes
curl -s -X PUT "$B/uploads/$UP" -H "$AUTH" -H 'Content-Type: application/gzip' --data-binary "@$F"

# 3. finalize: validate, scan, publish
curl -s -X POST "$B/uploads/$UP/finalize" -H "$AUTH" -H 'Content-Type: application/json' -d "{\"sha256\":\"$SHA\"}"
  • An upload session expires after 60 minutes and belongs to the user who began it.
  • The bytes must match the declared size and SHA-256 exactly, or nothing is stored (422 checksum_mismatch).
  • Finalize returns 201 with release.state equal to published or quarantined, plus the list of findings. A rejected release returns 422 release_rejected with the blocking findings; fix and upload again under the same version.
  • GET /uploads/{id} returns the session state.

# Changing things afterwards

Request Effect
PATCH /artifacts/{id} summary, description, repository, homepage, issues, keywords, visibility (+ confirmPublic when exposing). Namespace, slug and type cannot change
PATCH /releases/{id} releaseNotes only. Anything else returns 409 release_immutable
POST /artifacts/{id}/deprecate { "reason": "…", "confirm": true }
POST /artifacts/{id}/undeprecate —
POST /artifacts/{id}/archive { "confirm": true }
POST /releases/{id}/revoke { "reason": "…", "confirm": true } — permanent

# Account

Request Notes
GET /me Identity, credential scopes and namespaces
GET /me/tokens Metadata only
POST /me/tokens { name, scopes, namespaces?, expiresInHours?, ipAllowlist? } → the token, once
DELETE /me/tokens/{id} Revoke
POST /me/tokens/{id}/rotate Replacement token, once; the old one is revoked
GET /me/audit Your actions: before (cursor), limit, action (prefix)
POST /validate-manifest Checks a manifest object against the schema without uploading

# Reporting

bash
curl -s -X POST "$B/reports" -H 'Content-Type: application/json' \
  -d '{"artifact":"@ns/slug","version":"1.2.0","category":"malware","details":"At least twenty characters describing the problem.","contact":"optional@example.org"}'

Categories: malware, secret-leak, license, impersonation, spam, broken, other. No account needed.

# Health

GET /health/live and GET /health/ready (database and storage).

# SDK

A typed TypeScript client lives in the repository (packages/sdk, class RegistryClient). It is not yet published to npm.

Files main
Menu

Theme