Skills
A skill is a folder anchored by SKILL.md, per the
Agent Skills specification:
frontmatter (name, description, …) plus a body of instructions, and
optional references/, scripts/, and assets/ subdirectories. Where hosted
and MCP tools give a smith functions to call, a skill gives it
material to read — a procedure, a house style, a reference doc — that it
pulls in only when the task calls for it, instead of every turn.
Uploading a skill
POST /v1/skills takes the bundle either as one zip on a file part, or as
repeated files[] parts whose filenames carry each path relative to — and
including — the skill's root directory. Either way, creates the skill and its
first version:
# Authorization: tenant-admin token (server-side only)
curl -X POST https://api.cloud.ingram.tech/v1/skills \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-F "file=@invoice-review.zip"
# → 200 { "id": "skl_…", "object": "skill", "name": "invoice-review",
# "description": "Use when reviewing a supplier invoice.",
# "default_version": 1, "created_at": "…", "updated_at": "…" }
The equivalent as repeated files[] parts:
# Authorization: tenant-admin token (server-side only)
curl -X POST https://api.cloud.ingram.tech/v1/skills \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-F "files[]=@invoice-review/SKILL.md;filename=invoice-review/SKILL.md" \
-F "files[]=@invoice-review/references/vat.md;filename=invoice-review/references/vat.md"
A zip may not unpack to more than 32 MB, and a bundle carries at most 500
files. SKILL.md and everything under references/ come to at most 1 MB
together — they are what the model searches, and past that the content is
retrieval-shaped, which is a vector store. scripts/
and assets/ are not counted: they are read on demand and never indexed.
name is the spec's skill name, parsed from SKILL.md's frontmatter — it
must match the bundle's root directory, and it is the tenant-unique handle: a
second POST /v1/skills with the same name is refused with 409 skill_exists. GET /v1/skills lists a tenant's skills; GET /v1/skills/{id} fetches one; DELETE /v1/skills/{id} deletes it, refused
(409 skill_in_use) while any published agent version still references it.
Versions
A skill's versions are immutable and integer-numbered. Publish a new one the
same way you created the skill, against /versions:
# Authorization: tenant-admin token (server-side only)
curl -X POST https://api.cloud.ingram.tech/v1/skills/skl_.../versions \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-F "files[]=@invoice-review/SKILL.md;filename=invoice-review/SKILL.md"
# → 200 { "skill_id": "skl_…", "version": 2, … }
Publishing a version never moves default_version — an agent that names no
explicit version keeps running the one it already resolved to. Move it
explicitly:
# Authorization: tenant-admin token (server-side only)
curl -X POST https://api.cloud.ingram.tech/v1/skills/skl_... \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "default_version": 2 }'
GET /v1/skills/{id}/versions lists a skill's versions, newest first; GET /v1/skills/{id}/versions/{v} returns one, including the file list with each
file's size and content hash (sha256), and referenced_by — the published
agent versions that froze a reference to it (the same list a 409 skill_in_use names). Bytes never appear in this response, so this is how a
tenant sees exactly what they installed without the bytes themselves. DELETE …/versions/{v} deletes one version, refused (409 skill_in_use) when it is
the skill's default_version or a published agent version still references
it.
Reading a version back
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/skills/skl_.../versions/2/content \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-o invoice-review-v2.zip
Without ?path= the whole version comes back as a zip — the same shape the
upload accepts, so a version round-trips. With ?path=references/vat.md,
that one file's bytes.
Attaching a skill to an agent
An agent's draft carries a skills field alongside
enabled_hosted_tools and mcp_servers — the list of skills its smiths
carry:
# Authorization: tenant-admin token (server-side only)
curl -X PATCH https://api.cloud.ingram.tech/v1/agents/agt_... \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "skills": [ { "skill_id": "skl_..." } ] }'
A skill_id with no version resolves to the skill's default_version at
publish time: POST …/versions writes that number into the snapshot, so
moving default_version afterwards never changes what an already-published
agent version runs. Publishing refuses a skill_id or a version that
doesn't resolve (422) — a frozen snapshot can't be corrected later.
A smith may override its agent's skills the same way it overrides
mcp_servers — PATCH the smith; an explicit null clears the override and
re-inherits the agent's. A smith's own override is live config, not a
snapshot: omit version there and it follows default_version.
What the model gets
A skill's name and description are on every turn — cheap enough that the
model can decide whether the skill is relevant. The body of SKILL.md, and
anything under references/, is read only when the model decides the skill
applies, so an agent with many skills does not pay full context for all of
them every turn. skill_search searches the body and every reference file by
relevance, so the model can find one paragraph without reading the whole
bundle.
A skill's scripts/ run through the run_command hosted tool —
an agent that attaches a skill carrying scripts needs run_command enabled, or
the model can read the script but never run it. The sandbox box
unpacks every skill the run carries to /skills/<name>/ before the first command,
read-only. A script writes its output to /workspace (this run) or /cache (this
smith); a write under /skills is refused.
A skill that cannot be served does not fail the run — it becomes a
skill_unavailable warning on the run and
on its run.completed event, so an answer produced without a skill is never
shaped like one produced with it. The same applies when a run's skills together
exceed the search-index ceiling (4 MB of SKILL.md plus references/): the run
keeps them in the order the skills array lists and drops the rest, warning for
each — so ordering that array is how you say which skills matter most.
In the console
Skills in the console lists your skills, what each one carries, and which
agent versions froze it. Upload a bundle as a zip, publish a version, move
default_version, and download any version whole or file by file. An agent's
editor attaches skills in order — the same order that decides which survive a
run's search-index ceiling.