Hands for Agents Tasks API
The Tasks API from Hands for Agents — 3 operation(s) for tasks.
The Tasks API from Hands for Agents — 3 operation(s) for tasks.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/handsforagents-com:handsforagents-com-tasks-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hands for Agents Tasks API
version: 0.1.0
description: 'Engineering hands and on-site presence in the Czech Republic (EU) for AI agents. A person designs, makes, assembles, measures, tests, verifies or ships a physical thing, documents it with evidence, and the agent''s operator gets an invoice from a registered EU company.
Provider: your engineering s.r.o., company ID (IČO) 09738100, Praha 4, Czech Republic.
Every request is screened and quoted by a human; nothing is accepted automatically. `GET /services` and `POST /quotes` need no authentication. The other operations need the `access_token` returned by `POST /quotes`, sent as a bearer token. The same five operations are MCP tools at ; the MCP tools get_status, create_task and confirm_delivery answer briefly by default (get_status with `full: true` gives the complete record), while this REST API always returns the complete record.'
termsOfService: https://handsforagents.com/terms.html
contact:
name: your engineering s.r.o.
email: tasks@handsforagents.com
url: https://handsforagents.com
servers:
- url: https://api.handsforagents.com/v1
tags:
- name: Tasks
paths:
/tasks:
post:
operationId: create_task
summary: Accept a quote and the terms; get a Stripe Checkout URL
description: 'Returns checkout_url, the payment link of the first payment, as the quote''s payment_schedule states: full price, card hold, or deposit. Give it to the user or the operator to pay by card; it is valid for 4 days from the moment the task is created (GET /pay/{task_id} redirects to a Stripe Checkout page, a fresh one if the earlier page expired). The contract is concluded when that payment is received or authorised. No account is created. The answer has next_action (what to do now), like get_status; a test order has test_mode true and no checkout_url: the operator will simulate the payment AFTER you order, so right now nothing is paid or simulated. Report to the user only what this response says; do not add conclusions.'
security:
- taskToken: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCreate'
responses:
'201':
description: Task created, awaiting payment
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'400':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
tags:
- Tasks
/tasks/{task_id}:
get:
operationId: get_task_status
summary: get_status for a task
security:
- taskToken: []
description: 'Always read next_action: one or two sentences on what to do now (wait; review the quote and call create_task; pay checkout_url; call confirm_delivery), with paid, quote_valid_until and expected_ship_date. messages is the thread with the operator, newest first (from: operator or agent). Every status can change at any time: call again whenever the user asks, never answer from memory, and report only what the response says, without conclusions of your own. This REST answer is always the complete record; the MCP tools get_status, create_task and confirm_delivery answer briefly by default (get_status with full=true returns this complete record). updates lists the last 3 status changes, oldest first, each with the task status at that moment; older_updates counts the earlier ones (every status change is also e-mailed to the contact address). To answer an operator question, call get_status with message: send the same request as POST with a JSON body {"message": "..."} (plain text, 1-2000 characters); it is stored in the same thread and e-mailed to the operator, and the answer is the usual status, including your message (message_sent: true). Read the operator''s message first, then reply: while the operator has written something you have not read yet, your message is NOT stored; the answer is the usual status with that message in messages, message_sent: false and a next_action that says so, and you send your answer with the next call. Send it once and only with what the user told you: the same text again within 10 minutes is ignored (duplicate_ignored: true). E-mail to tasks@handsforagents.com with the quote_id in the subject still works. test_mode true marks a test order: nothing is paid or invoiced.'
parameters:
- name: task_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Task status, updates, evidence and shipments
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'401':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
tags:
- Tasks
post:
operationId: get_task_status_with_message
summary: get_status for a task, with a message to the operator
security:
- taskToken: []
description: 'This is get_status with the optional message, as a POST so that the text is in the body, not in the URL (which servers log). Without a message (empty body, or {"message": null}) it is a plain status call, like the GET. With a message it is written to the thread and e-mailed to the operator, and the answer is the same as the GET''s, including your message (from: agent) and message_sent: true. Read the operator''s message first, then reply: while the operator has written something you have not read yet, nothing is stored or e-mailed; the answer is the GET''s, with that message in messages, message_sent: false and a next_action that says so, and you send your answer with the next call (HTTP 200 either way, so check message_sent). Plain text, 1-2000 characters, never interpreted as HTML; an empty or longer message is a 400. Limits: 10 messages per hour per quote (its quote_id and task_id share it) and 30 per hour per client address (429). The same text again within 10 minutes, while it is still the newest message of the thread, is not stored or e-mailed a second time (the answer then has duplicate_ignored: true). It needs the same access_token as the GET; without one, or with another quote''s, nothing is stored or sent (401).'
parameters:
- name: task_id
in: path
required: true
schema:
type: string
requestBody:
required: false
content:
application/json:
schema:
$ref: '#/components/schemas/StatusMessage'
responses:
'200':
description: The same answer as the GET, now including your message; if the operator had written something you had not read, message_sent is false, nothing was stored and the answer shows that message
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'400':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
tags:
- Tasks
/tasks/{task_id}/delivery-confirmation:
post:
operationId: confirm_delivery
summary: Confirm delivery or open a dispute
security:
- taskToken: []
description: accepted=true closes the task and captures a card hold. accepted=false opens a dispute, also at the evidence stage before the balance is paid. Without a confirmation or dispute, the task counts as accepted 7 days after delivery. Report to the user only what this response says; do not add conclusions.
parameters:
- name: task_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DeliveryConfirmation'
responses:
'200':
description: Task confirmed, or dispute opened
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'400':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
tags:
- Tasks
components:
schemas:
TaskCreate:
type: object
required:
- quote_id
- terms_accepted
- terms_version
properties:
quote_id:
type: string
terms_accepted:
type: boolean
const: true
description: Acceptance of https://handsforagents.com/terms.html by the operator.
terms_version:
type: string
description: Currently 2026-09-29.
allow_anonymised_example:
type: boolean
default: false
description: Optional. Consent to publish this task as an anonymised example, under section 10 (Confidentiality) of the terms of service. Overrides the value sent with request_quote. Default false; can be withdrawn by e-mail.
OperatorMessages:
type: array
description: 'The thread with the operator, newest first: the operator''s notes and your own replies (from: agent over REST; the MCP tools show your own replies as from: you). An operator message you have not been shown yet is always included, also in the answer to your own reply (a message of yours sent while it is unread is not stored: message_sent is false). Read the operator''s message first, then reply: call get_status with message (POST with a JSON body over REST); e-mailing the tasks address with the quote_id in the subject also works.'
items:
type: object
required:
- at
- from
- text
properties:
at:
type: string
format: date-time
from:
type: string
enum:
- operator
- agent
text:
type: string
description: Plain text, at most 2000 characters. Never HTML.
Task:
type: object
required:
- task_id
- quote_id
- status
properties:
task_id:
type: string
quote_id:
type: string
status:
type: string
enum:
- awaiting_payment
- paid
- in_progress
- awaiting_balance
- delivered
- confirmed
- disputed
- refunded
- cancelled
description: 'The only task statuses there are, in the usual order: awaiting_payment, paid, in_progress, delivered, confirmed (awaiting_balance with a deposit; disputed, refunded, cancelled). paid is not in_progress: the operator sets in_progress when the work starts, and delivered (not the parcel arriving) is what allows accepting with confirm_delivery.'
checkout_url:
type: string
format: uri
description: 'The payment link of the payment that is due now (GET /pay/{task_id}, signed; opening it redirects to a Stripe Checkout page). Present while status is awaiting_payment or awaiting_balance and the link is valid: 4 days from when the payment was created, 28 for a balance. Never present for a test order.'
checkout_expires_at:
type: string
format: date-time
description: When checkout_url stops working. Present together with checkout_url.
payments:
type: array
items:
type: object
required:
- kind
- amount
- currency
- status
properties:
kind:
type: string
enum:
- full
- card_hold
- deposit
- balance
amount:
type: number
currency:
type: string
enum:
- EUR
- USD
status:
type: string
enum:
- awaiting
- authorised
- paid
- released
- refunded
- simulated
checkout_url:
type: string
format: uri
description: The payment link, while this payment is awaiting and the link is valid.
checkout_expires_at:
type: string
format: date-time
invoice_url:
type: string
format: uri
invoice_url:
type: string
format: uri
paid:
type: boolean
description: 'True when nothing is owed right now: a payment is settled (paid, card hold authorised, or simulated in a test order) and none is awaiting.'
quote_valid_until:
type: string
format: date
expected_ship_date:
type: string
format: date
description: The delivery or dispatch date stated in the quote.
scope:
type: string
description: What was quoted, as stated in the quote.
deliverables:
type: string
description: What you receive, as stated in the quote.
next_action:
type: string
description: 'One or two sentences in English: what the agent or the client should do now.'
messages:
$ref: '#/components/schemas/OperatorMessages'
duplicate_ignored:
type: boolean
description: Present and true only in the answer to a POST whose message was ignored because the same text was already the newest message of the thread (sent within the last 10 minutes).
message_sent:
type: boolean
description: 'Present only in the answer to a POST with a message: true, the message was stored and e-mailed to the operator; false, it was NOT stored because the operator had written something you had not read yet (that message is in messages, now marked as read; next_action says so): send your answer again. Absent when the same text was ignored (duplicate_ignored) and in every plain status call.'
test_mode:
type: boolean
description: 'Present and true only for a test order: no payment link; the operator simulates the payment after the order is placed (until then nothing is paid or simulated); no invoice, no contract.'
performed_by:
type: string
description: Name of the person who did the work.
updates:
type: array
description: The last 3 status changes and events, oldest first. status is the task status at the moment of that event (not the current one).
items:
type: object
properties:
at:
type: string
format: date-time
status:
type: string
note:
type: string
older_updates:
type: integer
minimum: 1
description: How many earlier updates are not listed. Present only when there are any; every status change is also e-mailed to the contact address.
evidence:
type: array
items:
type: object
properties:
name:
type: string
url:
type: string
format: uri
media_type:
type: string
captured_at:
type: string
format: date-time
latitude:
type: number
longitude:
type: number
shipments:
type: array
items:
type: object
properties:
carrier:
type: string
tracking_number:
type: string
tracking_url:
type: string
format: uri
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
message:
type: string
StatusMessage:
type: object
description: Optional body of POST /quotes/{quote_id} and POST /tasks/{task_id}. An empty body is a plain status call.
properties:
message:
type:
- string
- 'null'
minLength: 1
maxLength: 2000
description: 'Your reply to the operator: plain text, 1-2000 characters, never HTML. Null or absent: a plain status call.'
DeliveryConfirmation:
type: object
required:
- accepted
properties:
accepted:
type: boolean
reason:
type: string
description: 'Required when accepted is false: what differs from the quote.'
securitySchemes:
taskToken:
type: http
scheme: bearer
description: access_token returned by POST /quotes. Valid for that quote and the task created from it.
x-status: live