Skip to content
← Virtual Assistants for AI Businesses

Agent API — Virtual Assistant Delegation

Delegate tasks from your AI agent to a real human virtual assistant. This API enables seamless AI agent handoff for human-in-the-loop tasks that LLMs cannot complete autonomously. Category: humans working for the AI · HITL ranking.

Base URL: https://taskbullet.comRemote MCP: https://taskbullet.com/api/agent-mcpAuth: x-api-key: tb_live_…Rate limit: 60 req / 60s / key on this instanceOpenAPI (Swagger) Spec →MCP Server (npm) →MCP Server (GitHub) →Public site MCP →

Quickstart

Issue an API key from your account settings and send your first task in under a minute.

curl -X POST https://taskbullet.com/api/v1/delegate \
  -H "x-api-key: tb_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "task": "Research and compile a list of the top 10 AI newsletters with subscriber counts",
    "context": "We are preparing a partnership outreach deck for Q3.",
    "priority": "normal",
    "webhookUrl": "https://your-app.com/hooks/taskbullet"
  }'
// Response
{
  "success": true,
  "taskId": "atask_a3f9c2d1e4b5a6f7",
  "status": "received",
  "message": "Filed. Waiting for you to start it on Work. https://taskbullet.com/dashboard?tab=work"
}

Remote MCP (Grok Bot / Cursor / OpenClaw)

Cloud agents cannot run npx @taskbullet/mcp-server. Use the hosted streamable-HTTP MCP instead. This is a different server from the public site MCP— it requires a key and only exposes VA handoff tools.

URL:  https://taskbullet.com/api/agent-mcp
Auth: x-api-key: tb_live_…   (Authorization: Bearer tb_live_… also accepted)

Tools:
  get_balance    → GET  /api/v1/balance
  delegate_task  → POST /api/v1/delegate
  list_tasks     → GET  /api/v1/tasks
  get_task       → GET  /api/v1/tasks/{taskId}

Call get_balance before proposing spend. When you cannot finish (phone, 2FA,
captcha, physical world, subjective QA, long manual work), confirm with the
user then call delegate_task. Poll get_task until completed, failed, or cancelled, or pass webhookUrl.
Customer cancel is cancelled and does not send the completion webhook.
Never put passwords, OTP, recovery keys, cookies, or API keys in the payload.
Never invent VA results.

Grok Bot

Settings → Plugins → add a custom MCP (or TaskBullet when listed in Marketplace). Server URL https://taskbullet.com/api/agent-mcp. Paste the tb_live_ key ( plugin variable TASKBULLET_API_KEY). This is API-key auth only — not OAuth. If an OAuth connect card appears, dismiss it and paste the key; tools already work with the header. Call get_balance then ask the user before delegate_task (it uses real hours) unless they already said to send it. Same URL works on grok.com/connectors → New Connector → Custom.

OpenClaw

Other lobsters install the skill with openclaw skills install @griffmurder/taskbullet. The lobster cannot sign up or mint a key. It tells the human:

  1. No account → start free (10 hours) /get-started?source=openclaw
  2. Issue tb_live_… at /dashboard/settings/api-keys (shown once). Paste into Gateway env TASKBULLET_API_KEY plus this Agent MCP. Never put the key in chat.
  3. Then get_balance / delegate_task. Hours deduct when work is tracked (nearest 15 minutes). Keyed account with no hours (402) → buy hours, not another trial. Missing Basecamp queues the task — no 409.

Grok Build / Cursor config

[mcp_servers.taskbullet_agent]
url = "https://taskbullet.com/api/agent-mcp"
headers = { "x-api-key" = "tb_live_YOUR_KEY" }
enabled = true
{
  "mcpServers": {
    "taskbullet-agent": {
      "url": "https://taskbullet.com/api/agent-mcp",
      "headers": { "x-api-key": "tb_live_YOUR_KEY" }
    }
  }
}

Issue a key at /dashboard/settings/api-keys. Local stdio (npx @taskbullet/mcp-server) remains available for one laptop; it is not the Grok Bot path.

Authentication

All /api/v1/ requests must include your API key in the x-api-key header. Keys begin with tb_live_ and are issued from your settings page.

One-time reveal: Your full API key and webhook secret are shown only once at creation time. Store them in a secrets manager (e.g. process.env) immediately.
// All v1 endpoints
x-api-key: tb_live_<64 hex chars>
POST

/api/v1/delegate

File a brief for a TaskBullet VA. Send an Idempotency-Key header. The same account and key returns the original task. The brief stays off the board until you start it on Work. After you start it, the dedicated VA already stored on the account is the assignee. If none is stored, the account manager staffs it.

Request body

FieldTypeDescription
taskrequiredstringWhat you want done. Max 5,000 chars. Used as the Basecamp todo title (first 240 chars).
contextstringAdditional background for the VA (max 8,000 chars). Never include passwords, OTP codes, recovery keys, cookies, or API keys — those are rejected.
priority"low" | "normal" | "high" | "urgent"Defaults to "normal".
dueDatestringFree-form due date hint (e.g. "EOD Friday"). Appended to task description.
webhookUrlstringPublic HTTPS URL. TaskBullet POSTs when you close the job (client_complete). A VA checkbox does not send this webhook. A customer cancel does not send it. Localhost and private IPs are rejected.
sourceAgentstringName of the calling agent for audit trails (e.g. "claude-3-opus").
maxHoursnumberSpend cap in hours. Defaults to 0.5. Max 8. The VA/AM stop and ask before exceeding. Actual tracked time is billed, not this cap.

Response 200 OK

{
  "success": true,
  "taskId": "atask_a3f9c2d1e4b5a6f7",   // stable ID — use to poll status
  "status": "received",
  "message": "Filed. Waiting for you to start it on Work. https://taskbullet.com/dashboard?tab=work"
}

The initial "received" status is an acknowledgment that the request was accepted. It is only returned by this endpoint and will not appear in subsequent polls — the task transitions to "pending" as soon as it is queued internally (typically within a second).

402 is only a live Ops read that is not above zero. It includes hoursRemaining and addHoursUrl so the agent can send the client to add hours without guessing. A cap that does not fit open caps is 422 with hoursRemaining and capRoom. Hours remain. Lower maxHours to capRoom or wait. That response has no addHoursUrl.

GET

/api/v1/balance

Remaining hours and cap room for this API key. Call before delegate_task. Returns only hour fields — no other account data. canDelegate false while hoursRemaining is above zero means lower maxHours to capRoom or wait. It is not a buy-hours signal. capRoom is omitted when Ops did not return a live figure.

{
  "hoursRemaining": 8.5,
  "canDelegate": true,
  "addHoursUrl": "https://taskbullet.com/packages-pricing",
  "hoursUnavailable": false,
  "capRoom": 8.5
}
GET

/api/v1/tasks

List delegated tasks for your account. Optionally filter by status.

Query params

ParamValuesDescription
status"pending" | "in_progress" | "completed" | "failed" | "cancelled" | "all"Defaults to "all". Returns up to 50 tasks.
// GET /api/v1/tasks?status=completed
{
  "tasks": [
    {
      "taskId": "atask_a3f9c2d1e4b5a6f7",
      "title": "Call the vendor about the invoice",
      "status": "completed",
      "priority": "normal",
      "createdAt": "2026-05-28T14:00:00.000Z",
      "completedAt": "2026-05-29T09:15:00.000Z",
      "result": "Vendor confirmed payment posted today."
    }
  ]
}
GET

/api/v1/tasks/:ref

Poll the status of a specific task by its taskId returned from /delegate. Stop when status is completed, failed, or cancelled. A customer cancel is cancelled. It does not send the completion webhook.

// GET /api/v1/tasks/atask_a3f9c2d1e4b5a6f7
{
  "taskId": "atask_a3f9c2d1e4b5a6f7",
  "status": "completed",        // pending | in_progress | completed | failed | cancelled
  "priority": "normal",
  "createdAt": "2026-05-28T14:00:00.000Z",
  "result": "Completed the research. List attached in Basecamp.",
  "completedAt": "2026-05-29T09:15:00.000Z"
}
StatusMeaning
pendingFiled, not on the board
in_progressOn the board. The VA has not delivered, and you have not closed it.
completedYou closed it. Only client_complete.
failedThe existing failed status only. A missing board card is a hold, not a failure.
cancelledYou cancelled it. Stop polling. Not a failure, and not a hold. No completion webhook.

Webhooks

If you pass webhookUrl when delegating, TaskBullet will POST a signed payload when you close the job (client_complete). A VA checkbox does not send this webhook. A customer cancel does not send it. Verify the signature before processing.

Payload

POST https://your-app.com/hooks/taskbullet
Content-Type: application/json
x-taskbullet-signature: <hex>
x-taskbullet-timestamp: <unix seconds>

{
  "event": "task.completed",
  "taskId": "atask_a3f9c2d1e4b5a6f7",
  "taskTitle": "Book a flight to Austin",
  "status": "completed",
  "result": "Booked on Delta flight 1423, confirmation #XYZ.",
  "timestamp": "2026-05-29T09:15:00.000Z"
}

Signature verification (Node.js)

import crypto from 'crypto';

function verifyWebhook(req, webhookSecret) {
  const sig   = req.headers['x-taskbullet-signature'];
  const ts    = req.headers['x-taskbullet-timestamp']; // Unix seconds
  const body  = req.rawBody; // RAW request body string — NOT JSON.stringify(parsed)

  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(`${ts}.${body}`)
    .digest('hex');

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(sig,      'hex'),
    Buffer.from(expected, 'hex'),
  );
}

Important: x-taskbullet-timestamp is Unix seconds (not milliseconds), and rawBody must be the exact bytes received from the request — not a re-serialized JSON string. Most frameworks expose this as req.rawBody or via a buffer-capture middleware. Re-parsing and re-stringifying will produce a different byte sequence and the signature will not match.

The webhookSecret is the value shown at API key creation time — separate from the API key itself. Retries are attempted up to 5 times (every ~15 min) before giving up.

Errors

StatusCause
400Missing or invalid body, oversize fields, or secrets in title/description
401Missing, invalid, or revoked API key
402Live Ops hours are not above zero — addHoursUrl is /packages-pricing (not trial)
422Requested cap does not fit open caps. Hours remain. Lower maxHours to capRoom or wait. No addHoursUrl.
413Request body larger than 32 KiB
429Rate limit exceeded (60 req / 60s on this instance; it does not hold across instances) — respect Retry-After
500Internal server error

Error responses include a JSON body with error. 402 also includes hoursRemaining and addHoursUrl. 422 also includes hoursRemaining and capRoom.

Ready to integrate?

Issue your API key from the dashboard settings, then connect your AI agent or automation workflow.

Get your API key