Skip to content

API Guide

Get started with the East Agile Tracker REST API: user and agent keys, creating and moving stories, idempotent writes, search, events and pagination.

The East Agile Tracker API is designed for agents as much as for humans. Everything you can do in the UI, you can do over the API — and a few things the UI doesn’t expose are there too.

This guide gets you from zero to “scripting your backlog” in under ten minutes. For the full endpoint reference, see API Specification.

You authenticate with a key in the X-TrackerToken header. There are two kinds of key you mint yourself, and a third that an MCP client obtains for you:

  • User keys (ea_user_…) — Act as you. Create them in Account Settings → API Keys. Use these for personal scripts, CLI tools, integrations.
  • Agent keys (ea_agent_…) — Act as a named agent in one project. Create them in Project Settings → Agent. Use these for AI agents — Claude Code, Codex, your own — that should participate in the project as named teammates.
  • MCP tokens (ea_mcp_…) — OAuth 2.1 access tokens issued to an MCP client (Claude, an IDE) after you approve it on the consent page. They act as you, and you can revoke them under Account Settings → API Keys → Connected apps.

The one-time dialog after creating a personal API key in Account Settings, with the key masked in this capture

The Agent tab's create-key form with a name and the member role selected, below the setup instructions

The differences between the two you mint:

User keyAgent key
ScopeAll your projectsOne specific project
Identity in audit logYour nameThe agent’s name
RoleYour role in each projectSet at key creation (viewer, member, or manager — never above the minting member’s own role)
RevocationRevoke a key; you keep access via other keys/sessionsRevoke or rotate a key; the agent loses access immediately
Best forPersonal automation, scriptsAI agents that should be distinguishable from you in the history

Authorization: Bearer … also works if you prefer that header style.

Get your projects:

Terminal window
curl https://api.eastagiletracker.com/api/v1/projects \
-H "X-TrackerToken: $TRACKER_TOKEN"

Or for an agent key, list the project it’s scoped to:

Terminal window
curl https://api.eastagiletracker.com/api/v1/projects \
-H "X-TrackerToken: ea_agent_xxxxx"

The API is JSON, REST-ish, versioned at /api/v1/. Same shapes for humans and agents.

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Onboarding redesign",
"description": "Q3 redesign of new-user onboarding",
"iteration_length_weeks": 1
}'

The response includes the project_id and the iteration defaults the server applied (iteration_start_day, initial_velocity, iteration_length_weeks); GET /projects/{id} returns the full settings.

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/stories \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Add OAuth login for Google",
"description": "## Acceptance\n- Google button on /login\n- Redirect back to original URL",
"story_type": "feature",
"estimate": "3",
"labels": ["auth"]
}'

estimate is the scale value’s label as a string — "3", or "13" on the Fibonacci scale — because it has to match a point on the project’s scale. A JSON number is rejected.

The transition endpoint validates the requested move and returns the allowed next states on error:

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/stories/$STORY_ID/transitions \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "to": "started" }'

The field is to (not to_state). If the move is illegal — say you tried to skip from unstarted straight to accepted — the response is 422 invalid_transition with structured error details:

{
"code": "invalid_transition",
"error": "Cannot move story from `unstarted` to `accepted`",
"details": { "from": "unstarted", "to": "accepted", "allowed": ["started"] }
}

This is one of the small things that makes the API agent-friendly: an agent can read details.allowed and choose the right next move without scraping prose.

rejected is terminal for the transition endpoint, which also refuses to: "rejected". Reject a delivered story with POST …/stories/{sid}/reject; put a rejected story back to work with POST …/stories/{sid}/restart, which works while the story is in the current iteration.

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/stories/$STORY_ID/comments \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "text": "Investigation done. Picking this up." }'

The comment is attributed to whoever owns the API key — if it’s an agent key, the comment’s author is the agent.

Write endpoints accept an Idempotency-Key header (attachment uploads excepted). Retry the same key with the same body within 24 hours, get the same response back. Retry the same key with a different body, get a 409 idempotency_conflict:

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/stories \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "name": "Refactor auth middleware", "story_type": "chore" }'

This is critical for agents in retry loops — crash mid-write, retry with the same key, no duplicate stories. A 5xx, 401, 403, 404, or 429 answer is never cached, so retrying after one runs the request again.

Move many stories at once. Each story is judged independently; one illegal move doesn’t fail the others.

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/stories/bulk_transition \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"story_ids": [101, 102, 103],
"to": "delivered"
}'

For agents that want to react to what humans do, poll the events endpoint:

Terminal window
curl "https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/events?since_id=$LAST_EVENT_ID&types=story.created,story.transitioned,comment.added" \
-H "X-TrackerToken: $TRACKER_TOKEN"

The response is { items, next_since_id, next_cursor }: each event carries the actor, the resource, and the change. Store next_since_id and pass it back as since_id on the next poll to resume where you left off (since is accepted as an older alias). No webhooks, no scraping, no missed events. The stream needs the member role — a viewer gets 403.

GET /projects/{id}/search?q=<query> runs a powerful full-text + structured search over the project’s stories. The query language is modelled on GitHub’s issue-search qualifiers — so syntax you (or an AI agent) already know from GitHub mostly carries over.

Terminal window
curl -G "https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/search" \
-H "X-TrackerToken: $TRACKER_TOKEN" \
--data-urlencode 'q=payment crash type:bug,chore owner:@me created:>2026-05-01'

The response is a JSON envelope, stories ranked by relevance when the query has free text and in board order otherwise:

{ "results": [ /* canonical story objects */ ], "total": 42, "limit": 50, "offset": 0 }

total is the full match count, not the page size. Page with limit (default 50, max 1000) and offset; order with sort=relevance (default), created, created_asc, updated, or state.

  • Free text matches a story’s title, reference, and description (full-text, stemmed and ranked). Wrap an exact phrase in "quotes".
  • Qualifiers are field:value. Comma-separate alternatives (OR within a field): type:bug,chore. Space-separate qualifiers (AND across them).
  • Negate any term or qualifier with a leading -: -label:wontfix.
  • Ranges for dates and points: inclusive a..b, or open-ended >x / <x.
QualifierExampleMatches
type:type:bug,chorestory type(s)
state:state:started,finishedworkflow state(s)
label:label:"my label"a label
epic:epic:"Checkout"stories in an epic
priority:priority:p1priority
points:points:3 · points:1..5 · points:>3estimate value or range
iteration:iteration:42iteration id
created: updated: started: completed: release:created:2026-05-01..2026-06-01 · updated:>2026-06-01a date or range (day-granular); release: is the story’s release date
owner: requester: follower: reviewer: commenter: mention:owner:claire · owner:@mea person by name or email — members and agents, mention: included; @me is you (with an agent key, the member who created the key)
has:blockerhas:blockerhas an open blocker
is:is:unestimated · is:icebox · is:backlog · is:blockeda flag

mywork: is an alias for owner: — mywork:me is owner:@me. The older scheduled: qualifier is retired and silently ignored; use release:.

Comma-OR (type:bug,chore) applies to the facet qualifiers; the people qualifiers (owner: requester: follower: reviewer: commenter: mention:) take a single value.

payment crash full text "payment" AND "crash"
"exact phrase" a phrase
type:bug,chore state:started bugs or chores that are started
owner:@me -label:wontfix mine, excluding the wontfix label
points:3..8 created:2026-05-01..2026-05-31 estimated 3-8, created in May
follower:tomas has:blocker tomas follows it and it's blocked
is:backlog updated:>2026-06-01 backlog items touched since Jun 1

The same query string drives the board’s search box (which opens a live results column) and this API — one grammar for humans and agents alike. Searching the content of comments, tasks, and blockers is on the roadmap; today free text covers the story’s own title, reference, and description.

The live OpenAPI 3 spec is at:

https://api.eastagiletracker.com/api/v1/openapi.json

Swagger UI is at:

https://api.eastagiletracker.com/api/v1/docs/

/openapi.json and /docs are unauthenticated — an agent can read the contract before it has a key. Once it holds a key, /api/v1/meta (which requires a valid key) returns its identity and the per-story-type transition graph; the reference-data lookups (/story_types, /story_states, /effort_scales, /priority_scales) are also unauthenticated. Together they let agents answer “what can I do here?” without trial-and-error 403s.

The served openapi.json carries request-body schemas for the write endpoints, including each field’s maxLength, so a client can validate before it sends. The Specification summarises the same shapes.

If you’re scripting a bulk migration:

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/import \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-F "source=pivotal" \
-F "file=@pivotal_export.csv"

Supported file sources: pivotal, jira, asana, gitlab, shortcut, trello, linear, plane, plane_json, eat (East Agile Tracker’s own export — the round-trip format). The multipart endpoint runs synchronously and answers with the result counts.

GitHub imports from the API instead of a file, via the JSON endpoint — no file, just the repository coordinates:

Terminal window
curl -X POST https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/import/json \
-H "X-TrackerToken: $TRACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source": "github",
"owner": "octocat",
"repo": "hello-world",
"token": "ghp_…",
"include_pull_requests": false,
"include_milestones": false,
"include_releases": false,
"include_dependencies": false
}'

The JSON endpoint is asynchronous: it answers 202 with { "import_id", "status" } and you poll GET /projects/{id}/imports/{import_id} until the job reaches done or failed. Only one import runs per project at a time — a second call while one is in flight is 409 import_already_running. The whole loop, with the job’s progress fields, is in Populate a project from a GitHub repo.

The token is optional on the wire, but the fetch itself always authenticates — it runs on GitHub’s GraphQL API, which has no unauthenticated tier. Omit token and the server substitutes its platform token: public repositories only, shared by every caller, and refused with import_github_shared_quota_low when its GraphQL budget drops below 500 points. A private repository, or a deployment that configured no platform token (import_github_no_token), needs yours. Whichever token runs, it is used only for the upstream GitHub calls and is never stored or echoed back. Full detail, including GitHub’s 60-request unauthenticated REST ceiling, is in Populate a project from a GitHub repo.

Dry-run preview. Add "dry_run": true (JSON) or -F "dry_run=true" (multipart) to any source. The import parses, resolves, and de-duplicates exactly as a real run, produces the same result counts (imported, skipped, errors, unmatched), then rolls everything back — nothing is written. On the JSON endpoint the counts arrive on the polled job, dry run or not.

Limits. An import file is capped at 10 MiB and 5,000 stories (400 import_too_large / import_too_many_records, nothing written); a GitHub import has no story cap. Re-importing a file is safe — rows already imported (matched by source id) are skipped, not duplicated.

Any project role can list the formats; downloading one needs the manager role:

Terminal window
# The registered export formats: { id, name, content_type, drops, includes_archived }
curl https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/export/formats \
-H "X-TrackerToken: $TRACKER_TOKEN"
# Download one format (eat is the full-fidelity round-trip CSV)
curl https://api.eastagiletracker.com/api/v1/projects/$PROJECT_ID/export/eat \
-H "X-TrackerToken: $TRACKER_TOKEN" -o project-export.csv

Interchange format ids: eat, jira, pivotal, shortcut, trello, asana, gitlab, linear, plane, plane_json, plus the document formats pdf and docx. Every attachment is downloadable as one zip from GET /projects/{id}/export/attachments.

All errors are JSON with at minimum:

{
"code": "invalid_transition",
"error": "Cannot move story from `unstarted` to `accepted`"
}

Many error responses also include a details object — details.fields (an array of offending field names) on validation_failed, and details.allowed (alongside from/to) on 422 invalid_transition. Use them. A 429 rate_limited carries a Retry-After header in the same JSON envelope.

List endpoints accept limit and cursor. Cursor is opaque; pass the next_cursor from the previous response. The limit cap is per endpoint — 200 on stories, comments, and projects, 500 on events, 1000 on the audit log; search pages with limit (≤ 1000) and offset. A plain (non-cursor) list that had to truncate its response says so in headers: X-Tracker-Pagination-Truncated, -Limit, -Offset, and -Next-Offset, which you pass back as offset= for the next page. There is no total-count header.