API Overview
REST, JSON, API-key auth. Available on every plan, free included.
Authentication
Generate a key under Settings → API Keys (Admin role required). You'll see the key value exactly once, copy it somewhere you trust before closing the dialog, because we can't show it again. Pass it on every request as a Bearer token:
curl -H "Authorization: Bearer ld_your_api_key_here" \
https://api.fesk.io/v1/tickets
Each key is scoped to a workspace and can be revoked from the same settings page if it leaks.
Grant only the scopes the integration needs, such as tickets:read, tickets:write, or comments:write. A project-scoped key is the simplest option. If a key is not tied to a project, ticket and board requests must identify one, either a numeric project_id or a human-readable project slug (e.g. ?project=infrastructure); otherwise the API returns 400.
Discovering projects
You don't need to know internal project IDs. Call GET /v1/projects with any valid key to list the projects it can reach, each with a stable slug you can use in place of the numeric ID:
curl -H "Authorization: Bearer ld_your_api_key_here" \
https://api.fesk.io/v1/projects
{
"ok": true,
"data": {
"projects": [
{ "id": 3, "name": "Infrastructure", "slug": "infrastructure", "description": null }
],
"count": 1
}
}
Anywhere an endpoint needs a project, you may pass ?project=<slug> instead of ?project_id=<id>. A project-scoped key resolves the project automatically and needs neither.
Base URL
https://api.fesk.io/v1/
External integrations use the api. subdomain with a /v1 version prefix, for example https://api.fesk.io/v1/tickets. API keys only work on this subdomain; sending one to the main domain returns 401.
Response shape
Every response uses the same envelope, so you can write one client that handles them all:
{
"ok": true,
"data": { }
}
On errors, ok is false and there's an error object instead of data:
{
"ok": false,
"error": {
"code": 422,
"message": "Validation failed",
"validation_errors": { }
}
}
The X-Correlation-ID response header contains a request identifier (e.g. req_a1b2c3). Quote it when reporting bugs, it lets us trace your exact request through our logs.
Status codes
400, malformed JSON401, missing or invalid API key402, the included API allowance or configured monthly API limit has been reached403, authenticated but not permitted (wrong role, wrong workspace)404, resource doesn't exist (or you can't see it)422, validation failed;error.validation_errorshas field-level messages429, rate-limited; check theRetry-Afterheader5xx, something on our side; theX-Correlation-IDheader is what to send us
Safe retries
For create and update requests that may be retried, send a stable Idempotency-Key header. Repeating the same operation with the same key returns the stored result instead of creating the change twice. Use a new key for a genuinely new operation.
Rate limiting
Requests are rate-limited per API key using a sliding window. When you hit the limit, the API returns 429 with a Retry-After header. Wait at least that long; repeated clients should use exponential backoff with jitter.
API calls are also metered for billing. The first 5,000 calls per month are included. An admin can enable paid API usage or set a lower monthly call limit under Settings → Billing; reaching either boundary returns 402 with the reason and current usage.
Common endpoints
| Method | Path | What it does |
|---|---|---|
GET | /v1/projects | List accessible projects and their slugs |
GET | /v1/tickets | List tickets, paginated and filterable |
POST | /v1/tickets | Create a ticket |
GET | /v1/tickets/:id | Get one ticket with full detail |
PUT | /v1/tickets/:id | Update fields on a ticket |
That's a fraction of what's there, for the full list with request and response schemas, see the interactive API reference.