Developers
Agent skills
Skills teach a coding agent how Wilmo's API works, so it authenticates, pages and handles errors right the first time.
Install#
A skill is one SKILL.md file. Put it where your agent reads skills. For Claude Code, that is .claude/skills/<name>/SKILL.md in your project.
Terminal
mkdir -p .claude/skills/wilmo-api
curl -o .claude/skills/wilmo-api/SKILL.md https://www.wilmo.ai/developers/skills/wilmo-api.md
mkdir -p .claude/skills/wilmo-api-migrations
curl -o .claude/skills/wilmo-api-migrations/SKILL.md https://www.wilmo.ai/developers/skills/wilmo-api-migrations.mdwilmo-api
Using the Wilmo API
Authenticate, discover operations, call them, page, handle errors, rate limits and versions.
Read SKILL.md
---
name: wilmo-api
description: Use Wilmo's public API (REST /api/v2, MCP, the wilmo CLI) to read a customer-support organization's tickets, conversations, teams and reporting. Use when an integration, a script or an agent needs Wilmo data, when a request to app.wilmo.ai fails, or when choosing between a key, OAuth, MCP and the CLI.
---
# Wilmo API
One operation, every surface: each Wilmo operation is at once a REST endpoint, an MCP tool
and a CLI command, with the same input and output. Names say what the rows are, never whose:
your key or grant decides whose rows come back.
```text
REST https://app.wilmo.ai/api/v2/<resource> e.g. GET /api/v2/tickets
MCP https://app.wilmo.ai/api/mcp tool list_tickets (<keyword>_<resource>)
CLI npx @wilmo/cli tickets list the same tool
DOCS https://app.wilmo.ai/api/public/v1/openapi OpenAPI 3.1, every public operation
TODAY (tools/list and the OpenAPI document list yours)
REST MCP tool / OpenAPI operationId
GET /api/v2/teams list_teams
GET /api/v2/tickets list_tickets
GET /api/v2/tickets/{ticket_id} get_ticket
GET /api/v2/ticket-counts get_ticket_counts
GET /api/v2/dashboards list_dashboards
GET /api/v2/reports/{report_id}/results get_report_results
GET /api/v2/metrics/autopilot get_autopilot_metrics
GET /api/v2/ticket-analytics get_ticket_analytics
GET /api/v2/skills list_skills
GET /api/v2/tags list_tags
```
## Authenticate
```text
team API key Settings > API keys one team REST + MCP
organization key Settings > Connected apps every team of ONE org REST + MCP
OAuth (Claude, CLI) "Sign in with Wilmo" every team of one org MCP
an organization admin consents; read-only
```
Send `Authorization: Bearer <key or token>`. Never put a key in a URL.
## Discover what you may call
- Your operations: MCP `tools/list`, or `GET /api/public/v1/openapi` with your key as Bearer
(it lists the ones your scopes and your organization's enabled features allow).
- Map a market or brand to a team first: `GET /api/v2/teams` (MCP `list_teams`).
## Headers
```text
Wilmo-Version: 2026-11-06 shape requests and responses for this version (pin it in code)
Wilmo-Organization: <uuid> which organization, for a principal in several
Idempotency-Key: <uuid> on POST/PUT/PATCH/DELETE: a retry returns the first response
Request-Id (response) quote it when asking Wilmo for help
```
## Responses
```text
200/201 the object itself (null when the operation returns nothing, e.g. a delete)
a list: { "object": "list", "data": [...], "has_more": true, "next_cursor": "..." }
next page: the same request with cursor=<next_cursor>; stop when has_more is false
a list that takes offset (its next_cursor is always null): offset=<offset + data.length>
while has_more is true; total_count is every row
202 accepted: the work was queued; the body says what to follow (a job or run id)
204 done, no body
4xx/5xx { "error": { "type", "code", "message", "param", "request_id", "doc_url" } }
```
`error.message` is one short sentence saying what to fix. Act on `code`, not on the text:
```text
invalid_input / invalid fix error.param and resend
authentication_required send a valid key or token
forbidden / permission_* the key lacks the scope, or the person is not an admin
not_found it does not exist, is not yours, or its id is malformed: do not retry
rate_limited wait Retry-After seconds, then retry
idempotency_conflict a different request reused the key: use a new key
api_error (5xx) retry with the same Idempotency-Key, with backoff
```
## Rate limits
MCP: 300 requests per 10 minutes per grant or key, 1,500 per organization. REST: per key, per
team and per customer. A 429 carries `Retry-After`.
## Versions
Every key and grant is pinned to the API version of the day it was created. Breaking changes ship
only in a new dated version; see the wilmo-api-migrations skill and
https://app.wilmo.ai/api/public/v1/api-versions.
wilmo-api-migrations
Migrating between Wilmo API versions
Read the versions document and move a pinned integration to a newer API version.
Read SKILL.md
---
name: wilmo-api-migrations
description: Move an integration of Wilmo's public API from one dated API version to a newer one, safely. Use when Wilmo announces a new API version, when a response's Wilmo-Version header is older than the current version, or before using a field that only a newer version has.
---
# Migrating between Wilmo API versions
```text
versions dated: 2026-11-06, ... GET https://app.wilmo.ai/api/public/v1/api-versions
your version the Wilmo-Version you send, else your key/grant's pinned version
breaking only ever in a new version; your pinned version keeps its shape
additive new operations, new optional inputs, new output fields: arrive in every version
```
## Steps
1. Read the changelog: `GET /api/public/v1/api-versions`. For every version newer than yours,
read each change's `description` and `migration` for the operations you call.
2. Change your code for each one (a renamed field, a new required input).
3. Send `Wilmo-Version: <new version>` on every request, in tests first, then in production.
Pinning the header in code is the migration: nothing changes until you send it.
4. Check every response's `Wilmo-Version` header says the new version.
5. Roll back by sending the old version again: older versions keep working.
## Write code that survives additive changes
- Ignore output fields you do not know.
- Treat enums as open: handle an unknown value gracefully.
- Page with `next_cursor` until `has_more` is false; never assume a page size.