openapi: 3.0.3 info: title: SkillHub CLI Authentication API version: 1.0.0 description: >- Authentication contract for CLI identity and public skill reads. A valid Bearer credential overrides a Web Session identity. Once the Bearer scheme is used, the credential must be valid: empty, malformed, unknown, expired, or revoked Bearer credentials return HTTP 401 and never fall back to the Session or anonymous access. An absent Authorization header or an unsupported scheme such as Basic preserves a valid Web Session. Without a Session, public reads use anonymous visibility and whoami returns HTTP 401. servers: - url: / tags: - name: CLI Authentication - name: CLI Skills paths: /api/cli/v1/auth/whoami: get: tags: [CLI Authentication] summary: Return the current CLI identity operationId: cliWhoAmI description: Requires a valid Bearer credential or Web Session. Bearer takes priority over Session; invalid Bearer returns 401 without Session fallback. An absent or non-Bearer Authorization header preserves Session, but returns 401 when no Session exists. security: - bearerAuth: [] - sessionAuth: [] responses: '200': description: Authenticated CLI identity content: application/json: schema: $ref: '#/components/schemas/CliWhoAmIEnvelope' '401': $ref: '#/components/responses/Unauthorized' /api/cli/v1/skills/search: get: tags: [CLI Skills] summary: Search CLI-installable skills operationId: cliSearchSkills description: Valid Bearer overrides Web Session. Invalid Bearer returns 401 without Session fallback. An absent or non-Bearer Authorization header preserves Session; without Session, the request uses anonymous public visibility. security: - {} - sessionAuth: [] - bearerAuth: [] parameters: - name: q in: query required: false schema: {type: string} example: pdf description: Optional search text. - name: limit in: query required: false schema: {type: integer, format: int32, default: 20} example: 20 description: Maximum number of results. responses: '200': description: Search result content: application/json: schema: $ref: '#/components/schemas/CliSearchEnvelope' '401': $ref: '#/components/responses/Unauthorized' /api/cli/v1/skills/{namespace}/{slug}/resolve: get: tags: [CLI Skills] summary: Resolve a skill version operationId: cliResolveSkill description: Valid Bearer overrides Web Session. Invalid Bearer returns 401 without Session fallback. An absent or non-Bearer Authorization header preserves Session; without Session, the request uses anonymous public visibility. security: - {} - sessionAuth: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/Namespace' - $ref: '#/components/parameters/Slug' - name: version in: query required: false schema: {type: string} example: 1.0.0 description: Optional exact version; omitted resolves latest. responses: '200': description: Resolved version content: application/json: schema: $ref: '#/components/schemas/CliResolveEnvelope' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /api/cli/v1/skills/{namespace}/{slug}/download: get: tags: [CLI Skills] summary: Download the latest installable skill version operationId: cliDownloadLatestSkill description: Valid Bearer overrides Web Session. Invalid Bearer returns 401 without Session fallback. An absent or non-Bearer Authorization header preserves Session; without Session, the request uses anonymous public visibility. security: - {} - sessionAuth: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/Namespace' - $ref: '#/components/parameters/Slug' responses: '200': $ref: '#/components/responses/Download' '302': $ref: '#/components/responses/DownloadRedirect' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/StorageUnavailable' /api/cli/v1/skills/{namespace}/{slug}/versions/{version}/download: get: tags: [CLI Skills] summary: Download an exact installable skill version operationId: cliDownloadSkillVersion description: Valid Bearer overrides Web Session. Invalid Bearer returns 401 without Session fallback. An absent or non-Bearer Authorization header preserves Session; without Session, the request uses anonymous public visibility. security: - {} - sessionAuth: [] - bearerAuth: [] parameters: - $ref: '#/components/parameters/Namespace' - $ref: '#/components/parameters/Slug' - $ref: '#/components/parameters/Version' responses: '200': $ref: '#/components/responses/Download' '302': $ref: '#/components/responses/DownloadRedirect' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/StorageUnavailable' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: SkillHub API token description: API token issued by SkillHub. A valid token overrides Web Session; invalid lifecycle states all return the same 401 response without Session fallback. sessionAuth: type: apiKey in: cookie name: SESSION description: Spring Session browser identity. It is preserved when Authorization is absent or uses a non-Bearer scheme, and is overridden by a valid Bearer token. parameters: Namespace: name: namespace in: path required: true schema: {type: string} example: global description: Namespace slug. Slug: name: slug in: path required: true schema: {type: string} example: pdf-parser description: Skill slug. Version: name: version in: path required: true schema: {type: string} example: 1.0.0 description: Exact semantic version. responses: Download: description: ZIP package stream headers: Content-Disposition: schema: {type: string} description: Attachment filename. content: application/zip: schema: {type: string, format: binary} DownloadRedirect: description: Redirect to a presigned object-storage URL headers: Location: schema: {type: string, format: uri} BadRequest: description: Namespace, skill, or version cannot be resolved. content: application/json: schema: {$ref: '#/components/schemas/ErrorEnvelope'} Unauthorized: description: No valid supported identity is present where required, or the Bearer credential is empty, malformed, unknown, expired, revoked, or belongs to an unavailable user. Invalid Bearer never falls back to Web Session. content: application/json: schema: {$ref: '#/components/schemas/ErrorEnvelope'} example: code: 401 msg: Authentication required data: null timestamp: '2026-07-28T00:00:00Z' requestId: req-123 Forbidden: description: Credential is valid but token scope or resource permission is insufficient. content: application/json: schema: {$ref: '#/components/schemas/ErrorEnvelope'} example: code: 403 msg: Forbidden data: null timestamp: '2026-07-28T00:00:00Z' requestId: req-123 StorageUnavailable: description: Object storage is unavailable. content: application/json: schema: {$ref: '#/components/schemas/ErrorEnvelope'} schemas: Envelope: type: object required: [code, msg, data, timestamp, requestId] properties: code: {type: integer, format: int32} msg: {type: string} data: {type: object, nullable: true} timestamp: {type: string, format: date-time} requestId: {type: string, example: req-123} ErrorEnvelope: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: {type: object, nullable: true, example: null} CliWhoAmIEnvelope: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: $ref: '#/components/schemas/CliWhoAmI' CliWhoAmI: type: object required: [handle, displayName, email] properties: handle: {type: string, example: user-123} displayName: {type: string, example: CLI User} email: {type: string, format: email, nullable: true, example: cli@example.com, description: Email address when available; the required field is null when the account has no email.} CliSearchEnvelope: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: $ref: '#/components/schemas/CliSearchResult' CliSearchResult: type: object required: [items, total, limit] properties: items: type: array items: {$ref: '#/components/schemas/CliSearchItem'} total: {type: integer, format: int64, example: 1} limit: {type: integer, format: int32, example: 20} CliSearchItem: type: object required: [namespace, slug, latestVersion] properties: namespace: {type: string, example: global} slug: {type: string, example: pdf-parser} latestVersion: {type: string, example: 1.2.0} summary: {type: string, nullable: true, example: Parse PDF files} CliResolveEnvelope: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: $ref: '#/components/schemas/CliResolveResult' CliResolveResult: type: object required: [namespace, slug, version, versionId, fingerprint, downloadUrl] properties: namespace: {type: string, example: global} slug: {type: string, example: pdf-parser} version: {type: string, example: 1.2.0} versionId: {type: integer, format: int64, example: 42} fingerprint: {type: string, example: 'sha256:abc123'} downloadUrl: {type: string, example: /api/v1/skills/global/pdf-parser/versions/1.2.0/download}