Skip to content
SKILLS
Documentation menu

Reference

API reference

REST API overview — authentication, scopes, errors, pagination, caching, idempotency, rate limits and the upload flow.

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

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 "$B/artifacts/<ns>/<slug>/history"

latest is accepted in place of a version.

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:

{ "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

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

ScopeAllows
artifacts:readRead artifacts in allowed namespaces, including non-public ones
artifacts:createCreate artifacts in allowed namespaces
artifacts:updateUpdate summary, description, links, keywords, visibility
artifacts:deprecateDeprecate, undeprecate, archive
uploads:createBegin uploads and send archive bytes
releases:publishFinalize an upload into an immutable release
releases:updateEdit release notes only
releases:revokeRevoke a release
tokens:readList your tokens (metadata only)
tokens:writeCreate or revoke tokens, never broader than the caller
audit:readRead your audit trail
admin:moderate, admin:settingsElevated; 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

{
  "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.

StatusTypical codes
400bad_request
401unauthorized, invalid_token, token_expired, token_revoked, invalid_credentials
403insufficient_scope, namespace_forbidden, reauth_required, admin_required, csrf
404not_found (also returned for private artifacts you cannot see)
405method_not_allowed
409version_exists, artifact_exists, release_immutable, upload_state, conflict
410release_revoked, taken_down, upload_expired
413payload_too_large
415unsupported_media_type, not_previewable
422release_rejected (with details.findings), checksum_mismatch, idempotency_key_reused
428confirmation_required
429rate_limited (with Retry-After)
500internal_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 requestLimit
Anonymous reads240 per minute
Search90 per minute
Downloads120 per minute
Authenticated calls120 per minute
Upload begin / upload bytes30 per hour
Finalize (publish)30 per hour
Abuse reports5 per hour
Sign-in attempts8 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).

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

RequestEffect
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

RequestNotes
GET /meIdentity, credential scopes and namespaces
GET /me/tokensMetadata only
POST /me/tokens{ name, scopes, namespaces?, expiresInHours?, ipAllowlist? } → the token, once
DELETE /me/tokens/{id}Revoke
POST /me/tokens/{id}/rotateReplacement token, once; the old one is revoked
GET /me/auditYour actions: before (cursor), limit, action (prefix)
POST /validate-manifestChecks a manifest object against the schema without uploading

Reporting

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.

↑↓ to navigate · enter to open · esc to close