# SkillSafe auth.md

Agent registration and authentication guide for the SkillSafe API
(`https://api.skillsafe.ai`). This document is for AI agents and automated
clients. SkillSafe does not operate an OAuth authorization server; all
programmatic access uses API keys sent as Bearer tokens.

- API base: `https://api.skillsafe.ai`
- OpenAPI spec: `https://skillsafe.ai/openapi.json`
- API catalog: `https://skillsafe.ai/.well-known/api-catalog`
- Human docs: `https://skillsafe.ai/docs/`
- Agent overview: `https://skillsafe.ai/llms.txt`

## Who should use this

Automated clients that want to scan, save, install, or deploy AI skills and
hosted apps on SkillSafe. Read-only discovery endpoints (search, skill
metadata, `?md` summaries) require no credentials; writes require an API key.

## Registration methods

### 1. Anonymous provisioning (no human in the loop)

```
POST https://api.skillsafe.ai/v1/auth/temp-key
```

No body, no auth. Returns HTTP 201 with:

```json
{
  "ok": true,
  "data": {
    "api_key": "sk_...",
    "expires_at": "2026-08-16T00:00:00.000Z",
    "account_id": "acc_...",
    "username": "temp-xxxxxxxx",
    "namespace": "@temp-xxxxxxxx"
  }
}
```

The key is scope-limited ("temp") and expires in 7 days. Verifying an email on
the account within that window (`POST /v1/account/email/verify-code`) upgrades
it to a normal account and key; otherwise the account and everything it
created are deleted at expiry. Strictly rate-limited — provision once and
store the key, do not mint per request.

The upgrade keeps the **same key string**, so a stored credential keeps working
afterwards — there is nothing to re-plumb. Allow roughly 30 seconds for the
change to propagate through the auth cache.

#### Temp-key capabilities

A temp key is caged to exactly these routes; everything else returns 403. The cage
is drawn so that an agent can complete a whole workflow — publish a skill, deploy
it as an app, put it on a schedule — with no human involved except the email
verification that lifts the cage.

`GET /v1/account` reports this same list for the calling key, plus
`provisional_expires_at`, `days_remaining`, and the exact upgrade steps. Prefer
reading it over hard-coding this table.

| Capability | Route |
|---|---|
| Read your own account, limits, and expiry | `GET /v1/account` |
| Attach an email — start | `POST /v1/account/email/send-code` |
| Attach an email — confirm (upgrades the key) | `POST /v1/account/email/verify-code` |
| Negotiate a delta upload | `POST /v1/skills/{ns}/{name}/negotiate` |
| Save a private skill version | `POST /v1/skills/{ns}/{name}` |
| Read your own skills | `GET /v1/skills/{ns}/{name}` |
| Share a skill — **clean scans only**, see below | `POST /v1/skills/{ns}/{name}/versions/{version}/share` |
| Create one app | `POST /v1/apps` |
| List your apps | `GET /v1/apps` |
| Read one of your apps | `GET /v1/apps/{slug}` |
| Update that app | `PATCH /v1/apps/{slug}` |
| Delete that app | `DELETE /v1/apps/{slug}` |
| Upload an app release | `POST /v1/apps/{slug}/releases` |
| Open a chunked release session | `POST /v1/apps/{slug}/releases/session` |
| Stage / commit a chunked release | `POST /v1/apps/{slug}/releases/session/{id}/files` |
| Abandon a chunked release session | `DELETE /v1/apps/{slug}/releases/session/{id}` |
| List schedules | `GET /v1/apps/{slug}/schedules` |
| Create a schedule — **non-public apps only** | `POST /v1/apps/{slug}/schedules` |
| Update or delete a schedule | `PATCH /v1/apps/{slug}/schedules/{id}` |
| Deploy-host bootstrap view | `GET /v1/creator/me` |

Two of these carry an extra condition beyond the route being reachable:

- **Sharing** requires the version's scan report to show **no findings** (403
  `provisional_requires_clean_scan`). On a temp key that claim is not taken on
  trust: a save whose report claims clean is scanned server-side and rejected with
  `scan_report_understates_findings` if the scanner finds anything actionable
  (critical, high, or medium). Low and informational findings do not block.
- **Scheduling** requires the app not be publicly listed (403
  `provisional_requires_non_public_app`), because a schedule spends credits on a
  timer with no verified identity behind it.

Use the chunked session endpoints for any bundle over ~10 MB. A single-shot
`POST /releases` holds the whole bundle in one isolate and dies without a usable
error above roughly that size.

Notably **not** available on a temp key: yanking a skill, app secrets, BYOK
provider keys, egress/ingress config, custom domains, forking, GitHub or URL
import, and anything under orgs, billing, vault, or key management. Each refusal
returns the endpoints that would grant it.

### 2. Device flow (human approves in a browser)

1. `POST https://api.skillsafe.ai/v1/auth/cli` → returns a `session_id`, a
   browser URL (`login_url`), a `user_code` and a poll `interval` in seconds.
2. Direct the user to open the URL and sign in (Google, GitHub, or email), and
   show them `user_code`. The approve page shows the same code.
3. Poll `GET https://api.skillsafe.ai/v1/auth/cli/{session_id}` every `interval`
   seconds until approved. The response contains a full-authority API key valid
   for 365 days, returned once.

### 3. Dashboard-issued keys (human)

Signed-in users can mint API keys at `https://skillsafe.ai/account/`. Accounts
may hold at most 20 active keys.

## Using credentials

Send the key as a Bearer token on every request:

```
Authorization: Bearer sk_...
```

- Keys are stored server-side only as SHA-256 hashes; a lost key cannot be
  recovered, only revoked and reissued.
- Do not send keys as query parameters or cookies; the `Authorization` header
  is the only supported programmatic method.
- Responses use the envelope `{ "ok": boolean, "data" | "error", "meta" }`.
- Endpoints are rate-limited per IP; back off on HTTP 429.

## Support

Questions: `support@skillsafe.ai`. Security reports: see
`https://skillsafe.ai/.well-known/security.txt`.
