Skip to content
Everframe Docs
Documentation

Ticket API

Create a ticket from CI, a script, or a bot — without opening the dashboard.

Updated

The ticket API lets your own systems file a card onto an Everframe board directly — a failing CI run, a cron job, a Slack bot, anything that is not a human clicking around the dashboard.

curl -X POST https://everframe.dev/api/v1/tickets \
  -H "Authorization: Bearer $EVERFRAME_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Checkout crashes on iOS 18"}'

Get a token

Settings → API tokens → Create. An organisation admin role is required for every token operation — create, list, rename, re-scope, revoke.

Each token has:

  • A name, up to 80 characters. This becomes the ticket’s author in Everframe, on the card and in activity history, so name it after the system using it (ci-bot, support-triage), not a person. Tokens are deliberately left out of the @mention list — there is nobody behind one to notify.
  • A scope: all projects, or a chosen list. Narrowing to only the projects an integration actually needs limits the blast radius if the token leaks. A CI job that files against one project has no business holding a token that reaches every other one.

The token is shown once. Everframe stores only a hash and cannot show it again. Lose it and you revoke it and create a new one.

An API token is not an SDK key. An SDK key ships inside your application — a web bundle or a decompiled binary yields it — which is why the endpoint it authenticates only ever accepts reports. An API token can create tickets in your projects. Never embed one in an application you ship: it belongs in CI secrets or a server environment.

Authenticate

Authorization: Bearer evf_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Any problem with the credential — missing, malformed, unknown, or revoked — returns 401 { "error": "invalid_api_token" }, deliberately without saying which. Telling a caller a token is “revoked” rather than “invalid” would confirm it once existed. Revocation takes effect immediately.

Create a ticket

POST /api/v1/tickets
FieldTypeNotes
titlestring, required1–200 characters, plain text.
descriptionstringUp to 20 000 characters, plain text.
projectIduuidDefaults to the token’s only reachable project; required once it reaches more than one.
boardIduuidDefaults to the project’s first board.
columnIduuidDefaults to the board’s leftmost column.
prioritystringnone, low, medium, high. Defaults to none.
assigneeUserIduuid, nullableMust be a member of the organisation.
agentRepositoryIduuid, nullableMust be a repository already bound to the project.
startsAtstring, nullableYYYY-MM-DD.
dueAtstring, nullableYYYY-MM-DD.

projectId, boardId and columnId resolve in order. Omit the project and the token must reach exactly one — the request fails rather than guessing once it reaches more than one. Omit the board and you get the project’s first; omit the column and you get that board’s leftmost.

Assigning to the agent starts a run. If assigneeUserId names the organisation’s AI agent, Everframe enqueues an autonomous run against the ticket — the same as assigning a card to the agent in the dashboard. It consumes credits and may open a pull request. That is what makes agentRepositoryId useful, since it selects which bound repository the run works against. If you only want a card on the board, do not set assigneeUserId to the agent.

On success, 201:

{
  "id": "<uuid>",
  "title": "Checkout crashes on iOS 18",
  "projectId": "<uuid>",
  "boardId": "<uuid>",
  "columnId": "<uuid>",
  "createdAt": "<ISO 8601>",
  "url": "<dashboard deep link>"
}

url is a deep link straight to the new card — print it in CI output so a person can click through.

Idempotency

A CI job that retries after a timeout must not file the same ticket twice. Send an Idempotency-Key header derived from something already unique to the run:

-H "Idempotency-Key: ci-run-${GITHUB_RUN_ID}"

Reusing a key replays the original ticket — the same 201 and body — rather than creating a new one, so do not reuse one expecting it to have expired. The key is checked before the body is validated, so a retry whose body was mangled in transit still replays rather than failing with 400. Keys are scoped to the token that sent them.

The guarantee is for sequential retries, which is what a timeout-and-retry actually is. Two requests carrying the same key genuinely in flight at once can both pass the check before either has recorded it, and both create a ticket; from then on the key replays whichever won. If you fan out ticket creation concurrently, do not rely on the key alone to deduplicate.

Errors

StatusErrorMeaning
400invalid_inputBody failed validation, including an unrecognised field.
400project_requiredThe token reaches more than one project; name one.
400invalid_date_rangestartsAt is after dueAt.
400not_a_memberassigneeUserId is not a member of the organisation.
400invalid_agent_repositoryagentRepositoryId is not bound to the project.
400invalid_idempotency_keyEmpty, or longer than 255 characters.
401invalid_api_tokenMissing, malformed, unknown, or revoked.
403insufficient_scopeThe token lacks tickets:write; the body names it in required.
404project_not_foundNo such project, the token cannot reach it, or projectId was omitted and the token reaches none.
404board_not_foundNo such board, or it belongs to another project.
404column_not_foundNo such column, or it belongs to another board.
429—Over 60 requests/minute for this token.

project_not_found deliberately covers both “does not exist” and “not yours” — the same response either way, so a token cannot be used to map an organisation it has no access to by comparing error codes.

Limits

LimitValue
Rate limit, per token60 requests / minute
AttachmentsNot supported in v1

Versioning

Within /api/v1, fields may be added to requests and responses without notice; existing fields will not be removed or change meaning. Do not validate responses against a strict shape that rejects unknown fields — a client that does will break on a purely additive change.