# xAI Skills API (`/v1/skills`) — hosted skills **Status:** all five routes DOCUMENTED (OpenAPI only) + **ACCOUNT_RESTRICTED** — every call with our key returned **404** on 2026-09-18 (`{"error":{"code":404,"message":"The requested resource was not found…"}}` for GET, `text/plain` 404 for POST). No public docs page mentions `/v1/skills`; the only reference is `sources/xai/openapi/openapi.json` (5 operations, 5 schemas). A 404 does not mean the API does not exist (gated rollout is the likely explanation) — treat shapes below as OpenAPI-derived and untested. **Sources:** OpenAPI `https://docs.x.ai/openapi.json` (local `sources/xai/openapi/openapi.json`, operations `handle_list_skills_request`, `handle_upload_skill_request`, `handle_retrieve_skill_request`, `handle_delete_skill_request`, `handle_download_skill_content_request`; schemas `Skill`, `SkillList`, `UploadSkillMultipartRequest`, `DeletedSkill`, `ListSkillsParams`, `LocalShellSkill`, `ShellEnvironment`) · Grok Build [Skills, Plugins & Marketplaces](https://docs.x.ai/build/features/skills-plugins-marketplaces) (SKILL.md format) · Grok Bot [Skills and routines](https://docs.x.ai/grok-bot/skills-routines-and-automations). **Last verified:** 2026-09-18 (raw `tmp-live/xai-media/skills-*.json`). **Machine-readable:** `generated/fragments/endpoints/xai-media-voice-skills.json` (api_family `skills`), `parameters/xai-skills.json`, `objects/xai-media-objects.json` (Skill, SkillList, DeletedSkill, LocalShellSkill). ## What a skill is A **skill** is a directory whose `SKILL.md` starts with YAML frontmatter (`name`, `description`, optional `when-to-use`, `paths`, `allowed-tools`, `argument-hint`, `user-invocable`, `disable-model-invocation`, `metadata`) followed by Markdown instructions; it may carry scripts and resources. The same format is used by Grok Build (discovered from `.grok/skills/`, `~/.grok/skills/`, plugins, `~/.agents/skills/`, and Claude Code / AGENTS.md layouts) and by Grok Bot (private skill library, Marketplace). The hosted Skills API stores such a directory (zipped) server-side and extracts `name`/`description` from the frontmatter. ## Endpoints (OpenAPI) | Method / path | Request | Response | Documented errors | Live (our key) | |---|---|---|---|---| | `GET /v1/skills` | query `limit` (1–100, default 100), `after` (cursor = last id), `order` (`asc`\|`desc`, default `desc`) | `SkillList {object:"list", data:[Skill], first_id, last_id, has_more}` | — | 404 JSON | | `POST /v1/skills` | `multipart/form-data`, field `files`: one zip **or** the field repeated per file of a directory upload | `Skill` | 400 invalid upload, 413 payload too large | 404 text/plain (zip, single file, JSON body) | | `GET /v1/skills/{skill_id}` | path `skill_id` | `Skill` | 404 "Skill not found." | 404 JSON | | `DELETE /v1/skills/{skill_id}` | path | `DeletedSkill {id, object:"skill.deleted", deleted}` | 404 | not attempted | | `GET /v1/skills/{skill_id}/content` | path | raw zip bytes (`application/zip`, chunked) | 404 | not attempted | `Skill = {id, object:"skill", name, description, created_at (unix s), default_version:"1", latest_version:"1"}` — versions are "currently always 1". ## How skills reach inference The OpenAPI wires skills into inference only through the **shell tool environment**: `ShellCall.environment = {type:"local", skills:[LocalShellSkill{name, description, path}]}` — i.e. the client tells the model which local skill directories exist when it runs shell commands on the model's behalf (Responses API, `shell` tool; see the tools agent's docs). No request schema references a hosted `skill_id` yet (no `skills: [...]` field on `/v1/responses` or `/v1/chat/completions` in the spec), so the hosted store appears to be groundwork for Grok Build / Grok Bot sync rather than an inference-time parameter. ## Live probe (2026-09-18) ``` GET /v1/skills?limit=5 → 404 {"error":{"code":404,"message":"The requested resource was not found. Please check the URL and try again. Documentation is available at https://docs.x.ai/"}} POST /v1/skills files=@atlas-probe-skill.zip → 404 text/plain (122 bytes) POST /v1/skills files=@SKILL.md → 404 text/plain GET /v1/skills/skill_does_not_exist → 404 (same JSON envelope) ``` The test (`tests/xai/test_skills.py`) asserts the current behaviour (404 with the generic envelope) and will fail loudly when the API opens up, so the atlas can be re-verified. Example `examples/xai/skills/skills_crud.py` performs list → upload → get → content → delete and stops at the first non-2xx.