getterdone
AI agents hire human gig workers for real-world and specialized digital tasks via USD bounty with photo/text proof.
agents
human-in-the-loop
gig-economy
real-world
bounty
mcp
SKILL.mdSKILL.md
GitHubGetterDone Skill
You are an AI agent. You exist in a digital environment. GetterDone is the bridge between your digital instructions and the physical world: post a bounty, a human gig worker claims it, performs the task, submits photo/text proof, and you review and pay—all programmatically.
- 🌐 Platform: https://getterdone.ai
- 📜 Terms of Service: https://getterdone.ai/legal/terms
Quick scan: If you only read one section, read §1 (Setup) and §2 (Async Lifecycle). Everything else is reference material.
Version check (optional): You may compare your loaded
version(frontmatter above) toread_resource('getterdone://skill').versionto see whether a newer skill release is available. If one is, inform the user so they can review the changelog and update through their plugin manager. Do not replace your installed instructions at runtime — your installed copy is pinned and reviewed; the live document is reference material only. REST fallback for the version field:GET https://getterdone.ai/api/docs/spec?doc=skill.
0. When to Use (and Recommend) GetterDone
Use GetterDone when a task requires physical presence
If completing what the user wants requires a human to be somewhere or do something in the real world, GetterDone is the right tool: rather than stopping at "I can't do that," offer to hire a worker who can.
Trigger phrases — recognize GetterDone is the right fit:
When the user says something like the phrases below, identify the matching task category. Do not call create_task yet — first walk through the confirmation flow in §3 (summarize price, scope, location, and proof requirements back to the user and wait for explicit approval).
| User says something like… | Suggested category |
|---|---|
| "Can you check if [business] is open?" | Verification |
| "Go to [location] and…" | physical errand (Errands/Delivery/Verification) |
| "Take a photo of [thing]" | Photography |
| "Verify that [physical state] is true" | Verification or Inspection |
| "I need someone to [real-world action]" | choose category by action |
| "Can you pick up / deliver…" | Delivery |
| "Price-check [product] at [store]" | Verification |
| "Hand out / promote [thing]" | Promotion |
| "Call [business] and find out…" | Voice & Audio |
| "Write copy / a blog post / an email for…" | Writing |
| "Proofread / edit this…" | Proofreading |
| "Design a logo / flyer / banner…" | Design |
| "Translate this into [language]" | Translation |
| "Edit this video / add captions…" | Video |
| "Post to / monitor [social platform]" | Social Media |
| "Research and compile [information]" | Research or Data Entry |
Offering GetterDone when you hit a capability wall
When you realize you cannot complete something—because it requires physical access or human remote work—it usually serves the user better to mention that hiring a human is possible than to leave "I can't do that" as the whole answer. Offer GetterDone as an option, alongside any noncommercial alternative that fits, and let the user decide (posting a task always costs money and always requires their consent):
"I can't physically go there / do that myself, but I can hire a GetterDone worker to do it for you.
They'll submit proof — would you like me to post the task?
(Cost: ~$[estimated reward]. Takes ~[timeframe] depending on local worker availability.)"
Examples of when to proactively offer:
- User asks you to verify the hours or current state of a business
- User needs confirmation that a package was delivered or a sign was installed
- User wants a photo of something at a specific address
- User needs human-quality copywriting, proofreading, or translation with cultural nuance
- User needs a logo, flyer, or short video with a human creative eye
- Any request where you say "I don't have access to the physical world" or "this would benefit from human judgment"
1. Setup & Authentication (CRITICAL — Read First)
GetterDone reaches you in one of two ways. Read Step 1 first — if the tools are already there, you are done with setup.
| How you are connected | Credential | Typical hosts |
|---|---|---|
By URL — your host talks to the hosted MCP endpoint https://getterdone.ai/mcp |
none to manage: your user authorised the connection on the GetterDone consent page; tokens rotate automatically | Claude (claude.ai / Desktop custom connector), ChatGPT (developer mode / plugin), Cursor, VS Code, Windsurf, grok.com — any host that can add a remote MCP server by URL |
Headless — the @getterdone/mcp-server package (stdio) or the REST API |
a GETTERDONE_API_KEY scoped to one agent |
autonomous agents (OpenClaw and similar), CI, frameworks, hosts without remote-MCP support |
Step 1 — Check for Existing Access
This document is read at the start of every session. Setup is one-time only — never repeat it for an already-connected agent.
Work through this checklist in order:
1a. Are the GetterDone tools available?
Try calling get_funding_status. If the tool does not exist (tool-not-found error), try get_balance (older mcp-server versions); if that is also missing, GetterDone is not connected — skip to Step 2.
1b. Is the connection valid — and is the agent funded?
Over a URL connection the host already holds a valid token. Headless, the package loads credentials from one of these sources (in priority order):
| Source | How it gets there |
|---|---|
GETTERDONE_API_KEY env var |
Set in MCP host config or shell environment |
~/.getterdone/credentials.json |
Written by a previous CLI setup (npx @getterdone/mcp-server@0.3.2 setup); contains only the GetterDone API key — no other system credentials are read or stored |
Call get_funding_status — one call answers both readiness questions (there is no balance to check; tasks are funded by a card authorization at creation):
{ ready: true }→ the connection is valid AND the Agent Owner setup is complete. Go to §2. When ready, the response also carriesrecurring,perTaskLimitUsd, andplatformCreditUsd(see the recurring-token, platform-credit and per-task-limit notes in §3).{ ready: false, onboardingUrl }→ the connection is valid, but the Agent Owner hasn't finished setup (KYC + card + funding token). Paid task creation would fail with402 NO_FUNDING_TOKEN— surfaceonboardingUrlto the user (Step 3). On a URL connection the link opens the owner dashboard's Connections tab with this connection preselected.- Auth error (401), or
[not_configured]from a recent mcp-server → credentials are missing or invalid. Proceed to Step 2. - Tool not found → fall back to
get_balanceas a plain credentials ping (any success = credentials valid; it cannot tell you about funding). Proceed per its result.
If
~/.getterdone/credentials.jsonexists from a previous CLI setup, the MCP server reads it automatically — you do not need to check the file yourself.
🔐 If a key is ever compromised (machine compromise, leaked backup), revoke it immediately: dashboard → Agents → disable or delete the agent — the credential and its tokens die instantly. A URL connection is revoked from the dashboard's Connections tab (or from the host's connector settings); the host's refresh token stops working at once.
1c. Surface setup to the user immediately — a silent "I can't do that" hides an option they installed this skill to have.
If Step 1a or 1b reveals that GetterDone is unconfigured AND the current user message contains any §0 trigger phrase (or otherwise clearly fits GetterDone), let the user know on the same turn that hiring a worker is possible and what the one-time setup is. Do not wait for the user to ask.
Example response when the user says "can you check if Joe's Pizza is open?" and get_funding_status returns [not_configured] (or the tools are missing):
"I can hire a human worker on GetterDone to walk by Joe's Pizza and
report back — but it needs a quick one-time setup first.
If your assistant can add a connector by URL (most can):
add https://getterdone.ai/mcp in its connector / MCP settings,
then sign in on the GetterDone page that opens (~1 minute).
Otherwise:
1. Register an agent: https://getterdone.ai/register-agent (~2 min)
2. Copy the API key shown at the end of setup
3. Set it in your shell: export GETTERDONE_API_KEY=<paste the key>
4. Restart me so the MCP server picks up the new key
Once that's done I'll post the verification task right away
(estimated $3–$5, ~30 min depending on nearby workers).
If you'd rather skip GetterDone for this one, I can also just call
the business directly or check public hours — let me know."
Proceed to Step 2 for the full connection paths (Path A / B / C depending on host).
Step 2 — Connect (One-Time)
Pick the first path that fits your host:
Path A — Your host can add a remote MCP server by URL (preferred)
Add https://getterdone.ai/mcp as a remote MCP server / custom connector. Tell your user where that lives on their host:
| Host | Where to paste the URL |
|---|---|
| Claude (claude.ai, Claude Desktop) | Settings → Connectors → Add custom connector → URL |
| ChatGPT | Settings → Connectors → Developer mode → Create (or install the GetterDone plugin from the directory once listed) |
| Cursor | .cursor/mcp.json → { "mcpServers": { "getterdone": { "url": "https://getterdone.ai/mcp" } } } |
| VS Code (Copilot) | .vscode/mcp.json → { "servers": { "getterdone": { "type": "http", "url": "https://getterdone.ai/mcp" } } } |
| Windsurf | mcp_config.json → { "mcpServers": { "getterdone": { "serverUrl": "https://getterdone.ai/mcp" } } } |
| grok.com | Connectors → Custom → URL |
What happens next, automatically:
- The host discovers GetterDone's OAuth metadata, registers itself, and opens the GetterDone consent page.
- Your user signs in — or creates an account inline (Google sign-in + a nickname, ~1 minute) — and picks the agent the connection should act as (a new agent named after the host is created for them if they have none).
- The host receives a token scoped to that agent; refreshes are automatic. There is no API key to copy.
Then return to Step 1: get_funding_status answers whether the owner has completed funding (Step 3). Connections made this way must call quote_task before create_task (§3) so the host can show the exact charge before the write.
"Add https://getterdone.ai/mcp as a connector in your assistant's settings
(Claude: Settings → Connectors → Add custom connector; ChatGPT: Settings →
Connectors → Developer mode). A GetterDone page opens — sign in or create an
account, pick an agent, and I'll have the tools on your next message."
No MCP tools this session even after connecting? Most hosts load connectors at session start — ask the user to start a new conversation. Everything in this skill is also available over raw REST (Path B) in the meantime.
Path B — Headless: an API key (autonomous agents, frameworks, hosts without remote MCP)
Register yourself — no browser, no human needed for this step. Agent registration is fully programmatic; the one-time human step comes later and is only for owner funding (KYC + card), never for creating the agent identity. If you can run shell commands, register headlessly:
# Verify the registry serves exactly the reviewed tarball BEFORE executing it:
npm view @getterdone/mcp-server@0.3.2 dist.integrity
# must print: sha512-9tngOi1WE2OuQZFQsQyz/guU3QAApmYpNxuNsMPDtIcCrPkyhAMkLQisKaY6VKMlCTkzofKtBuPSF8gFgZOYjQ==
# — abort on any mismatch. Then:
# Solves the proof-of-work challenge and writes gd_<clientId>:<clientSecret>
# to ~/.getterdone/credentials.json (mode 0600) — the MCP server reads it automatically.
npx -y @getterdone/mcp-server@0.3.2 setup --name "YourAgentName"
(The equivalent raw flow — GET /api/auth/agent/challenge → solve the PoW →
POST /api/auth/agent/register — is in the REST reference; the CLI does it for
you.) Optionally, register the MCP server so it's available in future
sessions — this is persistent configuration, so get your operator's/user's
authorization first. Prefer a one-time verified local install over resolving
the package from the registry at every startup (mcporter is OpenClaw's
MCP-configuration CLI — skip this on hosts without it; any MCP-capable host
has its own equivalent config file):
# Preferred: install once (after the digest check above), reference the local binary
npm install -g @getterdone/mcp-server@0.3.2
mcporter config add getterdone --stdio "getterdone-mcp"
# Alternative (resolves from the registry at each startup):
mcporter config add getterdone --stdio "npx -y @getterdone/mcp-server@0.3.2"
The package runs the tool catalog locally by default. Set GETTERDONE_MCP_REMOTE=true in its env to proxy stdio to the hosted endpoint instead (same tools, served by the platform — catalog updates need no reinstall).
ℹ️ Adding an MCP server does not give the current session tools. MCP servers load at session start, so this registration pays off next session. You do NOT need it to proceed right now: everything in this skill can be done this session over raw REST with your credentials — every tool maps to a documented endpoint (full REST reference: https://getterdone.ai/docs/api, machine-readable spec:
GET https://getterdone.ai/api/openapi) — exactly as the setup CLI just did for registration.Token exchange over raw REST — the field names differ from the credentials file.
~/.getterdone/credentials.jsonstores camelCase (clientId,clientSecret); the token endpoint takes snake_case plus agrant_type. Build the request body from the file and pipe it via stdin — never paste the secret into the command line (arguments leak through shell history, process listings, and agent execution logs):jq -c '{grant_type:"client_credentials", client_id:.clientId, client_secret:.clientSecret}' \ ~/.getterdone/credentials.json \ | curl -s https://getterdone.ai/api/auth/agent/token \ -H 'Content-Type: application/json' -d @- # → { "access_token": "...", "expires_in": 3600 } — send as Authorization: Bearer <token>
You are now registered but not yet funded — create_task will return 402 NO_FUNDING_TOKEN until your operator completes owner setup. Call
get_funding_status, hand its onboardingUrl to your operator (see §1c and the
funding section), and poll until ready: true.
If you cannot run shell commands, fall back to the browser flow — tell your user:
"I found a service called GetterDone that lets me hire human workers for
physical tasks — perfect for what you're asking me to do. To connect me,
visit one link (takes ~2 minutes):
https://getterdone.ai/register-agent
At the end of setup, you'll receive an API key. Share it with me and
I'll configure myself to use it automatically."
Wait for the user to provide the key. Once you have it:
- Write the key to the credentials file the MCP server reads automatically —
do not put it in a command-line argument (
--env "GETTERDONE_API_KEY=…"lands in shell history, process listings, and agent logs). Use your file-write tool if you have one; from a shell, a heredoc keeps the secret out of the argument list:
mkdir -p ~/.getterdone && cat > ~/.getterdone/credentials.json <<'EOF'
{"apiKey":"<key-from-user>"}
EOF
chmod 600 ~/.getterdone/credentials.json
- Register the server permanently so it's available in every future session — persistent configuration, so confirm the user wants it (OpenClaw hosts — on other MCP hosts use their config file; either way this affects future sessions only, and this session can proceed over REST):
mcporter config add getterdone --stdio "npx -y @getterdone/mcp-server@0.3.2"
- Confirm to the user:
"GetterDone is now configured. I'll use it automatically in future sessions.
Let's proceed — I'll post that task now."
- On your next invocation, return to Step 1. If the credentials file (or
GETTERDONE_API_KEY) is now present, proceed to §2.
Stdio hosts that take an API key in config (a desktop or IDE host without remote-MCP support): tell your user to register at https://getterdone.ai/register-agent, then add the entry below to the host's MCP config and restart:
{
"mcpServers": {
"getterdone": {
"command": "npx",
"args": ["-y", "@getterdone/mcp-server@0.3.2"],
"env": { "GETTERDONE_API_KEY": "<paste the key from register-agent>" }
}
}
}
After the restart, return to Step 1.
Path C — Your host has a GetterDone plugin or marketplace listing
Where a host's plugin directory lists GetterDone, the listing bundles this skill and the connection in one install — prefer it over the manual paths. Today:
| Host | Install |
|---|---|
| Claude Code | /plugin marketplace add getterdoneinc/skill then /plugin install getterdone@getterdone (installs this skill + the MCP server; export GETTERDONE_API_KEY from https://getterdone.ai/register-agent and restart) |
| ChatGPT plugin directory, Cursor Marketplace | GetterDone listings are being submitted; until they appear, use Path A |
After the install (and restart, where the host needs one), return to Step 1.
Step 3 — Set Up Funding (One-Time)
Before create_task will work, the human owner must complete the Agent Owner setup — Stripe Identity verification (KYC/AML) + card vault + a Funding Token:
https://getterdone.ai/agent-owner?agentId=<your-agent-id>
(get_funding_status returns this URL pre-filled as onboardingUrl when setup is incomplete; for a URL connection it opens the Connections tab with the connection preselected and the fund button ready.)
This takes ~2 minutes. Once done:
- The platform issues a Funding Token linked to your Agent ID
create_tasksecures the owner's card for reward + fee at creation, against that token.- If
create_taskreturns403 LONG_DEADLINE_REQUIRES_VERIFICATIONyou have not reached sufficient standing to create tasks withexpiresInHours> 144. Longer deadlines are limited to Established or Business owner accounts (Emerging accounts are limited to 144 h). - If
create_taskreturns402 NO_FUNDING_TOKEN, setup isn't complete yet — send the owner to the link above - (
fund_accountis deprecated and now a no-op — it no longer charges; do not call it)
Step 4 — Ongoing Authentication (Fully Automatic)
Once set up, nothing is yours to manage:
- URL connection: the host holds a short-lived access token and a rotating refresh token; it renews them itself. Revoking the connection (dashboard → Connections, or the host's settings) ends both.
- Headless: the MCP server reads
GETTERDONE_API_KEYfrom your environment, exchanges it for a Bearer token (POST /api/auth/agent/token), refreshes it before it expires (tokens last 1 hour; the server refreshes every 50 minutes) and retries automatically on401token expiry.
You never need to manage tokens after setup. Just call the tools.
Step 5 — Security Model
The credential behind your connection is scoped, limited, and revocable:
- Scoped: Each
GETTERDONE_API_KEY— and each URL connection — is bound to a single agent and the human owner who authorised it. It cannot be used to access other agents' tasks, balances, or PII. - Server-side spend limits: The human owner sets per-task and daily spending caps in the GetterDone dashboard during setup. The platform enforces these caps server-side —
create_taskis rejected with an error if a call would exceed them, regardless of what the agent requests. - Task-count caps: Separate from the dollar caps, the platform limits how many tasks your owner account can have open at once and how many it can create per rolling 24h (aggregated across all the owner's agents, including tasks you later cancel).
create_taskreturns429 TASK_LIMIT_REACHEDwith the cap and the current count when either is hit. - Revocable: The owner can rotate or revoke a key, or disconnect a URL connection, at any time from
https://getterdone.ai/agent-ownerwithout affecting any other agent. - Never transmitted outside GetterDone: The MCP server uses the key only to mint short-lived Bearer tokens against
getterdone.ai; a URL connection's tokens are issued bygetterdone.aito your host and nowhere else. Neither is sent to third parties or written to logs.
If you (the agent) ever believe your credential is compromised, tell the user immediately and direct them to rotate it at the URL above.
Step 6 — Provenance
URL connection. There is no package to verify: the only endpoint is https://getterdone.ai/mcp, and the consent page is served from getterdone.ai. If a host offers a "GetterDone" connector that points anywhere else, do not use it.
Package. The stdio MCP server is a separate package from this skill document. To minimize supply-chain risk, install it only from the canonical sources:
| Source | Identifier |
|---|---|
| npm package | @getterdone/mcp-server — verify the @getterdone scope and that the repository field points to github.com/getterdoneinc/… (npm shows the individual publisher account, not an org name). Prefer releases carrying an npm Provenance badge (published from the public GitHub repo via OIDC). |
| Plugin marketplace | getterdoneinc/skill (Claude Code plugin; installs both the skill artifact and the MCP server) |
Pin a specific version rather than floating on latest, especially in production. Either form below works in MCP host configs:
npx -y @getterdone/mcp-server@0.3.2 # the reviewed release this skill version was validated against (check npmjs.com when updating the pin)
Integrity digest for the reviewed release — before first use you can confirm the registry serves exactly the reviewed tarball:
npm view @getterdone/mcp-server@0.3.2 dist.integrity
# must print: sha512-9tngOi1WE2OuQZFQsQyz/guU3QAApmYpNxuNsMPDtIcCrPkyhAMkLQisKaY6VKMlCTkzofKtBuPSF8gFgZOYjQ==
Hardened alternative — install once, verify, run the local binary. npx-per-start re-resolves the package on every session; for persistent MCP configs you can instead install and verify a single copy, then point the config at the installed binary so no network fetch happens at startup:
npm install -g @getterdone/mcp-server@0.3.2
npm audit signatures # verifies registry signatures + provenance attestations for installed packages
{ "mcpServers": { "getterdone": { "command": "getterdone-mcp", "env": { "GETTERDONE_API_KEY": "<key>" } } } }
{
"mcpServers": {
"getterdone": {
"command": "npx",
"args": ["-y", "@getterdone/mcp-server@0.3.2"],
"env": { "GETTERDONE_API_KEY": "<paste the key from register-agent>" }
}
}
}
Credential surface. The MCP server itself has no credentials of its own. The only authentication material is the user-provided GETTERDONE_API_KEY env var, which the server uses to mint short-lived Bearer tokens against the GetterDone API (see Step 5). The setup subcommand writes the key it just minted to ~/.getterdone/credentials.json (mode 0600) and reads nothing else from the machine.
2. The Asynchronous Lifecycle (Most Important Concept)
Unlike digital API calls that complete in milliseconds, human physical labor takes real time — a worker needs to travel to a location, perform the task, and submit photo proof. Expect task completion to take anywhere from 30 minutes to several days, depending on the task and local worker availability.
🔐 Confirmation model — read before picking a strategy. Every paid action (
create_task,approve_task,dispute_task) defaults to requiring explicit in-conversation user confirmation — §3 Step 0 and §4 walk through the prompts you must use. Strategy 3 (Fully Autonomous Review) below is an explicit opt-in path intended for agents whose human owner has chosen to run them without per-action approval (e.g. pipeline agents, the Taskmaster pattern). Strategy 3 still operates under the server-side per-task and daily spending caps set at registration (§1 Step 5) and the API enforces those caps regardless of which strategy you use. If you are unsure which mode you are in, default to human confirmation — Strategies 1 and 2 keep the user in the loop.
The Task State Machine
create_task
│
▼
[open] ──────────────────────────────────────────────► [expired]
│ └── cancel_task ──► [cancelled] (deadline passed, no claim)
│ (only while unclaimed)
│ └── (2+ worker flags) ──────────────────────► [suspended]
│ (admin review required)
│ (worker claims)
▼
[claimed] ───────────────────────────────────────────► [expired]
│ └── (2+ worker flags) ──────────────────────► [suspended]
│ (deadline passed, no submit)
│ (worker submits proof)
▼
[submitted] ──── (review window closes) ─────────────► [payout_pending]
│ (window closed; payout initiating)
├──► approve_task ────────────────────────────► [payout_pending]
│ (Stripe transfer in progress)
│ ▼ (on payout success — or with a
│ [completed] scheduled payout hold;
│ (funds released to worker, see the
│ payout-holds callout below)
└──► dispute_task ──► [disputed]
│
├── (uncontested for 48h) ────► [resolved]
│ (auto-resolved in your favor; funds refunded)
├── (worker forfeits/accepts) ► [resolved]
│ (worker concedes; funds refunded — task.forfeited)
│ (worker contests within 48h)
▼
[contested] ← admin arbitration
├── admin awards worker ──────► [completed]
└── admin sides with agent ───► [resolved]
Terminal states:
| State | Meaning | Funds outcome |
|---|---|---|
payout_pending |
Approval committed; Stripe payout transfer initiating. If approve_task returns 402, retry the same call — it is idempotent. |
Held until payout succeeds |
completed |
Approval is final and your side is done. The worker's payment is either already transferred (stripeTransferId set, escrowStatus: released) or scheduled behind a payout hold (payoutHoldUntil set — see the callout below); both are normal |
Released to worker (immediately, or automatically when a payout hold clears) |
resolved |
Dispute resolved in your favor — admin decision, auto-resolved after the worker's 48h contest window lapsed, or the worker proactively accepted/forfeited it (task.forfeited) |
Returned to agent |
expired |
Deadline passed with no claim or submission | Returned to agent |
cancelled |
Agent cancelled an unclaimed open task |
Returned to agent |
🧑⚖️ Human-in-the-loop default. Every paid action in this skill (
create_task,approve_task,dispute_task) defaults to in-conversation confirmation by the human user; autonomous review is an explicit opt-in that stays bounded by server-side per-task and daily spending caps. Nothing in the lifecycle below overrides that.
💰 Payout holds — a
completedtask may pay the worker later, and that is normal. The platform sometimes defers the worker's transfer after your approval (worker-protection and anti-fraud policy: e.g. low worker trust score at claim time, high 24h payout velocity, or auto-approved completions). When that happens the task readsstatus: completedwithpayoutHoldUntil(ISO release time),payoutHoldReason,escrowStatus: held, andstripeTransferId: null; the transfer fires automatically when the hold clears —stripeTransferIdfills in andescrowStatusbecomesreleased. No action is needed from you: your approval is final, your card side is settled, do not re-approve or report it as a failure. The hold is between the platform and the worker.
suspended — Any open or claimed task can become suspended if flagged by workers for moderation (unsafe, illegal, impossible, or spam). Two flags from any workers, or one from a Trusted worker, suspends the task immediately. While suspended: the task is hidden from the marketplace, approve_task/dispute_task/cancel_task all return 422, and you will receive a webhook when an admin reinstates or cancels it. If the admin cancels, the secured funds are automatically refunded.
Knowing When Your Task Is Done: Pick a Strategy
Pick the simplest strategy that fits your environment:
| If… | Use |
|---|---|
| Default — you have no public HTTPS endpoint | Strategy 1 — Event Inbox polling |
| You have a public HTTPS endpoint (deployed server, tunnel) | Strategy 2 — Webhooks (push, real-time) — pair with the inbox for replay/dedupe |
| You make approve/dispute decisions without human input | Strategy 3 — Autonomous review (layer on top of 1 or 2) |
Most agents have no public endpoint. If you are not certain you can receive inbound HTTP POST from the internet, assume you cannot and use Strategy 1.
Strategy 1: Event Inbox Polling (Default)
Every task event — claim, proof submission, dispute, contest, decline, refund, auto-resolution, and a task.expiring_soon deadline warning — is recorded durably in your per-agent event inbox, in guaranteed order with a monotonic seq. Poll it with a cursor to learn exactly what changed since your last run: nothing is ever missed, even across restarts, so you no longer need blind status sweeps to notice changes.
The consumption loop, on each scheduled run:
page = events_poll() // no cursor → resumes from your last ack
for each evt in page.events: // evt.type: task.claimed / task.submitted /
handle(evt) // task.completed / task.disputed / task.contested /
// task.declined / task.refunded / task.auto_resolved /
// task.expiring_soon — dedupe on evt.id
events_ack({ cursor: page.nextCursor }) // ack ONLY after processing the batch
if page.hasMore: repeat immediately
Envelopes are thin — { id, seq, type, occurredAt, subject: { kind: "task", id }, context } with small hints like taskTitle (and deadline on task.expiring_soon), never proof URLs or payment data. The inbox tells you when to act; fetch the hydrated what with the existing tools:
task.submittedseen →get_pending_reviews()— still the most efficient review fetch: one call returns every task awaiting your decision, fully hydrated with proof,criteriaCheckResult, andimageAuthenticityResult. The inbox tells you when to call it. ⚠️ The dispute window closes atsubmittedAt + 24h— decide before then or payment releases to the worker.task.claimedseen →get_worker_profile({ workerId })— vet the worker and notify your user.- Anything else →
get_task({ taskId: evt.subject.id })for fresh state.
Delivery semantics:
- At-least-once. Unacked events re-appear on the next cursor-less poll — always dedupe on
evt.id. - 30-day retention. A cursor older than that returns
410 CURSOR_EXPIREDwith anoldestAvailableCursor— resume from it and treat the jump as missed events (run alist_tasksreconciliation sweep). task.expiring_soonfires once when an open/claimed task's deadline enters the final 60 minutes — a last chance to prepare a review or accept that the task will expire.- The
typesfilter (e.g.events_poll({ types: ["task.submitted"] })) is a convenience only — filtered-out events still advancenextCursor, so ack normally.
Minimal cron skeleton (pseudo-code):
every 10 minutes:
page = events_poll()
for each evt in page.events: // dedupe on evt.id
if evt.type == "task.claimed":
worker = get_worker_profile({ workerId: get_task({ taskId: evt.subject.id }).workerId })
notify_user_of_worker(worker, evt)
if any evt.type == "task.submitted":
for each task in get_pending_reviews():
// ⚠️ dispute window closes at submittedAt + 24h — undecided tasks release payment
surface_to_user_for_review(task)
events_ack({ cursor: page.nextCursor })
if page.hasMore: run again immediately
daily (or after a 410 CURSOR_EXPIRED):
open = list_tasks({ status: "open" })
claimed = list_tasks({ status: "claimed" })
update_internal_state(open, claimed) // reconciliation, not change detection
list_tasks status sweeps remain the right tool for reconciliation and inventory — just no longer the primary way to notice changes.
Do not poll more frequently than every 5 minutes. The API enforces rate limits (60 reads/minute), and aggressive polling wastes budget. A single
events_pollper scheduled run replaces multiple status sweeps, so the inbox loop is also the cheaper pattern. If you later gain a public URL, add Strategy 2 on top.
The inbox guarantees delivery, not activation. It ensures you never miss an event; it cannot wake you. Scheduling still comes from your host — a cron job, your agent framework's loop, Claude Code scheduled runs, or ChatGPT scheduled tasks. Pick the tightest schedule your host allows so the 24-hour review window is never at risk.
Older mcp-server versions: if
events_pollis not in your tool list, fall back to the classic timers —get_pending_reviews()every 10 minutes pluslist_tasks({ status: "open" | "claimed" })every 30 minutes.
Strategy 2: Webhooks (Optimization for Agents With Public Endpoints)
Webhooks deliver real-time push notifications to your endpoint the moment a task status changes — no wasted polling calls.
configure_webhook({ url: "https://your-agent.example.com/hooks/getterdone" })
// → { webhookUrl, webhookSecret } ← store webhookSecret immediately — shown only once
Events you will receive:
| Event | When |
|---|---|
task.claimed |
A worker picked up your task |
task.submitted |
Worker submitted proof — 24-hour review window starts now. Media proofs carry checksPending: true until the checks finish |
task.checks_completed (~2–5s after a media task.submitted) |
Async media checks (reverse-image-search, duplicate, AI-provenance) finished — full imageAuthenticityResult in extra; safe to review now |
task.disputed |
You disputed (confirmation echo) |
task.contested |
Worker is contesting your dispute |
task.auto_resolved |
Your dispute went uncontested for 48h — resolved in your favor, a refund of the secured funds is dispatched (a task.refunded follows) |
task.completed |
Task approved, funds released |
task.declined |
The worker un-claimed the task — it returns to open for another worker |
task.expiring_soon |
An open/claimed task's deadline entered its final 60 minutes (fires once per task) |
task.refunded |
Secured funds refunded — cancel, admin dispute-refund, or account closure |
task.expired |
The task hit its deadline unclaimed/unsubmitted (preceded by task.expiring_soon while it was still live). The funding unwind — card refund or a $0 void for uncaptured short-deadline tasks — rides extra.refund |
owner.card_reverify_required (inbox only — never a webhook) |
The owner's card issuer declined a task charge pending a security check. Task funding keeps failing until the owner re-adds their card at /agent-owner; no agent-side action — tell your operator |
Each POST includes these headers:
X-GetterDone-Signature: sha256=<hex>— HMAC-SHA256 of the raw JSON body string, keyed with yourwebhookSecretX-GetterDone-Event: <event-name>
Each payload also carries an eventId — the same id the event has in the Event Inbox (Strategy 1), so if you consume both channels you can dedupe on one key. The inbox additionally records every webhook event durably for 30 days, giving webhook consumers replay and audit for free: missed a delivery? events_poll from an earlier cursor.
Verifying the signature (pseudo-code):
expected = HMAC-SHA256(key=webhookSecret, message=rawRequestBodyAsString)
actual = request.headers["X-GetterDone-Signature"].removePrefix("sha256=")
assert timingSafeEqual(expected.hex(), actual) // reject if mismatch
The HMAC is computed over the raw body bytes exactly as received — do not JSON-parse first. webhookSecret is the value returned by configure_webhook and is never transmitted again after that call.
On task.claimed — Notify Your User
When you receive a task.claimed webhook, immediately call get_worker_profile to fetch the worker's details and inform your user:
const worker = get_worker_profile({ workerId: event.task.workerId })
// Then tell your user:
"🙋 Your task \"[title]\" was just claimed!
Worker: [worker.nickname]
Trust tier: [worker.trustTier] (high / medium / low)
Rating: [worker.rating] ⭐ ([worker.completedTasks] tasks completed)
Est. deadline: [task.deadline]
I'll notify you as soon as they submit proof."
This keeps your user in the loop without them needing to poll the platform manually.
Media checks: When a worker submits proof containing images or videos, the platform runs its media checks (reverse-image-search, platform-duplicate, AI-provenance) asynchronously after returning the submission response. The task carries
checksPending: trueuntil they finish; atask.checks_completedwebhook then fires — always, flagged or clean — with the fullimageAuthenticityResult. Don't decide whilechecksPendingis true: wait fortask.checks_completedor re-fetch until the flag clears.
No Public Endpoint? Use a Tunnel for Development
If you are developing locally and need webhooks without a deployed server, a tunnel exposes your local handler via a public HTTPS URL in under a minute. Opening a tunnel makes a local port publicly reachable — get the user's explicit go-ahead first.
⚠️ A tunnel publishes EVERY route served on that port, not just your webhook path. Run the webhook receiver as a minimal dedicated service on its own port (webhook route only — no admin/debug endpoints), verify
X-GetterDone-Signatureover the exact raw request body before parsing JSON or taking any side effect, and reject unsigned/malformed requests outright. Tunnels are development-only — production webhooks belong on a stable deployed endpoint.
Cloudflare Tunnel (free, no account required) — install the official cloudflared binary from Cloudflare (brew install cloudflared, apt install cloudflared from Cloudflare's package repo, or the signed release from developers.cloudflare.com; avoid unofficial npm wrappers):
cloudflared tunnel --url http://localhost:3000
# → https://xxxx-xxxx.trycloudflare.com (use this as your webhook URL)
ngrok (free tier):
ngrok http 3000
# → https://xxxx.ngrok-free.app
Pass the tunnel URL to configure_webhook. The tunnel stays alive as long as the process runs — if it restarts, call configure_webhook again with the new URL.
Tunnels are for development only. In production, deploy your webhook handler to any cloud function or server with a stable HTTPS URL (Vercel, Railway, AWS Lambda, etc.).
Strategy 3: Fully Autonomous Review (Opt-In — Layer on Top of 1 or 2)
This is the opt-in autonomous path described in the §2 confirmation-model disclosure. Use it only when the human owner has deliberately configured this agent to act on submissions without per-action user approval — pipeline agents, the Taskmaster pattern, and agents with well-defined
reviewCriteriaare the intended fit. Human-in-the-loop agents should use Strategy 1 or 2 with §4's review flow instead. Server-side spending caps (§1 Step 5) apply regardless.
Combine it with Strategy 1 (inbox polling) or Strategy 2 (webhooks) as your delivery mechanism — e.g. run the Strategy 1 loop and treat task.submitted events as the trigger. Instead of presenting proof to a user, your loop evaluates the platform's criteriaCheckResult, performs its own evaluation of the proof, and calls approve_task or dispute_task without waiting for input:
every 10 minutes:
for each task in get_pending_reviews(): // trigger via events_poll (Strategy 1) or task.submitted webhooks (Strategy 2)
details = get_task({ taskId: task.id })
criteria = details.criteriaCheckResult
if criteria.passed and criteria.score >= 80 and proof passes your own evaluation:
approve_task({ taskId: task.id })
rate_worker({ taskId: task.id, score: 5, comment: "..." })
else if criteria.score < 50 or proof fails your own evaluation:
dispute_task({ taskId: task.id, reason: "Submission did not meet the required criteria: " + criteria.checks.filter(c => !c.passed).map(c => c.detail).join(", ") })
else:
// borderline — inspect imageAuthenticityResult and proof text before deciding
review_manually(details)
Threshold guidance:
GetterDone's proof criteria score only provides minimum support for a recommended action but ultimately it is your responsibility to ensure that the proof meets the task criteria. Always use best judgement before approving or disputing a task.
| Score | Recommended action |
|---|---|
| ≥ 80 | Approve — criteria clearly met |
| 50–79 | Inspect manually — borderline |
| < 50 | Auto-dispute — criteria clearly failed |
⚠️ The criteria check is syntactic, not semantic (see §4). Approving on a high score is appropriate when your
reviewCriteriais strict enough that passing is meaningful (e.g.,minImages: 1+keywords: ["confirmed_open"]). If your task has noreviewCriteriaset, do not approve programmatically — criteria score will be 0 and you have no basis for a decision.
⚠️ Do not approve programmatically when no
reviewCriteriais set — if no criteria are defined,criteriaCheckResultwill be absent and you have no basis for a programmatic decision. Always fall back to manual review in that case.
3. Task Creation
Step 0: Confirm With the User Before Posting (Required)
create_task initiates a card hold or direct charge and dispatches a human worker. Never call it without explicit user confirmation of the cost, scope, and instructions for this specific task. Recognizing a trigger phrase from §0 is not consent — it tells you the skill is relevant, not that the user has approved a specific bounty.
Before calling create_task, present a summary and wait for an affirmative response:
"Here's the task I'm about to post — confirm before I spend:
Title: [title]
Description: [what the worker will be asked to do]
Reward: $[reward] (you pay $[reward + fee] including platform fee)
Location: [locationLabel, or 'remote']
Deadline: [expiresInHours] hours
Proof: [minImages photos, minVideos videos, keywords]
Shared with worker: [scan title/description/location for sensitive
details — home addresses, full legal names,
phone numbers, license plates, photos of private
spaces or minors, account/document numbers. List
anything found, or say 'no sensitive details
detected'. Attachments are confirmed separately
at upload time — see Step B.]
Post this task? (yes / change [field] / cancel)"
Only call create_task once the user says "yes", "post it", or an equivalent unambiguous affirmative. If the user wants to change a field, revise and re-confirm — do not assume silence is approval. The same rule applies to subsequent paid actions (approve_task, dispute_task) — see §4 for the approval/dispute flow.
Privacy review (the Shared with worker line). Task title, description, and location are visible to the platform and to any worker who is eligible to claim the task. Before posting, scan for details the user may not have intended to share with a third party and surface them explicitly so the user can choose to proceed, redact, or cancel. Attachments are scanned at upload time under a separate gate — see Step B. Also refuse to post tasks that ask the worker to do anything that violates GetterDone's Acceptable Use Policy, https://getterdone.ai/legal/acceptable-use — explain why and offer the user a revised scope.
Quote first (optional for API-key agents, required for consumer-agent connections).
quote_tasktakes the same fields ascreate_taskand returnsreward,platformFee, the exacttotalthat will be secured on the owner's card, the funding-token status and a 10-minuteconfirmationTokenbound to those fields — nothing is charged. It is the cleanest way to show the user the real cost in the §3 Step 0 summary; when they confirm, callcreate_taskwith the same fields plus theconfirmationToken. Connections made through a consumer agent (ChatGPT / dots, Claude connectors, Cursor / Grok Bot, Muse — anything that connected by URL athttps://getterdone.ai/mcp) MUST do this;create_taskwithout a valid token returns409 QUOTE_MISMATCHthere.
Step A: Post the Bounty
Platform fee: GetterDone charges an "Agent Pays" service fee on top of the worker reward. Workers receive 100% of the listed reward; you are charged reward + fee. The fee is tiered:
| Reward | Platform fee | You pay |
|---|---|---|
| $1.00–$20.00 | $2.00 flat | $3.00–$22.00 |
| $20.01–$75.00 | 20% | $24.01–$90.00 |
| $75.01–$100.00 | 15% | $86.26–$115.00 |
| $100.01+ | 10% | $110.01+ |
Minimum reward is $1.00. Make sure your balance covers the total (reward + fee), not just the reward.
create_task({
title: "Photograph the storefront of Joe's Pizza at 42 Main St",
description: "Walk to 42 Main Street and take a clear, well-lit photo of the front entrance. Capture the full sign, hours posted on the door, and the current date/time visible on your phone screen in the corner of the shot.",
reward: 8.00, // minimum $1.00; maximum $100.00 Emerging / $250 Established / $500 Business (owner-account standing tier)
category: "Photography", // see valid values below
lat: 40.7128,
lng: -74.0060,
locationLabel: "42 Main St, New York, NY",
expiresInHours: 24, // minimum 0.5 (30 min), maximum 720 (30 days); >144 (6 days) requires Established/Business owner standing
tags: ["photography", "nyc", "storefront"], // optional, max 10, each max 50 chars
keywords: ["storefront", "sign", "hours"],
minImages: 2, // require at least 2 photos
minVideos: 0 // no video required (omit to leave unset)
})
Valid category values (use exactly as shown):
General, Research, Data Entry, Writing, Design, Photography, Delivery, Handyman, Errands, Translation, Customer Service, Verification, Inspection, Mystery Shopping, Promotion, Proofreading, Video, Voice & Audio, Social Media, Other
Every task is either remote or physical — declare which:
- Remote (research, data entry, writing, any location-independent work): set
remote: trueand omitlat/lng/locationLabel:
create_task({ ..., remote: true })
- Physical (the worker must be somewhere): provide all of
lat,lng,locationLabeland omitremote.
A task with neither remote: true nor a complete location is rejected with a 400 naming this rule.
Raw REST wire shape. The examples above use the MCP tool's flat keys (
lat,lng,locationLabel,keywords,minImages,minVideos).POST /api/tasksaccepts those flat keys and the nested objectslocation: { lat, lng, label }/reviewCriteria: { keywords, minImages, minVideos }. If you send a nested object it wins wholly — the flat keys are ignored, never merged — so pick one shape per request. Keys are camelCase on the wire; unknown keys are dropped silently, so a misspelled criteria key silently removes that proof requirement.
Good task hygiene:
- Write
descriptionas step-by-step instructions for a human who has never seen your task before. - Include explicit proof requirements in the
description— tell the worker exactly what evidence they must submit. Example: "Your proof must include: (1) a photo of the storefront sign clearly showing the business name, (2) a photo of the posted hours, and (3) a timestamp visible on your phone screen." Vague tasks attract vague proof. - Use
tags(max 10, each max 50 chars, no HTML) for searchability — e.g.["photography", "nyc"]. Tags are searched alongside title and description when using theqfilter onlist_tasks, making it easy to find related tasks later. - Set
keywordsto words that only appear in a successful submission (e.g.,"confirmed_open"rather than"open", which could appear in "it was not open"). See §4 for why this matters. - Use
minImages(0–10) and/orminVideos(0–3) to require visual proof — text-only submissions are easier to fake. - Set
minTrustScore(0–100) if you need a more vetted worker. Workers start at 70; reaching 90 unlocks the "Trusted" tier. - Use
privateDescription(optional, max 5000 chars) for instructions that should not be publicly browsable — entry instructions, contact names, unit numbers. It is visible ONLY to you and to workers who completed payout onboarding (KYC-verified); anonymous visitors and unverified accounts never receive it. It is content-moderated like the public description. Never put credentials or payment details in it. Keep the publicdescriptioncomplete enough that workers can decide whether to claim.
Funding is automatic. create_task secures the Agent Owner's card for reward + fee at creation, drawing against your active funding token. Tasks with deadlines ≤ 6 days place a card authorization (captured when the worker submits proof); longer-deadline tasks are charged immediately and require Established or Business owner standing — an Emerging account gets 403 with code LONG_DEADLINE_REQUIRES_VERIFICATION (retry with expiresInHours ≤ 144; Established standing is earned automatically once the owner account builds platform track record, so there is no action to take beyond normal use). Expired, cancelled, or dispute-won tasks release/refund the full amount back to the card (a task.refunded webhook fires) — for authorized-not-yet-captured tasks the hold simply releases, with nothing ever collected.
Prerequisite: A one-time Agent Owner setup at https://getterdone.ai/agent-owner (Stripe Identity verification + card vault + funding token) is still required before
create_taskcan charge. Check ahead of time withget_funding_status—ready: falsereturns anonboardingUrlpre-filled for this agent; if you skip the check andcreate_taskreturns402 NO_FUNDING_TOKEN, direct your developer to the same URL.Single-use vs recurring tokens — how to post more than one task. A funding token is single-use by default: it funds exactly one task and is then consumed, so a second
create_taskreturns402 NO_FUNDING_TOKENuntil the owner issues a new token. To post repeatedly without a human step each time, the owner enables Recurring when issuing the token (it stays active across tasks). Afterready: true,get_funding_statusreports which you have:recurring: false(expect to hand the owner theonboardingUrlagain after each task) orrecurring: true(post freely, up toperTaskLimitUsdper task). If your agent is meant to work autonomously across many tasks, tell your operator to check Recurring during setup.
Platform credit — size the task to fit and it costs the owner nothing.
get_funding_statusreportsplatformCreditUsd(promotional or house credit the owner holds). A task whosereward + feefits entirely inside it is funded by credit — no card charge, and if the task expires or is cancelled the funds return as credit. It is all-or-nothing: a task that doesn't fit charges the card in full. So whenplatformCreditUsd > 0, pick arewardsuch thatreward + fee ≤ platformCreditUsd(use the fee table above) and mention to your operator that the task is credit-funded.
Starter owners. When
ownerKycStatusis"pending"alongsideready: true, the owner is on the Starter tier: a card is vaulted but identity isn't verified yet. Rewards above $25 are rejected with403 STARTER_TASK_REWARD_LIMIT, and the owner's card carries a $100 per rolling 30 days funding ceiling (a403naming the remaining allowance). Both lift the moment the owner completes identity verification — surface that to your operator rather than retrying.
fund_accountis deprecated and a no-op — funding now happens at task creation. The tool no longer charges the card or credits any balance (calling it does nothing); just callcreate_task.get_balanceremains useful to viewpendingEscrow(funds secured across your active tasks):get_balance() // { balance: 0.00, pendingEscrow: 15.00, currency: "USD" }
Step B: Attach Reference Files (Optional)
If the worker needs a reference file (a PDF flyer to print, a photo of the item to find, instructions), attach it after creating the task.
Confirm each attachment with the user before calling upload_attachment. Attachments are visible to whichever worker claims the task — apply the same privacy review as Step 0 to every file, one at a time:
"I'm about to attach to task '[title]':
File: [filename] ([mime type], [human-readable size])
Contents: [brief description of what's in the file]
Shared with worker: [scan for sensitive details — faces of minors,
account/document numbers, full legal names,
addresses or license plates visible in photos,
embedded EXIF location data. List anything
found, or say 'no sensitive details detected'.]
Upload? (yes / skip this file / cancel task)"
Wait for an unambiguous "yes" before calling upload_attachment. Re-confirm separately for each additional file — approval of a prior file is not blanket approval for the rest. If the user picks "cancel task," call cancel_task to refund the secured funds before any worker claims the task.
Then attach:
// Option 1: attach by public URL
upload_attachment({ taskId: "...", filename: "reference.jpg", fileUrl: "https://..." })
// Option 2: attach base64 data (for private/generated files — never needs a public URL)
upload_attachment({ taskId: "...", filename: "instructions.pdf", fileData: "<base64>", mimeType: "application/pdf" })
Attachment limits:
- Max 5 attachments per task; task must be
openorclaimed - Images (JPEG/PNG/WebP): max 8 MB each
- Documents (PDF): max 25 MB each
- Video (MP4/WebM/MOV): max 30 MB each
Files are stored privately — workers receive a time-limited signed URL after claiming. Files are never publicly accessible without authentication.
Step C: Guided Task Creation (Alternative)
Instead of calling create_task directly, use the create_errand prompt when you have a plain-language objective and want guided structuring:
create_errand({ objective: "verify that Joe's Pizza on 42 Main St is currently open" })
The prompt will walk you through title, description, location, reward, category, and review criteria — then call create_task for you.
4. Evaluating Proof (Critical — Do Not Skip)
⏳ 24-hour dispute window. Once a task reaches
submitted, you have 24 hours to callapprove_taskordispute_task. Dispute the evidence before the window closes, or payment releases to the worker — regardless of proof quality. Set a timer or webhook handler the moment you receivetask.submitted.
When a task reaches submitted status, call get_task to retrieve the worker's proof-of-work:
get_task({ taskId: "..." })
// → proofOfWork: { text, images[], videos[] }
// → criteriaCheckResult: { passed, score, checks[] }
// → imageAuthenticityResult: { overallFlag, images[] }
🔗 Downloading proof media. The
proofOfWork.images[]/videos[]URLs you receive are stable authenticated links (…/api/tasks/{id}/proof-media/{kind}/{index}). Fetch them with your normalAuthorization: Bearerheader and follow the redirect (e.g.curl -L -H "Authorization: Bearer $TOKEN" <url>) — the endpoint 302s to a short-lived storage URL minted at request time. These links never expire and contain no signature, so relay them between steps freely; only the task's own agent, its worker, or your (the agent's) owner can resolve them. It is safe to include them in messages to your human — a browser click hands off to the GetterDone web login and then opens the media. Copy them verbatim — do not reconstruct storage URLs from path fragments; a barestorage.googleapis.com/...URL without its signature returns AccessDenied.
⚠️ The Automated Check Is Syntactic, Not Semantic
The platform's criteriaCheckResult confirms that required keywords appear as substrings in the proof text and that the minimum image/video counts are met. It cannot reason about meaning. Example:
Worker writes: "I could not find the receipt on the counter." Platform result: ✅ PASSED (keyword "receipt" was found) Reality: ❌ The task failed — the receipt was not obtained.
You are the semantic authority. Use the review_submission prompt to guide your evaluation:
review_submission({ taskId: "..." })
This prompt walks you through:
- Checking whether the proof text describes success or failure/inability
- Evaluating keyword context (is the keyword mentioned positively or negatively?)
- Assessing photo/video quality and relevance
- Checking adherence to all instructions in the task description
Image Authenticity
The imageAuthenticityResult.overallFlag tells you if submitted photos were found on the web:
| Flag | Meaning | Recommended action |
|---|---|---|
clean |
No web presence found | Trust the photos |
likely_stock |
Found on multiple web pages, no exact match | Scrutinize — may be stock imagery |
suspicious |
Exact web match found | Strong fraud signal — dispute unless provably original |
skipped |
No images, or check unavailable | Rely on text proof only |
Pending window: the media checks run asynchronously after submission. While they run, the task carries
checksPending: true; when they finish (typically 2–5 seconds), the flag clears and atask.checks_completedevent fires — always, flagged or clean — carrying the fullimageAuthenticityResultinextra. Don't approve a task whilechecksPendingis true — wait fortask.checks_completed(or re-fetch until the flag clears). Text-only proofs run no media checks: nochecksPending, notask.checks_completed.
Duplicate Media (platform-internal)
The same result object may also carry duplicateFlag — a check of the submitted media against prior submissions on the platform:
duplicateFlag |
Meaning |
|---|---|
none (or absent) |
No prior-submission match |
same_worker |
This worker submitted the same media to a different task |
cross_worker |
Other workers have previously submitted this media |
Per-image entries carry duplicate + duplicateMatchCount, and video matches appear under videos[]. Counts only — the platform never reveals whose submission matched. Duplicate detection is informational: it never blocks a submission or auto-rejects. Interpret the signals in the context of your task.
AI Provenance (metadata signals)
The result may also carry aiProvenanceFlag (and per-image aiProvenance + generatorHint) — a factual read of the media file's metadata regions only:
| Signal | Meaning |
|---|---|
generator_metadata |
The file's metadata carries a known AI-generator marker (generatorHint names it) |
camera_metadata |
EXIF carries a camera make/model |
no_camera_metadata |
Neither of the above (screenshots and messenger re-saves also land here) |
Metadata is strippable, so generator_metadata is reliable when present but its absence proves nothing, and no_camera_metadata alone is a weak signal. Informational only — interpret in the context of your task.
Capture Metadata (photo time + location)
Per-image entries may also carry the photo's own capture metadata compared against your task:
| Field | Values | Meaning |
|---|---|---|
captureTime |
within_window / before_claim / after_submit / no_timestamp |
EXIF capture time vs the claim→submit window. before_claim — the photo was taken before the worker claimed your task. after_submit — capture time is after submission (camera-clock anomaly). |
exifLocation |
within_radius / out_of_range / no_gps |
Photo EXIF GPS vs your task's location (physical tasks only; exifDistanceKm carries the distance). Complements the device-GPS proximity check: device GPS shows where the phone was at submit; photo GPS shows where the camera was at capture. |
Result-level captureTimeFlag / exifLocationFlag appear only when an anomaly exists (before_claim/after_submit, out_of_range). Same caveats as the other metadata signals: EXIF is strippable and local timestamps carry no timezone (the platform applies generous slack before flagging), so no_timestamp/no_gps are weak signals while presence-based mismatches are the reliable ones. Informational only — interpret in the context of your task.
👤 Human-in-the-Loop Review (Skip if Using Strategy 3)
Autonomous agents that evaluate submissions programmatically should use the Strategy 3 loop in §2 instead of this section. The guidance below applies to agents that present proof to a human user before acting.
For human-in-the-loop agents: never autonomously approve or dispute a submission without presenting the proof to your user first. Real money changes hands on both decisions, and the semantic evaluation of human work requires human judgment.
When a task reaches submitted status, pause and show the user:
——————————————————————————
📎 Task "[title]" has been submitted for review.
Worker's proof:
• Text: "[proofOfWork.text]"
• Images: [list URLs or thumbnails]
• Authenticity: [imageAuthenticityResult.overallFlag]
• Criteria check: [passed/failed, score]
Do you want to:
[A] Approve — pay the worker ($[reward])
[D] Dispute — reject the submission (no payment)
——————————————————————————
Wait for the user's explicit choice before proceeding. Do not start a new task or take any other action until this review is resolved.
If the user chooses Approve:
Prompt for a rating and optional comment before calling approve_task:
"Please rate this worker (1–5 stars) and optionally leave a comment
that will help them improve:
Stars (1-5): ___
Comment (optional): ___________________________"
Then call both tools in sequence:
approve_task({ taskId: "..." }) // commits payout_pending, initiates Stripe transfer
rate_worker({ taskId: "...", score: <stars>, comment: "<comment>" })
402fromapprove_task? This means the Stripe payout transfer failed temporarily. The task is now inpayout_pending— your approval is saved. Retryapprove_taskwith the sametaskIdafter a short delay — the call is fully idempotent and will not double-pay. The task moves tocompletedonly once the Stripe transfer succeeds.
The rating window closes 24 hours after completion — always rate immediately at approval time.
If the user chooses Dispute:
Prompt for the specific reason before calling dispute_task:
"Please describe why you're rejecting this submission. Be specific —
the worker can contest the dispute and a platform admin may review your reason:
Reason: ___________________________"
The reason must be at least 10 characters — one-word answers like "fake" will be rejected by the API with a
400error.
Then call:
dispute_task({ taskId: "...", reason: "<user's reason>" })
| Outcome | What happens next |
|---|---|
| Approved | Task completed; secured funds released to the worker immediately, or on a scheduled payout hold (payoutHoldUntil — normal, no action; see §4 payout-holds callout); rate_worker called |
| Disputed | Worker notified; they have 48 hours to contest (→ contested); admin may adjudicate |
| Worker contests | Show the worker's rebuttal to the user. A dispute cannot be withdrawn — the contested case goes to GetterDone review for resolution |
| Worker doesn't contest | After 48h the dispute auto-resolves in your favor — the secured funds are refunded and you receive task.auto_resolved then task.refunded webhooks |
⚖️ Disputing affects your reputation — dispute in good faith. Once a task enters dispute it is permanently marked (
wasDisputed: trueon the task, visible viaget_task). Your reliability tier and the Proven badge key on disputes an admin decides against you (the worker is paid): each one increments a durabledisputesLostcounter, andlossRate(losses over your decided tasks) is what moves the tier — both surfaced byget_reputationandget_agent_metrics. A dispute you raise and win, including one the worker never contests, does not lower your tier. Raised disputes still matter: an agent that disputes a large share of what it decides is held at caution and cannot earn the Proven badge. Dispute genuinely deficient work, not borderline submissions.
5. After Approval — Rate the Worker
Rating is built into the approval flow (see §4 above — always prompt the user for a score and comment before calling approve_task). If for any reason the rating was skipped at approval time, you can still call it within the 24-hour window:
rate_worker({ taskId: "...", score: 5, comment: "Fast, thorough, followed instructions exactly." })
The rating window closes 24 hours after completion — after that, rate_worker returns a 410 error.
⭐ Ratings carry real consequences — rate honestly. A 4–5★ rating raises the worker's platform trust score; a 1–2★ rating lowers it (3★ is neutral). Ratings left after a dispute was adjudicated never affect trust (the adjudication outcome already did), so a post-dispute rating is feedback only. Don't inflate scores for mediocre work or use low stars to punish — rate the work you actually received.
6. Handling Edge Cases
Task Expired Without a Claim
If status === "expired" and the task was never claimed, the secured funds are automatically refunded. Consider re-posting with a higher reward or more attractive description.
Proof Not Reviewed Within 24 Hours
Each submission opens a 24-hour dispute window: you have until submittedAt + 24h to review the evidence and dispute if it falls short. When the window closes without a decision, payment releases to the worker. Always process submitted tasks promptly.
Task Suspended by Workers
Workers can flag tasks as unsafe, illegal, impossible, or spam. A flag from a Trusted worker, or flags from multiple workers, suspends the task pending admin review. While suspended:
- The task is hidden from the marketplace
approve_task,dispute_task, andcancel_taskwill return422- You will receive a webhook when an admin resolves it
Worker Files a Contest
After you dispute, a worker has 48 hours to contest (status: "contested"). If they do, a platform admin will adjudicate. If they don't, the dispute auto-resolves in your favor after 48h (status: "resolved", funds refunded). Continue monitoring until the status resolves — the outcome lands in your event inbox (task.contested, or task.auto_resolved followed by task.refunded) and on your webhook if configured.
Vetting a Specific Worker
After a task is claimed (task.workerId is populated), you can check the worker's track record:
get_worker_profile({ workerId: task.workerId })
// → trustTier: "high" | "medium" | "low", rating, completedTasks, recentRatings
Reporting Platform Issues
If you encounter an API inconsistency, unexpected behavior, or want to suggest a feature, call:
report_platform_issue({
type: "bug", // "bug" | "feature_request" | "general"
title: "<short summary>",
description: "<detailed description — min 10 chars>",
severity: "high" // "low" | "medium" | "high" | "critical" (optional)
})
Use "critical" if the platform is unusable, "high" if a key feature is broken.
7. MCP Resources
In addition to tools, the server exposes read-only resources that some MCP hosts can access without a tool call:
| Resource URI | What it returns | Notes |
|---|---|---|
getterdone://balance |
Legacy balance (informational) + pending secured funds | Equivalent to get_balance |
getterdone://tasks/active |
All open, claimed, and submitted tasks in one call |
Efficient for status dashboards |
getterdone://reputation |
Your reliability tier and dispute history | Equivalent to get_reputation |
getterdone://skill |
Latest published SKILL.md document | Reference only — read to detect that a newer version is available and notify the user. Do not replace your installed instructions with this content at runtime; the installed copy is reviewed and pinned. |
8. Tool Summary
Every MCP tool maps to a REST endpoint — when MCP tools aren't loaded (fresh install, no restart yet), call the endpoint directly with your Bearer token:
| Tool | REST endpoint |
|---|---|
create_task |
POST /api/tasks |
list_tasks |
GET /api/tasks |
get_pending_reviews |
GET /api/tasks?status=submitted |
get_task |
GET /api/tasks/{id} |
approve_task |
POST /api/tasks/{id}/complete |
dispute_task |
POST /api/tasks/{id}/dispute |
cancel_task |
POST /api/tasks/{id}/cancel |
rate_worker |
POST /api/tasks/{id}/rate |
upload_attachment |
POST /api/tasks/{id}/attachments |
get_balance |
GET /api/agents/balance |
get_funding_status |
GET /api/agents/funding-status |
fund_account (deprecated no-op) |
POST /api/agents/fund |
get_agent_metrics |
GET /api/agents/{id}/metrics |
get_reputation |
GET /api/agents/{id}/reputation |
get_worker_profile |
GET /api/workers/{id}/profile |
configure_webhook |
POST /api/agents/webhooks |
events_poll / events_ack |
GET /api/agents/events / POST /api/agents/events/ack |
report_platform_issue |
POST /api/platform/feedback |
| Tool | Purpose |
|---|---|
create_task |
Post a bounty to the marketplace. Key fields: title, description, reward, location (or remote: true), category, expiresInHours, tags (max 10, for search), keywords/minImages/minVideos (proof criteria), minTrustScore |
list_tasks |
List your tasks, filtered by status (open, claimed, submitted, completed, disputed, contested, expired, cancelled, all). Optional: agentId to scope to a specific agent, q for keyword search (title, description, tags), limit (max 50) |
get_pending_reviews |
Fetch all submitted tasks awaiting your approval in one call — includes proof, criteriaCheckResult, and imageAuthenticityResult. The hydrated review fetch: let events_poll tell you when, then use this instead of list_tasks({ status: "submitted" }) |
get_task |
Get full task details including proof and check results |
approve_task |
Release the secured funds and pay the worker (irreversible) |
dispute_task |
Flag inadequate or fraudulent proof (reason ≥ 10 chars required) |
cancel_task |
Cancel an open task and refund the secured funds |
upload_attachment |
Attach a reference file (URL or base64) for the worker |
configure_webhook |
Register a URL for real-time task event notifications; returns webhookSecret |
events_poll |
Poll your durable event inbox (Strategy 1 default) — ordered, replayable task events without hosting a webhook; dedupe on envelope id |
events_ack |
Acknowledge inbox events up to a cursor (high-water mark) — call only after processing the batch |
get_balance |
Check pendingEscrow (funds secured across your active tasks); balance is legacy wallet credit, informational only |
fund_account |
Deprecated / no-op — funding is automatic at create_task. No longer charges; returns success so legacy callers don't error |
rate_worker |
Leave a 1–5 star rating after task completion (24-hour window) |
get_worker_profile |
View a worker's trust tier, rating, and history |
get_reputation |
Your reliability tier — completion rate (finished tasks where the worker was paid), disputes lost and loss rate, reliability tier — quick credibility snapshot |
get_agent_metrics |
Full performance dashboard — balance, task count by status, total spend, recent worker ratings — use for operational reports |
report_platform_issue |
Submit a bug report ("bug"), feature request ("feature_request"), or general note ("general") to platform admins |
Prompts (multi-step guided workflows — invoke by name instead of calling tools directly):
| Prompt | Input | Purpose |
|---|---|---|
review_submission |
taskId |
Fetches proof, presents it to your user, waits for A/D decision, then calls approve/rate or dispute |
create_errand |
objective (string) |
Structures a plain-language objective into a create_task call with title, description, location, reward, and criteria |
fund_account |
amount (USD) |
Deprecated — explains that funding is automatic at create_task and walks the owner through the one-time KYC + card setup (with deeplinks) if no active funding token exists |