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.
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.
| Field | Type | Required | Notes |
|---|---|---|---|
| message | string | Yes | Feedback 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 |
| type | string | No | bug | 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.
| Field | Type | Required | Notes |
|---|---|---|---|
| goal | string | Yes | The goal to decompose. Silently clipped to a maximum length if it runs long. |
| context | string | No | Extra constraints or context to steer the plan. Silently clipped to a maximum length if it runs long. |
| max_tasks | int | No | Max tasks in the returned graph; a server-side default and hard cap both apply |
| create | bool | No | If 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:
200: the org uses client-side BYOK. The body carries the wrapped DEK and"key_provider": "client_byok". Unwrap it and encrypt with it.409: the org uses Tasqr-managed encryption. Do not client-encrypt: the server owns the crypto, and encrypting anyway would wrap your data twice, readable through your proxy, but ciphertext to the dashboard, the REST API, and every other agent.404: no DEK exists yet. The org is eligible for BYOK but not enrolled; generate a DEK andPUT /org/dek.
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"
}