Skip to content
AI/LLM: This documentation page is available in plain markdown format at /docs/api.md

Overview

The Tank REST API gives you programmatic access to every capability in the registry: searching skills, publishing new versions, managing stars, triggering security scans, and the full CLI OAuth flow. All endpoints live under a single base URL and follow consistent JSON conventions.

Base URL

https://tankpkg.dev/api/v1

For self-hosted deployments, replace the host with your own domain. The /api/v1 prefix is always required.


Authentication

Authenticated endpoints require a Bearer token in the Authorization header.

Authorization: Bearer tank_xxxxxxxxxxxxxxxxxxxxxxxx

Obtaining a token

  • CLI: run tank login — opens GitHub OAuth in your browser, then stores the issued key in ~/.tank/config.json automatically.
  • Dashboard: go to Settings → Tokens and create a token with the scopes you need.

Token prefix

All Tank API keys begin with tank_. Tokens without this prefix are rejected with 401.

Scopes

ScopeGrants
skills:readRead skill metadata, versions, files, stars
skills:publishPublish new skills and new versions
skills:adminAdministrative actions (moderation, user management)
Tokens inherit the minimum scope needed. A token with only `skills:read` cannot publish; you will receive a `403` if you try.

Rate Limits

Rate limits are applied per IP for anonymous requests and per token for authenticated requests.

TierRequests / hour
Anonymous100
Authenticated1,000
Pro10,000

When you exceed your limit, the API returns 429 Too Many Requests. The response includes a Retry-After header indicating how many seconds to wait before retrying.


Error Responses

All errors follow a consistent envelope. The error field contains a machine-readable code and the message field contains a human-readable explanation.

{
  "error": "UNAUTHORIZED",
  "message": "Bearer token is missing or invalid."
}

Common HTTP status codes

StatusError CodeWhen it occurs
401UNAUTHORIZEDToken missing, malformed, or revoked
403FORBIDDENToken valid but lacks the required scope
404NOT_FOUNDSkill, version, or resource does not exist
409VERSION_EXISTSYou attempted to publish an already-existing version
422VALIDATION_ERRORRequest body failed Zod schema validation
429RATE_LIMITEDHourly request limit exceeded

Validation error shape

When a 422 is returned, the issues array mirrors the Zod validation output so you can map errors back to specific fields.

{
  "error": "VALIDATION_ERROR",
  "message": "Request body validation failed.",
  "issues": [
    {
      "path": ["manifest", "version"],
      "message": "Invalid semver string."
    }
  ]
}

GET /api/v1/search

Full-text search across all public skills. Uses a hybrid strategy: ILIKE for exact prefix matching, pg_trgm trigram similarity for fuzzy matching, and a PostgreSQL tsvector full-text index with weighted ranking. Results are ranked by relevance score, then by download count as a tiebreaker.

Authentication: Not required. Private skills are hidden from anonymous results.

Query parameters

ParameterTypeDefaultDescription
qstringSearch query. Matched against skill name, description, and author.
pagenumber1Page number. Minimum 1.
limitnumber20Results per page. Range 150.

Response

{
  "results": [
    {
      "name": "@vercel/next-skill",
      "description": "Teaches agents to scaffold, build, and deploy Next.js apps.",
      "visibility": "public",
      "latestVersion": "2.3.1",
      "auditScore": 9,
      "publisher": "vercel",
      "downloads": 14820,
      "stars": 312,
      "updatedAt": "2026-03-01T11:00:00.000Z"
    }
  ],
  "page": 1,
  "limit": 20,
  "total": 142
}

Example

curl "https://tankpkg.dev/api/v1/search?q=seo+audit&limit=5"

Skills

POST /api/v1/skills — Publish a skill

Initiates skill publication. The response includes a pre-signed uploadUrl; you must PUT the tarball to that URL and then call POST /api/v1/skills/confirm to finalize.

Authentication: Required — skills:publish scope.

Request body

{
  "manifest": {
    "name": "@acme/my-skill",
    "version": "1.0.0",
    "description": "Does something useful for AI agents.",
    "visibility": "public",
    "permissions": {
      "network": { "outbound": ["*.acme.com"] },
      "filesystem": { "read": ["./src/**"] }
    },
    "repository": "https://github.com/acme/my-skill"
  },
  "readme": "# My Skill\nInstall with `tank install @acme/my-skill`.",
  "files": ["tank.json", "README.md", "index.md"]
}
FieldTypeRequiredDescription
manifest.namestringYesScoped or unscoped skill name. Scoped names (@org/name) require org membership.
manifest.versionstringYesSemver string. Must not already exist for this skill.
manifest.descriptionstringYesShort description shown in search results.
manifest.visibility"public" | "private"YesPublic skills are visible to everyone. Private skills require auth.
manifest.permissionsobjectYesDeclared permission budget. Validated against Zod schema.
manifest.repositorystringNoSource repository URL for provenance display.
readmestringNoMarkdown content for the skill's registry page.
filesstring[]NoList of file paths included in the tarball.

Validation rules

  • manifest.name must match /^(@[a-z0-9-]+\/)?[a-z0-9-]+$/
  • manifest.version must be a valid semver string
  • Scoped skill names require the authenticated user to be a member of that organization
  • Version conflict: publishing 1.0.0 when 1.0.0 already exists returns 409
  • Permission escalation: PATCH bumps with new permissions, or MINOR bumps adding dangerous permissions (network, subprocess), are rejected with 422

Response 201 Created

{
  "uploadUrl": "https://storage.tankpkg.dev/tarballs/abc123?token=...",
  "skillId": "skill_01HXYZ",
  "versionId": "ver_01HABC"
}

Example

curl -X POST https://tankpkg.dev/api/v1/skills \
  -H "Authorization: Bearer tank_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": {
      "name": "@acme/my-skill",
      "version": "1.0.0",
      "description": "Does something useful.",
      "visibility": "public",
      "permissions": {}
    }
  }'

GET /api/v1/skills/:name — Get skill metadata

Returns top-level metadata for a skill plus its latest published version.

Authentication: Not required for public skills. Private skills require a token with skills:read scope and membership in the owning organization.

Path parameters

ParameterDescription
nameSkill name, URL-encoded. Scoped names use @org%2Fskill or @org/skill (both accepted).

Response 200 OK

{
  "name": "@vercel/next-skill",
  "description": "Teaches agents to scaffold, build, and deploy Next.js apps.",
  "visibility": "public",
  "latestVersion": "2.3.1",
  "publisher": {
    "name": "vercel"
  },
  "createdAt": "2025-11-15T08:30:00.000Z",
  "updatedAt": "2026-03-01T11:00:00.000Z"
}

Example

curl https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill \
  -H "Authorization: Bearer tank_xxx"

GET /api/v1/skills/:name/versions — List all versions

Returns every published version for a skill, ordered newest first.

Authentication: Same access control as GET /api/v1/skills/:name.

Response 200 OK

{
  "name": "@vercel/next-skill",
  "versions": [
    {
      "version": "2.3.1",
      "publishedAt": "2026-03-01T11:00:00.000Z",
      "auditScore": 9,
      "verdict": "PASS"
    },
    {
      "version": "2.3.0",
      "publishedAt": "2026-02-15T09:22:00.000Z",
      "auditScore": 8,
      "verdict": "PASS"
    }
  ]
}

Example

curl https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill/versions

GET /api/v1/skills/:name/:version — Get version metadata

Returns full metadata for a specific published version, including its permissions declaration and security scan verdict.

Authentication: Same access control as GET /api/v1/skills/:name.

Path parameters

ParameterDescription
nameSkill name.
versionExact semver string (e.g. 2.3.1).

Response 200 OK

{
  "name": "@vercel/next-skill",
  "version": "2.3.1",
  "description": "Teaches agents to scaffold, build, and deploy Next.js apps.",
  "visibility": "public",
  "permissions": {
    "network": { "outbound": ["*.vercel.com", "*.anthropic.com"] },
    "filesystem": { "read": ["./src/**"], "write": ["./dist/**"] },
    "subprocess": false
  },
  "repository": "https://github.com/vercel/next-skill",
  "auditScore": 9,
  "verdict": "PASS",
  "integrity": "sha512-abc123...",
  "publishedAt": "2026-03-01T11:00:00.000Z"
}

Example

curl https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill/2.3.1

GET /api/v1/skills/:name/:version/files/:path — Get file content

Returns the raw content of a specific file within a skill version's tarball. Useful for previewing SKILL.md, README.md, or any other file without downloading the full archive.

Authentication: Same access control as GET /api/v1/skills/:name.

Path parameters

ParameterDescription
nameSkill name.
versionExact semver string.
pathFile path within the tarball (e.g. SKILL.md, src/index.ts).

Response

Returns the raw file bytes with an appropriate Content-Type header (e.g. text/markdown for .md files, text/plain for unknown extensions).

Example

curl https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill/2.3.1/files/SKILL.md

POST /api/v1/skills/confirm — Finalize publication

After uploading the tarball to the uploadUrl returned by POST /api/v1/skills, call this endpoint to finalize the publish. Tank verifies the SHA-512 integrity hash, triggers the security scanner, and marks the version as published.

Authentication: Required — skills:publish scope.

Request body

{
  "skillId": "skill_01HXYZ",
  "versionId": "ver_01HABC",
  "integrity": "sha512-ZnVuZ3MtYXJlLWNvb2wuanNvbg=="
}
FieldTypeRequiredDescription
skillIdstringYesskillId from the publish initiation response.
versionIdstringYesversionId from the publish initiation response.
integritystringYesSHA-512 hash of the tarball in the format sha512-<base64>.

Response 200 OK

{
  "name": "@acme/my-skill",
  "version": "1.0.0",
  "status": "published",
  "auditScore": 8,
  "verdict": "PASS"
}
The security scan runs synchronously during confirmation. The `verdict` in the response reflects the result of the 6-stage pipeline. A `FAIL` verdict does not block publication but is prominently displayed on the skill page and in `tank audit` output.

Example

curl -X POST https://tankpkg.dev/api/v1/skills/confirm \
  -H "Authorization: Bearer tank_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "skillId": "skill_01HXYZ",
    "versionId": "ver_01HABC",
    "integrity": "sha512-ZnVuZ3MtYXJlLWNvb2wuanNvbg=="
  }'

Stars

POST /api/v1/skills/:name/star — Star a skill

Adds a star to a skill on behalf of the authenticated user. Idempotent — starring an already-starred skill returns 200 without creating a duplicate.

Authentication: Required — skills:read scope.

Response 200 OK

{ "starred": true, "stars": 313 }

Example

curl -X POST https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill/star \
  -H "Authorization: Bearer tank_xxx"

DELETE /api/v1/skills/:name/star — Unstar a skill

Removes a star from a skill. Idempotent — unstarring a skill that was not starred returns 200.

Authentication: Required — skills:read scope.

Response 200 OK

{ "starred": false, "stars": 312 }

Example

curl -X DELETE https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill/star \
  -H "Authorization: Bearer tank_xxx"

GET /api/v1/skills/:name/star — Check star status

Returns whether the authenticated user has starred a skill.

Authentication: Required — skills:read scope.

Response 200 OK

{ "starred": true, "stars": 312 }

Example

curl https://tankpkg.dev/api/v1/skills/%40vercel%2Fnext-skill/star \
  -H "Authorization: Bearer tank_xxx"

Security Scanning

POST /api/v1/scan — Scan a tarball

Upload a .tar.gz tarball to run Tank's 6-stage security pipeline without publishing. Useful for CI/CD validation or auditing third-party skills before installing them.

Authentication: Not required. Unauthenticated scans are rate-limited more aggressively.

Request

Send the tarball as a multipart/form-data upload with the field name file.

curl -X POST https://tankpkg.dev/api/v1/scan \
  -H "Authorization: Bearer tank_xxx" \
  -F "[email protected]"

Response 200 OK

{
  "verdict": "PASS_WITH_NOTES",
  "auditScore": 7,
  "summary": {
    "critical": 0,
    "high": 0,
    "medium": 1,
    "low": 2
  },
  "findings": [
    {
      "stage": "static",
      "severity": "medium",
      "rule": "HARDCODED_URL",
      "message": "Hardcoded external URL detected in src/index.ts at line 42.",
      "file": "src/index.ts",
      "line": 42
    }
  ],
  "stages": {
    "stage0": "ok",
    "stage1": "ok",
    "stage2": "ok",
    "stage3": "ok",
    "stage4": "ok",
    "stage5": "ok"
  }
}

Verdict rules

VerdictCondition
PASSZero findings, or only informational notes.
PASS_WITH_NOTESLow-severity findings only.
FLAGGED1–3 high-severity findings.
FAILAny critical finding, or 4+ high-severity findings.

Pipeline stages

StageNameWhat it checks
stage0IngestTarball structure, SHA-512 hashing, extraction safety (no symlinks, no path traversal, no absolute paths)
stage1StructureRequired files (SKILL.md, manifest), file count (under 100), total size (under 50 MB)
stage2StaticAST analysis — eval, exec, obfuscated code, suspicious imports
stage3InjectionPrompt injection patterns in Markdown and skill definition files
stage4SecretsHardcoded credentials, API keys, tokens using entropy analysis
stage5Supply chainDependency tree analysis, known-malicious package hashes
Each stage is independent. A failure in one stage does not prevent subsequent stages from running. All findings are aggregated into the final verdict.

CLI Auth Flow

The CLI uses a browser-based OAuth flow to obtain an API key without requiring users to copy-paste tokens manually. The three-step sequence is: start → (user authorizes in browser) → exchange.

POST /api/v1/cli-auth/start — Begin OAuth flow

Generates a short-lived poll token and constructs the browser authorization URL.

Authentication: Not required.

Request body

{ "deviceName": "MacBook Pro (work)" }

Response 200 OK

{
  "pollToken": "poll_01HXYZ...",
  "authUrl": "https://tankpkg.dev/api/v1/cli-auth/authorize?token=poll_01HXYZ...",
  "expiresIn": 300
}

The authUrl should be opened in the user's browser. expiresIn is in seconds (5 minutes). After this window the poll token is invalidated and the flow must be restarted.

Example

curl -X POST https://tankpkg.dev/api/v1/cli-auth/start \
  -H "Content-Type: application/json" \
  -d '{"deviceName": "MacBook Pro (work)"}'

GET /api/v1/cli-auth/authorize — Grant access

This URL is opened in the user's browser (not called directly by the CLI). The user is shown a consent screen, authenticates with GitHub if not already signed in, and approves the token issuance.

Query parameters

ParameterDescription
tokenThe pollToken from the start response.

After the user approves, the browser is redirected back to the CLI callback and the poll token transitions to an authorized state. The CLI can now exchange it.


POST /api/v1/cli-auth/exchange — Exchange for API key

Exchanges an authorized poll token for a permanent API key. The CLI polls this endpoint after opening the browser URL, backing off until the user completes the authorization or the token expires.

Authentication: Not required.

Request body

{ "pollToken": "poll_01HXYZ..." }

Response 200 OK (authorized)

{
  "apiKey": "tank_xxxxxxxxxxxxxxxxxxxxxxxx",
  "scopes": ["skills:read", "skills:publish"]
}

Response 202 Accepted (pending — user has not yet authorized)

{ "status": "pending" }

Response 410 Gone (expired or already consumed)

{ "error": "POLL_TOKEN_EXPIRED", "message": "The poll token has expired or already been used." }
Poll tokens have a 5-minute TTL and are single-use. Once exchanged successfully, the token is invalidated. The CLI (`tank login`) handles polling and backoff automatically.

Example

# Poll until authorized (simplified — real CLI uses backoff)
curl -X POST https://tankpkg.dev/api/v1/cli-auth/exchange \
  -H "Content-Type: application/json" \
  -d '{"pollToken": "poll_01HXYZ..."}'

Badge

GET /api/v1/badge/:name — Audit score badge

Returns an SVG badge displaying a skill's current audit score. Designed to embed directly in GitHub READMEs and documentation.

Authentication: Not required.

Path parameters

ParameterDescription
nameSkill name, URL-encoded (e.g. @vercel%2Fnext-skill).

Response

Returns image/svg+xml content. The badge color encodes the score tier:

ScoreColorMeaning
9–10GreenExcellent
7–8Yellow-greenGood
5–6YellowFair
3–4OrangePoor
0–2RedFailing

Usage in Markdown

![Tank Audit Score](https://tankpkg.dev/api/v1/badge/@vercel%2Fnext-skill)

Example

curl https://tankpkg.dev/api/v1/badge/%40vercel%2Fnext-skill -o badge.svg

Full Publish Workflow

The following shows the complete three-step flow the tank publish CLI command performs internally.

# Step 1 — Initiate publish, get a pre-signed upload URL
RESPONSE=$(curl -s -X POST https://tankpkg.dev/api/v1/skills \
  -H "Authorization: Bearer tank_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": {
      "name": "@acme/my-skill",
      "version": "1.2.0",
      "description": "Does something useful.",
      "visibility": "public",
      "permissions": {}
    }
  }')

UPLOAD_URL=$(echo $RESPONSE | jq -r '.uploadUrl')
SKILL_ID=$(echo $RESPONSE | jq -r '.skillId')
VERSION_ID=$(echo $RESPONSE | jq -r '.versionId')

# Step 2 — Upload the tarball to the pre-signed URL
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/gzip" \
  --data-binary @my-skill-1.2.0.tgz

# Step 3 — Compute SHA-512 and confirm
INTEGRITY="sha512-$(openssl dgst -sha512 -binary my-skill-1.2.0.tgz | base64)"

curl -X POST https://tankpkg.dev/api/v1/skills/confirm \
  -H "Authorization: Bearer tank_xxx" \
  -H "Content-Type: application/json" \
  -d "{
    \"skillId\": \"$SKILL_ID\",
    \"versionId\": \"$VERSION_ID\",
    \"integrity\": \"$INTEGRITY\"
  }"

Command Palette

Search skills, docs, and navigate Tank