Skills API
Create, evaluate, publish, and adopt agent skills, and manage the rubric config that gates publishing.
Base path: /api/v1/skills. See the Skill Marketplace concept page for the lifecycle and object model, and the Eval Results API for external verdict ingestion.
Entitlement and roles
skill_marketplace entitlement (Enterprise plan). Reads need the viewer role; writes need member.Endpoints
Routes are matched in this order (the public shelf and rubric routes are registered before :id so they aren't swallowed by it):
/api/v1/skills/publicBrowse the public shelf (cross-org).
/api/v1/skills/public/:idGet a public shelf skill.
/api/v1/skills/public/:id/adoptCopy a public skill into your org.
/api/v1/skills/rubricGet (or seed) your org's rubric config.
/api/v1/skills/rubric/:idUpdate your org's rubric config.
/api/v1/skillsCreate a skill.
/api/v1/skillsList your org's skills.
/api/v1/skills/:idGet a skill.
/api/v1/skills/:idUpdate a skill.
/api/v1/skills/:idDelete a skill.
/api/v1/skills/:id/publishPublish to the public shelf (gated).
/api/v1/skills/:id/unpublishUnpublish.
/api/v1/skills/:id/evaluateForce an out-of-band re-evaluation.
/api/v1/skills/:id/evaluationsList a skill's evaluation history.
Create
Either supply raw_source (frontmatter + body, parsed as-is) or discrete fields (name, description required; headline, body optional), which get synthesized into that shape.
curl -X POST https://api.ingateai.com/api/v1/skills \
-H "X-Ingate-Key: sk-ingate-your-key" \
-H "Content-Type: application/json" \
-d '{
"raw_source": "---\nname: pdf-summarizer\nheadline: Summarize a PDF\ndescription: Extracts text and produces a concise summary\n---\n## Steps\n1. Extract text\n2. Summarize"
}'Response 201: the created Skill, version: 1, visibility: "private". Creation also dispatches an async evaluation against the org's rubric.
Create Errors
| Status | Error code | Cause |
|---|---|---|
400 | invalid_body | Malformed JSON |
400 | missing_required | raw_source absent and name/description also absent |
400 | invalid_frontmatter | Frontmatter fails validation (missing name/description, non-kebab-case name, malformed YAML) |
500 | internal_frontmatter_synthesis | Synthesizing frontmatter from discrete fields failed unexpectedly |
List
curl "https://api.ingateai.com/api/v1/skills?visibility=private&name_like=pdf&limit=50" \
-H "X-Ingate-Key: sk-ingate-your-key"| Param | Type | Description |
|---|---|---|
visibility | string | private or public; empty = all |
name_like | string | Substring match on name |
limit | int | Max results |
Response 200: {"skills": [ ... ]}, the caller's own org only.
Get, Update, Delete
GET /api/v1/skills/:id returns the Skill. PUT /api/v1/skills/:id is a partial update accepting any of raw_source, name, headline, description, body; if raw_source is supplied it is re-parsed and wins over any other field in the same request. The update response is the updated Skill (re-fetched after write, so the version and timestamps reflect the new state); it also logs a new SkillVersion snapshot and dispatches re-evaluation. DELETE /api/v1/skills/:id returns {"deleted": true}.
Publish
Preflight: the skill's current version must have a passing evaluation from one of the org's allowed_verdict_sources (rubric config; defaults to ["internal"], Ingate's own judge only). Publishing an already-public skill is idempotent and returns 200, not 409.
{"published_at": "2026-08-29T12:00:00Z"}409 not_publishable, no passing evaluation for the current version from an allowed source:
{
"error": "publish requires a passing evaluation for the current version",
"error_code": "not_publishable",
"skill_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"version": 3
}See the Eval Results API for how to let an external evaluator's verdict satisfy this gate.
Unpublish
Idempotent: unpublishing an already-private skill also returns 200. Response 200: {"unpublished": true}.
Force Evaluate
Dispatch an out-of-band re-evaluation regardless of the periodic scheduler's cadence.
{"dispatched": true, "queued": true}queued: false means the pipeline's dispatch buffer was full and the request was dropped. Retry later.
List Evaluations
curl "https://api.ingateai.com/api/v1/skills/d290f1ee-6c54-4b01-90e6-d701748f0851/evaluations?limit=20" \
-H "X-Ingate-Key: sk-ingate-your-key"limit defaults to 50, capped at 200. Response 200: {"evaluations": [ ... ]},
{
"id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"skill_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"skill_version": 3,
"rubric_config_id": "7c9e6...",
"axes": {
"name_clarity": {"score": 0.9, "rationale": "..."},
"description_task_descriptiveness": {"score": 0.85, "rationale": "..."}
},
"overall_score": 0.88,
"passed": true,
"judge_provider": "openai",
"judge_model": "gpt-4o-mini",
"latency_ms": 842,
"prompt_tokens": 512,
"completion_tokens": 96,
"source": "internal",
"created_at": "2026-08-29T12:00:00Z"
}source is "internal" for Ingate's built-in judge, or an external evaluator name (e.g. "promptfoo") for verdicts ingested via the Eval Results API. external_ref (omitted when empty) is an opaque pointer into that external system.
Public Shelf
Browse
curl "https://api.ingateai.com/api/v1/skills/public?name_like=pdf&description_like=summar&limit=50" \
-H "X-Ingate-Key: sk-ingate-your-key"| Param | Type | Description |
|---|---|---|
name_like | string | Substring match on name |
description_like | string | Substring match on description |
limit | int | Max results |
Response 200: {"skills": [ ... ]} across all orgs' published skills.
Get
GET /api/v1/skills/public/:id returns the public Skill.
Adopt
Deep-copies a public shelf skill into the caller's org as a new, private skill (version: 1, source_skill_id set to the shelf entry). Renames on name collision (-adopted-1, -adopted-2, …).
{
"skill": { /* the new Skill */ },
"renamed": false,
"name": "pdf-summarizer"
}Errors: 400 invalid_id, 404 not_found (source skill), 409 duplicate_name (no non-colliding name found after 100 attempts).
Rubric Config
Get (Seed on First Access)
GET /api/v1/skills/rubric seeds a default RubricConfig for the org from server-configured defaults if none exists yet. Response 200: the RubricConfig.
Update
Partial update: only provided fields change. :id is the config's id (from the GET response above). Accepts any of judge_provider, judge_model, embed_provider, embed_model, embed_dims, weights, pass_threshold, allowed_verdict_sources.
curl -X PUT https://api.ingateai.com/api/v1/skills/rubric/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
-H "X-Ingate-Key: sk-ingate-your-key" \
-H "Content-Type: application/json" \
-d '{"allowed_verdict_sources": ["internal", "promptfoo"]}'Response 200: the updated RubricConfig. Errors: 400 invalid_id, 400 invalid_body, 404 not_found.