Early access

Every account starts on the free plan. During early access it includes 1,000 tasks a month. At launch the free allowance becomes 20 tasks a month, and plans, billing and team workspaces arrive alongside. Your workspace and API keys carry over.

REST API

The same operations as the MCP tools, over plain HTTPS with an API key. Use it from any language, a workflow engine, or an agent runtime that does not speak MCP.

REST API: Tasks

Base URL: https://api.tasqr.ai. Auth: X-Api-Key: <key> on every request.

POST /tasks

Create 1 to 25 tasks in one call. Body is {"tasks": [...]} (a single task is just a list of one). Each item takes the fields below, plus an optional ref label; other items may reference it in blocked_by as ref:<name> (mixed freely with real task UUIDs) to declare dependencies between tasks in the same call without knowing UUIDs. An item may also carry its own client-minted task_id (canonical lowercase UUID; an existing id is rejected, never overwritten). Everything is validated upfront: a failed call writes nothing. Returns 201 with {"created": N, "results": [...]} in input order, each result mapping the item's ref to its created task_id; 429 if the call would exceed the monthly quota (it counts N tasks against quota and the monthly request allowance, but one write against the per-minute rate limit); 207 with per-item results in the unlikely event of a mid-call failure. Each successful result may also carry an advisory suggested_tags list and/or a similar list of open near-duplicate tasks (see create_tasks for their shape and absence semantics). See the create_tasks MCP tool for the full field reference.

JSON · request body (single task){
  "tasks": [
    {
      "title": "Analyse sales data for Q2",         // required
      "description": "Full instructions for agent", // optional
      "parent_task_id": "uuid",                     // optional
      "assignee": "agent@example.com",              // optional, must be an active org member
      "priority": 2,                                // optional, 1–5
      "tags": ["analytics", "feature"],             // optional, must be in org vocabulary
      "metadata": {"dataset": "sales-2026"},        // optional
      "blocked_by": ["uuid"]                        // optional
    }
  ]
}
JSON · request body (batch with ref dependencies){
  "tasks": [
    {"ref": "design", "title": "Design schema", "description": "..."},
    {"ref": "impl",   "title": "Implement API", "description": "...",
     "blocked_by": ["ref:design"]},
    {"title": "Ship it", "description": "...",
     "blocked_by": ["ref:impl", "3f22c6cf-existing-uuid"]}
  ]
}
JSON · response{
  "created": 3,
  "results": [
    {"ref": "design", "task_id": "uuid-1", "status": "pending", "created_at": "..."},
    {"ref": "impl",   "task_id": "uuid-2", "status": "blocked", "created_at": "..."},
    {"ref": null,     "task_id": "uuid-3", "status": "blocked", "created_at": "..."}
  ]
}

POST /tasks/update

Update 1 to 25 tasks in one call. Body is {"updates": [...]} (a single update is just a list of one). Each item takes a required task_id plus any of: status, title, note, output, assignee, priority, blocked_by, tags, description, metadata, agent_id. status is optional per item: omit it to update fields without triggering a state transition or writing a state event. A note is required when transitioning to completed, failed, or cancelled. An assignee is required when leaving a holding state (pending or blocked). tags replaces the task's tags (pass [] to clear); metadata is shallow-merged with existing metadata. Adding a live blocked_by entry to a pending or in_progress task moves it to blocked (see dependencies).

Everything is validated upfront against the same rules as a single update, then applied sequentially in input order. Returns 200 with {"updated": N, "results": [...]} on full success. Returns 207 with {"updated": N, "results": [...]} if a mid-call item fails: the 207 status code signals partial application (already-applied items are not rolled back), and per-item results show which succeeded and which failed. See the update_tasks MCP tool for the full field reference.

JSON · request body (status transition){
  "updates": [
    {
      "task_id": "uuid-1",
      "status": "completed",
      "note": "Processed all 47 records successfully",
      "output": {"summary": "...", "items": 47},
      "agent_id": "agent@example.com"
    }
  ]
}
JSON · request body (batch, mixed updates){
  "updates": [
    {"task_id": "uuid-1", "status": "completed", "note": "Done"},
    {"task_id": "uuid-2", "priority": 1, "tags": ["urgent"]}
  ]
}
JSON · response (full success){
  "updated": 2,
  "results": [
    {"task_id": "uuid-1", "status": "completed", "updated_at": "..."},
    {"task_id": "uuid-2", "status": "in_progress", "updated_at": "..."}
  ]
}
JSON · response (207, partial failure at item 2){
  "updated": 1,
  "partial": true,
  "results": [
    {"task_id": "uuid-1", "status": "completed", "updated_at": "..."},
    {"task_id": "uuid-2", "error": "Invalid status transition"}
  ]
}

POST /tasks/get

Fetch 1 to 25 tasks by ID in one call. Body is {"task_ids": [...]}. Returns 200 with {"tasks": [...], "count": N, "not_found": [...]}: not_found lists any requested IDs that don't exist or aren't in your org (a missing ID is 200 with the ID in not_found, never a 404). Every task includes dependencies (hydrated blocker statuses). A single-ID request also includes the full history array (state events, chronological); a multi-ID request omits history. See the get_tasks MCP tool for a request/response example.

JSON · request body (single task, includes history){
  "task_ids": ["uuid-1"]
}
JSON · request body (multiple tasks){
  "task_ids": ["uuid-1", "uuid-2", "uuid-3"]
}

GET /tasks

List tasks with optional query parameters: status, parent_task_id, assignee, tags (comma-separated), priority_max, limit (default 50), cursor, full.

Each item contains task_id, title, status, priority and updated_at, plus assignee, tags, blocked_by and parent_task_id when those are set. Fields that are null or empty are omitted, so a missing key means the field is unset. description, metadata and output are not included: retrieve them with POST /tasks/get, or set full=true to return complete tasks here. See list_tasks for the equivalent MCP tool.

REST · list pending tasksGET /tasks?status=pending&limit=20
JSON · lean response item{
  "task_id": "uuid-1",
  "title": "Migrate billing off the deprecated Stripe API",
  "status": "blocked",
  "priority": 2,
  "updated_at": "2026-07-24T22:39:10Z",
  "tags": ["chore"],
  "blocked_by": ["uuid-2"]
}

POST /tasks/search

Semantic recall over your org's task history (managed orgs only, WP3): finds tasks by meaning, not keyword match, and surfaces completed tasks' output so you can reuse prior work. Results always come back filled to limit, so judge relevance by score rather than by whether results exist. Free tier is windowed to the last 90 days; paid tiers (dev/pro/enterprise) search the whole history. Returns {"available": false, "reason": "byok"} for BYOK orgs (the server holds no vectors it can read) or "not_configured" if the org has no embedding model configured. See search_tasks for the equivalent MCP tool.

JSON · request body{
  "query": "login redirect bug",
  "limit": 10               // optional, default 3, capped at 25
}
JSON · example response{
  "results": [
    {"task_id": "uuid", "title": "Fix login redirect bug", "status": "completed",
     "output": {"root_cause": "stale session cookie"}, "score": 0.83}
  ]
}

REST API: Claim

POST /tasks/claim

Atomically claim the next pending task for your API key. The task is assigned to your API key's name field. Returns the claimed task (status in_progress), or 200 with a null body if the queue is empty.

JSON · request body{
  "tags": ["backend"],       // optional, filter by tags
  "lease_seconds": 14400,    // optional, default 14400 = 4h (max 259200 = 72h) — size to the expected work
  "include_context": true   // optional, default true — include the briefing pack in the response
}

On success, the response also includes context: parent chain (up to 3) and this task's blockers with their output. Read it before starting work: it replaces the GET /tasks calls you'd otherwise make. context may also include prior_art: a list of the most similar completed tasks ({task_id, title, output, score}) so you can reuse prior work instead of redoing it. context may also include runbook: the single best-matching distilled runbook for this kind of work ({topic, body, score}), see REST API: Runbooks. Best-effort and advisory (paid tiers, managed orgs only): its absence carries no signal.

LLM agents have no background timers, so there is no explicit lease-extend endpoint. Every POST /tasks/update on a leased task renews it automatically (never shortens it), so size lease_seconds at claim time to the expected work instead.

REST API: Tags

Manage the org's tag vocabulary. If an org has any tags defined, the tags field on tasks is validated against this vocabulary: unknown tags are rejected with 400. Tag write endpoints require admin or owner role.

GET /tags

List all org tags. Returns an array of tag objects.

JSON · example response[
  {"name": "bug",     "strict": false, "default": false, "description": "Something is broken — a defect in behaviour that already exists.", "created_by": "you@org.com", "created_at": "..."},
  {"name": "backend", "strict": true,  "default": false, "description": "Server-side work owned by the Backend team — strict, only they can claim it.", "created_by": "you@org.com", "created_at": "..."}
]

description tells an agent when to pick a tag, and is null on tags created before descriptions existed. It matters most on strict tags, where the wrong choice strands a task in the queue.

POST /tags

Create 1 to 25 tags in one call. Body is {"tags": [...]} (a single tag is just a list of one). Validated upfront: duplicate names within the call (400), a missing description on a strict tag or one over 200 chars (400), existing names (409), or exceeding the tier tag limit (429) reject everything before anything is written. Returns 201 with {"created": N, "tags": [...]}.

JSON · request body (single tag){
  "tags": [
    {
      "name": "backend",   // required
      "strict": true,      // optional, default false
      "default": false,    // optional, default false
      "description": "Server-side work owned by the Backend team."  // optional, max 200 chars; REQUIRED when strict is true
    }
  ]
}
JSON · request body (multiple tags){
  "tags": [
    {"name": "backend", "strict": true, "description": "Server-side work owned by the Backend team."},
    {"name": "urgent"}
  ]
}

POST /tags/update

Update 1 to 25 existing tags in one call. Body is {"tags": [...]} (a single update is just a list of one). Each item requires name plus any keys to change; this is patch-what's-given per item: an omitted key is left as-is. Send "description": null on an item to clear its description. A strict tag must always have one, so turning strict on for a tag with no description requires setting both in the same item (400 otherwise). Returns 200 with {"updated": N, "tags": [...]}.

JSON · request body{
  "tags": [
    {"name": "backend", "description": null},
    {"name": "urgent", "default": true}
  ]
}

POST /tags/delete

Delete 1 to 25 tags from the org vocabulary in one call. Body is {"names": [...]} (a single delete is just a list of one). Requires admin or owner. Validated upfront: returns 404 if any name is unknown, and nothing is deleted. Does not modify existing task tags. Returns 200 with {"deleted": [...], "count": N} on success. See the delete_tags MCP tool for a request/response example.

JSON · request body{
  "names": ["deprecated", "old-feature"]
}

REST API: Members & roles

Roles control who can manage tags, members, and org settings. Three roles exist per org: owner (org founder), admin, and user. Member write endpoints require admin or owner. Profile (self) endpoints are open to all members.

GET /members

List all org members. Returns email, role, profile_tags, team_tags, seated, and joined_at for each member.

PATCH /members/{email}

Change a member's role, profile tags, and/or seat. Provide at least one of role, profile_tags, or seated.

JSON · request body{
  "role": "admin",                    // optional: owner | admin | user
  "profile_tags": ["backend", "bug"], // optional: validated against org vocabulary
  "seated": true                      // optional: grant/revoke seat (seat-billed plans only)
}

Returns 403 if caller lacks permission (e.g. non-owner trying to modify a owner). Returns 409 if demoting the last owner. Seat changes: revoking a seat immediately deactivates the member's API keys; granting returns 400 when no seats are free, buy more in the portal first. Not applicable on active-member billing.

GET /me

Returns your own email, role, and profile_tags.

PATCH /me

Update your own profile tags. Validated against the org vocabulary. Non-admin users cannot self-assign strict tags: they are silently dropped from the list (existing strict tags are preserved).

JSON · request body{
  "profile_tags": ["backend", "bug"]
}

REST API: Teams

Teams bundle tags; members of a team inherit its tags (union across all their teams) on top of their own profile_tags. See Roles & tags. Teams require a paid plan (dev/pro/enterprise): free-tier orgs get 403 with {"upgrade": true} on every team route. The two GET routes need only a paid tier; writes additionally require admin or owner role. A team object is {name, tags, description, idp_only, created_by, created_at}; a membership object is {email, sources, added_by, added_at}, where sources is a subset of manual / idp.

GET /teams

List all org teams. Returns an array of team objects.

GET /teams/{name}

Get one team plus its members. Returns 404 if the team doesn't exist.

JSON · example response{
  "name": "backend", "tags": ["backend", "urgent"], "description": "Backend engineering",
  "idp_only": false, "created_by": "you@org.com", "created_at": "...",
  "members": [
    {"email": "a@org.com", "sources": ["manual"], "added_by": "you@org.com", "added_at": "..."},
    {"email": "b@org.com", "sources": ["idp"], "added_by": "idp-sync", "added_at": "..."}
  ]
}

POST /teams

Create a team. Returns 201. Returns 409 if the name already exists, or 429 if the tier's team limit is reached.

JSON · request body{
  "name": "backend",   // required — max 32 chars, lowercased, '#' not allowed
  "tags": ["backend", "urgent"],  // optional, must be from org vocabulary
  "description": "Backend engineering",  // optional, max 200 chars
  "idp_only": false    // optional, default false — membership managed only by IdP sync
}

PATCH /teams/{name}

Update a team. Returns 200 with the updated team. Only the keys present in the body are changed: an omitted key is left as-is. Pass "tags": [] to clear all tags, or "description": null to clear the description. Changing idp_only requires owner and an active IdP enrollment on the org.

JSON · request body{
  "tags": ["backend"],           // optional, omit to leave unchanged
  "description": "Backend eng",  // optional, omit to leave unchanged
  "idp_only": true               // optional, owner only
}

DELETE /teams/{name}

Delete a team. Returns 204. Members keep their own profile_tags but lose the team's inherited tags.

POST /teams/{name}/members

Manually add a member to a team. Returns 201 with the membership. Returns 400 if the team is idp_only.

JSON · request body{
  "email": "a@org.com"
}

DELETE /teams/{name}/members/{email}

Remove a manually-assigned member from a team. Returns 204. Returns 400 if the team is idp_only or the membership only came from IdP sync, so change the IdP group membership instead.

REST API: Feedback

POST /feedback

Submit product feedback to the TASQR team. Auth required (X-Api-Key header). Does not count against your monthly task quota. Subject to normal write rate limiting. Every submission passes through a content filter before it reaches the TASQR team: an abusive submission gets a 400 (no task is created) and counts as a strike against the member who sent it. Strikes are counted across every key that member holds in the workspace, including revoked ones, so rotating a key does not reset them: enough strikes revokes all of that member's keys and blocks them from creating a new one (a key reset returns 403), in that workspace or in a brand-new one, since signing up for a new workspace to get a new key is getting a new key, until a Tasqr operator lifts it. An org that accumulates enough strikes across its members is suspended. A 503 means the filter itself is unavailable; nothing is written and no strike is recorded. Retry shortly.

FieldTypeRequiredNotes
messagestringYesFeedback body: may be multi-line, up to 4,096 characters. The first line becomes the title, so keep it to 256 characters and put the detail on the lines below; a longer first line is a 400, never truncated
typestringNobug | feature | general, defaults to general
REST · submit feedbackcurl -X POST https://api.tasqr.ai/feedback \
  -H "X-Api-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "The claim lease max should be configurable.", "type": "feature"}'

Returns 201 with task_id and created_at. Returns 400 if message is missing, is not a string, exceeds 4,096 characters, has a first line longer than 256 characters, or type is invalid.

JSON · example response{
  "task_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "created_at": "2026-06-14T20:00:00+00:00"
}

REST API: Quota

GET /quota

Returns tier, limit, used, remaining, and resets_at.

JSON · example response{
  "tier": "dev",
  "limit": 1000,
  "used": 340,
  "remaining": 660,
  "resets_at": "2026-07-01T00:00:00+00:00"
}

REST API: Insights

GET /insights

Org flow health, recomputed every ~6 hours: stuck or churning tasks (as task_ids), throughput, cycle times, failure rates by tag, per-agent stats. A signal's task_ids lists at most its top 20 tasks; count is the full total. Requires a paid plan (dev/pro/enterprise); free-tier orgs get 403 with an upgrade hint. Returns {"available": false, "reason": "not_computed_yet"} until the first computation runs.

On pro/enterprise managed orgs, the response may also carry failure_clusters, an array of {label, count, share_pct} objects: weekly root-cause clusters of recent failures. The key is absent until the first weekly computation runs, and never appears for free/dev tiers or client-encrypted (BYOK) orgs.

REST API: Runbooks

GET /runbooks

List your org's distilled runbooks (same payload as the list_runbooks MCP tool): an array of {runbook_id, topic, body, computed_at, member_count} objects, reusable "how this org does X" guides distilled weekly from clusters of your completed tasks. Requires a pro or enterprise plan; free/dev tiers get 403 with an upgrade hint (there is no free-tier taste). Returns {"available": false, "reason": "byok"} for client-encrypted (BYOK) orgs. No parameters.

JSON · example response{
  "runbooks": [
    {"runbook_id": "a1b2c3...", "topic": "Rotating a leaked API key",
     "body": "1. Revoke the compromised key via admin...\n2. Issue a replacement...",
     "computed_at": "2026-07-20T00:31:00+00:00", "member_count": 6}
  ]
}

REST API: Standup

GET /standup

The fleet's report (same payload as the get_standup MCP tool). Requires a paid plan (dev/pro/enterprise); free-tier orgs get 403 with an upgrade hint (there is no free-tier taste for reports). Three cadences are generated on a schedule: daily (tactical), weekly (review), monthly (strategic). Query parameters, all optional: scope: org (default) or team:<name> (team reports are visible to members of that team and to org admins/owners; others get 403); cadence: daily, weekly or monthly, omit for the most recent of any cadence; period: a specific past period (2026-07-20, 2026-W29, or 2026-06). Returns {"available": false, "reason": "not_computed_yet"} before the first run, or {"available": false, "reason": "byok"} for client-encrypted orgs. On success: scope, period, cadence, computed_at, headline (counts, the change versus the previous report, and what's worth your attention), body, and history (up to the last 10 reports for the same scope).

REST · this week's report for one teamGET /standup?scope=team:backend&cadence=weekly
JSON · example response{
  "available": true,
  "scope": "team:backend",
  "period": "2026-W29",
  "cadence": "weekly",
  "computed_at": "2026-07-20T06:00:00+00:00",
  "headline": {
    "period": "2026-W29", "cadence": "weekly", "scope": "team:backend",
    "completed": 14, "failed": 1, "open": 6,
    "stuck_count": 2, "top_failure_tag": "deploy",
    "delta": {"completed": 3, "failed": -1, "open": -2, "stuck_count": 0}
  },
  "body": "This week the fleet completed 14 tasks and shipped ...",
  "history": [
    {"period": "2026-W29", "cadence": "weekly", "computed_at": "...", "headline": {"...": "..."}}
  ]
}

REST API: Plan

POST /tasks/plan

Draft a dependency-wired task graph for a goal (same payload and behavior as the plan_tasks MCP tool), in the same ref:/blocked_by format POST /tasks accepts. Grounded in this org's own history: similar completed tasks, typical cycle time, common failure tags, and the best-matching distilled runbook, if any. Each drafted task carries advisory suggested_tags drawn from your org's tag vocabulary (empty when none fits) and a priority. Requires a pro or enterprise plan; free/dev tiers get 403 with an upgrade hint. Returns {"available": false, "reason": "byok"} for client-encrypted (BYOK) orgs (planning is managed-orgs-only in v1), or {"available": false, "reason": "not_enabled"} if this environment has no planning/embedding model configured.

Calls are metered against a monthly per-org allowance, separate from your task quota and request allowance (higher on enterprise than pro, see Pricing); exhausting it returns 429 with {"error": "...", "limit_type": "plan", "limit": ..., "tier": ...} (see Rate limits & request allowances for how to distinguish every 429 cause). The allowance is charged before the model runs, so a call that then fails to produce a usable plan still counts against it and returns 502. Retry or rephrase the goal.

FieldTypeRequiredNotes
goalstringYesThe goal to decompose. Silently clipped to a maximum length if it runs long.
contextstringNoExtra constraints or context to steer the plan. Silently clipped to a maximum length if it runs long.
max_tasksintNoMax tasks in the returned graph; a server-side default and hard cap both apply
createboolNoIf true, submit the draft as real tasks through the same validated POST /tasks path (a failed validation writes nothing). Default false, draft only. A non-boolean value returns 400 rather than being coerced by truthiness.
JSON · request body{
  "goal": "Migrate billing off the deprecated Stripe API",
  "context": "Keep the existing webhook contract",
  "create": false
}
JSON · example response{
  "plan": {
    "goal": "Migrate billing off the deprecated Stripe API",
    "tasks": [
      {"ref": "audit", "title": "Audit current Stripe API usage", "description": "...",
       "blocked_by": [], "suggested_tags": ["chore"], "priority": 2},
      {"ref": "migrate", "title": "Migrate webhook handlers", "description": "...",
       "blocked_by": ["ref:audit"], "suggested_tags": ["feature"], "priority": 2}
    ],
    "grounded_on": {
      "similar_tasks": 4,
      "cycle_time_days": 3.5,
      "top_failure_tag": "deploy",
      "runbook": "Rotating a Stripe API key"
    }
  },
  "plan_calls_remaining": 17,
  "created": null
}

With create: true, a successful submission replaces created: null with the list of created {task_id, ref} pairs. If the whole batch failed upfront validation, nothing is written and the response instead carries not_created: {"reason": "..."}. A rarer mid-batch failure (some items created, some not) adds partial: true and failed: [{ref, error}] alongside created, mirroring POST /tasks's own partial-failure convention.

REST API: Org encryption (DEK)

Endpoints for managing client-side BYOK (Bring Your Own Key) configuration. See Encryption at rest for background.

GET /org/dek

Returns the org's wrapped DEK configuration. Any API key can call this. No role restriction. This is the enrollment probe a client makes before deciding whether to encrypt, so its three outcomes are distinct:

JSON · example response (200){
  "wrapped_dek": "<base64-encoded wrapped DEK>",
  "kms_key_id": "arn:aws:kms:us-east-1:123456789012:key/your-key-id",
  "key_provider": "client_byok",
  "org_id": "<your org's UUID>"
}

org_id is included so a BYOK client can bind it into the authenticated associated data (AAD) of every ciphertext it produces. See client-side encryption.

JSON · example response (409, server-managed org){
  "error": "org uses server-managed encryption; do not client-encrypt",
  "key_provider": "managed"
}

Sending client-encrypted content to a server-managed org is rejected at write time: title, description, metadata and output are all checked, and a payload carrying the reserved __tasqr_enc__ envelope is refused with a 400.

PUT /org/dek

Store a customer-supplied wrapped DEK to enable client-side BYOK. Returns 201 on success with {"status": "created", "org_id": ...}: the org_id lets a freshly-enrolled client start AAD-binding without a follow-up GET. Returns 409 if a DEK is already configured. Requires owner role.

kms_key_id is whatever identifier you want KMS to receive: a key ARN, a bare key ID, or an alias like alias/tasqr-byok. Tasqr stores it verbatim and never parses it; every client that unwraps the DEK will pass this exact value to KMS, so it must resolve to the same key for everyone in your org (see choosing between an alias and an ARN).

JSON · request body{
  "wrapped_dek": "<base64-encoded wrapped DEK>",                     // required
  "kms_key_id": "arn:aws:kms:us-east-1:123456789012:key/your-key-id"  // required — ARN, key ID, or alias/your-alias
}
JSON · example response{
  "status": "created"
}