Menu path: Dashboard > Tasks

Tasks

Overview

Tasks let an AI such as ChatGPT or Claude run work on your own server (a worker). Say "Place an order for 30 of A-1042 in our ERP", and the AI sends the job to your worker and shows you the result in the conversation.

  • 3Min API only delivers the task and records its status — the worker does the actual work
  • A worker is an HTTP server that you run

When to use it

  • Having internal systems do something — place an ERP order, check stock, search an internal database
  • Work that takes time — generating reports, bulk conversions (up to 24 hours)
  • Scripts that only run on your own machine — attach a public URL with a tunnel (see below)

If the goal is to store and collect data, use an endpoint instead.

Not comfortable writing worker code? Ask the AI you already use: "Build me a 3minapi task worker". The AI reads the same contract as this page from the help tool and writes the code.

How to get here

  • Register a worker: Dashboard → TasksWorkers tab → Register worker
  • Check runs: Dashboard → TasksRuns tab

How it works

① Register a worker on the web      (once)
② AI → task_create                  task created, status working
③ 3Min API → POST to worker         signed request (includes callback_url)
④ Worker → 2xx right away           "received" only, not a result
⑤ Worker → callback_url             progress messages (optional) → completed or failed
⑥ AI → task_get                     reads the result
Where What happens there
Web dashboard Register, edit and delete workers; view and rotate secrets; view runs
AI chat List workers, send tasks, check status and results, cancel
Worker server Receive requests, do the work, report the result

The AI cannot see a worker's URL or secrets. Both are handled on the web only.

Registering a worker (web)

Field Description Limit
Name Required. The name the AI uses. Unique regardless of case, cannot be changed 50 characters
Description Required. The AI picks a worker from this description 500 characters
Worker URL Required. The address that receives tasks by POST 2,048 bytes
Authorization header / Authorization value Optional. For the worker's own auth. With only a value, the header is Authorization 128 / 8,192 bytes
  • Write a specific description. The AI sees only the name and description, never the URL. Instead of erp-1, write something like "Places an order in our ERP and returns the order number" — what it takes and what it returns — so the AI can pick it

Two secrets, opposite directions

Registering issues two secrets automatically. Find them on the worker page (Dashboard → TasksWorkers tab → select a worker) with Show secret · Copy secret.

Secret Format Direction Used for
Signing secret whsec_... 3Min API → worker Verifying that a request really came from 3Min API
Callback key tm_task_... worker → 3Min API Sent as Authorization: Bearer when reporting
  • The two values are different and not interchangeable. An endpoint API key (tm_live_ · tm_test_) is not a callback key either
Change What happens What the worker should do
Rotate signing secret For 48 hours, new tasks carry both the new and the previous signature. Retries of tasks created before the rotation (up to about 5 hours) carry only the previous one Keep the previous secret for at least 6 hours, then switch within 48 hours
Regenerate callback key The old key stops working immediately (running tasks included) Switch right away

Rotating again within 48 hours invalidates the original secret immediately. The dashboard shows only the current secret, so copy it before rotating if your worker needs to keep the previous one.

Building a worker

Five rules for workers

  1. Return 2xx within 15 seconds. Do the work after responding
  2. Accept the request with 2xx even if your work fails. Report the failure instead (5xx is retried, other 4xx fail the task at once — see response codes)
  3. Deduplicate on task_id. The same task can arrive twice (at-least-once)
  4. Signature verification is optional but recommended. Without it, anyone who knows the worker URL can send fake tasks (signature verification)
  5. Report the result to callback_url. Send completed or failed once. With no report, the task becomes failed after 24 hours (status report API)

Delivery request

The request 3Min API sends to the worker URL.

POST https://worker.example.com/tasks
Content-Type: application/json
webhook-id: 0199a1b2-...
webhook-timestamp: 1757650867
webhook-signature: v1,K3XkZ1n0...
X-3minapi-Task-Id: 0199a1b2-...
X-3minapi-Delivery-Attempt: 1
Authorization: Bearer xxx
Header Content
webhook-id Task ID (same value as task_id)
webhook-timestamp Send time (Unix seconds). New on every attempt
webhook-signature Signature. Two space-separated values during rotation (signature verification)
X-3minapi-Task-Id Task ID (same value as webhook-id)
X-3minapi-Delivery-Attempt Which delivery attempt this is (from 1)
Your registered auth header Only if you entered an authorization header and value when registering

Body:

{
	"type": "task.created",
	"timestamp": "2026-09-12T04:21:07.123Z",
	"data": {
		"task_id": "0199a1b2-...",
		"input": { "sku": "A-1042", "qty": 30 },
		"callback_url": "https://api.3minapi.com/api/v1/tasks/0199a1b2-.../result"
	}
}
Field Content
task_id Task ID. Use it for deduplication
input The JSON object the AI sent (same values, key order may differ)
callback_url This task's report URL. Send status and results here

Worker response codes

Only the status code counts; the response body is ignored.

Response Result
2xx Accepted. The task stays working until a report arrives
5xx (except 501 · 505), 429, 15-second timeout, network error Retried after 30s · 2m · 10m · 1h · 4h — 6 attempts in total (about 5 hours). A 429 Retry-After is honored only when longer
Other 4xx (401 · 404 · 409, …), 501, 505 failed at once, no retry
  • Return 401 when signature verification fails. If the secret was entered wrong, the very first task fails right away, so you find the setup problem quickly

Signature verification (optional)

The webhook-signature header is the signature. 3Min API computes it with the worker's signing secret, and the worker repeats the calculation to check that it matches.

signed content = "{webhook-id}.{webhook-timestamp}.{raw request body}"
key            = the signing secret without whsec_, base64-decoded bytes
signature      = "v1," + base64( HMAC-SHA256(key, signed content) )
  • With an official Standard Webhooks library it takes a few lines (example code). For packages per language and a manual implementation, see Verifying the webhook signature (same scheme)
  • Verify against the raw body bytes. Parsing the JSON and serializing it again always fails
  • The library rejects a webhook-timestamp more than 5 minutes off (blocks replayed requests)
  • When two signatures arrive, either one matching is enough (rotation rules)
  • If you don't verify, at least check that callback_url starts with https://api.3minapi.com/ — your callback key is sent to that address

Status report API

Send status to the callback_url from the delivery request. Progress messages can be sent many times; completed or failed is sent once.

POST https://api.3minapi.com/api/v1/tasks/{task_id}/result
Authorization: Bearer tm_task_...
Content-Type: application/json
  • URL: callback_url arrives in this format with the task ID filled in. Don't build it yourself — use it as is
  • Callback key: Callback key on the worker page → Copy secret. Keep it in the worker server's environment variables or similar

In progress — the task stays working, and the AI sees the message through task_get

{ "status": "working", "status_message": "1/2 Checking stock" }

Completed

{
	"status": "completed",
	"result": { "order_id": "PO-20260912-001" },
	"status_message": "Order PO-20260912-001 placed"
}

Failed

{
	"status": "failed",
	"error": { "code": 1001, "message": "Out of stock", "data": { "available": 12 } }
}
Field Rule
status One of working · completed · failed
status_message Required for working, optional otherwise. Cut off past 256 characters. For failed without one, error.message is used
result Required for completed. Any JSON except null
error Required for failed. code (integer) + message (string); data is optional
  • The whole body must be 100 KB or less. result and error are never cut off, so a larger body is rejected with 413
  • Each accepted report counts as one API call. Send progress messages only at meaningful steps

Status report API response codes

Code Meaning What the worker should do
202 Accepted. Usually applied within a few seconds
400 Malformed body, invalid task ID, NUL character in a string Fix the code
401 Missing or wrong callback key (endpoint API keys included) Check the key
404 Not a task of this worker Check callback_url
409 The task already finished (completed · failed · cancelled) Stop reporting
413 Body over 100 KB. Nothing was recorded If the task is still working, shrink and resend
415 Content-Type is not JSON Fix the header
503 Temporary outage Resend shortly

Example code (Node.js)

Requires Node.js 18 or later and "type": "module" in package.json.

// npm i express standardwebhooks
import express from 'express';
import { Webhook } from 'standardwebhooks';

// The signing secret (whsec_...) and the callback key (tm_task_...) are different values
const wh = new Webhook(process.env.TASK_SIGNING_SECRET.replace('whsec_', ''));
const CALLBACK_KEY = process.env.TASK_CALLBACK_KEY;
// Accepted task_ids — store these in a database or Redis in production
const seen = new Set();

const app = express();

// The signature covers the raw bytes. Parsing the JSON first makes verification fail.
app.post('/tasks', express.raw({ type: 'application/json' }), (req, res) => {
	let envelope;
	try {
		envelope = wh.verify(req.body, {
			'webhook-id': req.header('webhook-id'),
			'webhook-timestamp': req.header('webhook-timestamp'),
			'webhook-signature': req.header('webhook-signature')
		});
	} catch {
		return res.sendStatus(401);
	}

	const { task_id, input, callback_url } = envelope.data;

	// Rule 3 — don't process a task you've already accepted
	if (seen.has(task_id)) return res.sendStatus(202);
	seen.add(task_id);

	// Rule 1 — respond first, work afterwards
	res.sendStatus(202);
	run(input, callback_url);
});

async function run(input, callbackUrl) {
	try {
		// Progress message — the task stays working; the AI sees it through task_get
		await report(callbackUrl, { status: 'working', status_message: '1/2 Checking stock' });
		await checkStock(input); // ← your actual work

		await report(callbackUrl, { status: 'working', status_message: '2/2 Placing the order' });
		const orderId = await createOrder(input);

		// Completed — the result plus a short summary
		await report(callbackUrl, {
			status: 'completed',
			result: { order_id: orderId },
			status_message: `Order ${orderId} placed`
		});
	} catch (err) {
		// Rule 2 — if the work or a report fails, report failed
		await report(callbackUrl, {
			status: 'failed',
			error: { code: 1001, message: String(err?.message ?? err) },
			status_message: 'Could not place the order'
		}).catch(console.error);
	}
}

// callbackUrl — the callback_url from the delivery request, used as is
async function report(callbackUrl, body) {
	for (let attempt = 1; attempt <= 3; attempt++) {
		const res = await fetch(callbackUrl, {
			method: 'POST',
			headers: {
				Authorization: `Bearer ${CALLBACK_KEY}`,
				'Content-Type': 'application/json'
			},
			body: JSON.stringify(body)
		}).catch(() => null);

		// 202 accepted, 409 task already finished — nothing more to send
		if (res?.status === 202 || res?.status === 409) return;
		// 400 · 401 · 404 · 413 give the same result when resent
		if (res && res.status !== 503) throw new Error(`report rejected: HTTP ${res.status}`);
		// Only 503 and network errors are retried after a pause
		if (attempt < 3) await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
	}
	throw new Error('report failed: service unavailable');
}

app.listen(8080);

Running a worker on your own machine

A worker needs a public URL reachable from the internet. For a machine behind a home or office router, use a tunnel to attach a public HTTPS address to a local port.

cloudflared tunnel --url http://localhost:8080
ngrok http 8080
  • Setup and usage: Cloudflare Quick Tunnels · ngrok getting started
  • Add your path to the printed address (e.g. https://xxxx.trycloudflare.com/tasks) and register it as the Worker URL
  • Temporary addresses without an account change on every run. Update the Worker URL each time, or use the service's fixed-address option
  • If the machine is off or the tunnel is down, the task usually becomes failed after retries (about 5 hours). A service that returns 404 for an offline address makes it failed at once

Sending tasks (AI)

Once a worker is registered, you can use it straight from an AI chat. No need to know the tool names.

  • "Place an order for 30 of A-1042 in our ERP"
  • "What happened to the order I sent earlier?"
  • "Cancel the task I just sent"
Tool What it does
worker_list Names and descriptions of registered workers (no URLs or secrets)
task_create Creates a task from a worker name and input (JSON object, up to 100 KB)
task_get One task's status, message, and result or error
task_list Recent tasks (without result bodies)
task_cancel Cancels running tasks (up to 100)
  • The same worker and the same input within 2 minutes (up to about 4 minutes depending on timing) return the existing task. Completed and cancelled tasks are returned as they are; only a failed task is created again
  • Cancelling does not notify the worker. Only the remaining delivery attempts stop; later reports from a worker that already received the task get 409

Checking status

Status Meaning Can change?
working Being delivered, or the worker is processing it Yes
completed The worker reported completion Final
failed The worker reported failure, delivery failed, or 24 hours passed Final
cancelled Cancelled Final

The reason for failed is kept in status_message.

Cause status_message example
Worker reported failure The worker's message (or error.message if none)
Retries exhausted The task could not be delivered to the worker after 6 attempts (no response within 15s).
Immediate failure status The task could not be delivered to the worker after 1 attempt (HTTP 401).
24 hours passed Worker did not respond within 24 hours.

Where to check:

  • AItask_get · task_list. The worker's result is passed on as is, without summarizing
  • WebRuns tab. Filter by period (7 · 30 · 60 days; 7 on Free) and status, and click a row to see input, result, error and delivery. If delivery finally failed, Delivery shows attempts, last response and reason

Limits

Item Value
Workers 1 on Free / unlimited on paid plans
Tasks · concurrent runs Unlimited (within your monthly API call quota)
Deadline 24 hours after creation — then failed (checked hourly, so up to about 25 hours)
History retention At least 60 days on paid plans / on Free, workers and their runs are deleted 7 days after the worker was registered
input size 100 KB
Report body 100 KB (status_message cut off at 256 characters)
Delivery timeout · retries 15 seconds · 6 attempts over about 5 hours
Signing secret rotation grace 48 hours
Callback key regeneration Old key stops working immediately
API call counting One successful task tool call and one accepted report each count as 1 call. Deliveries and retries count as 0. Reports are accepted even over quota
Delivery guarantee At-least-once (the same task can arrive twice)

Privacy

  • A worker's result and error reach the AI conversation unmasked, as is
  • 3Min API does not detect personal data inside free-form JSON, so don't put more personal data in results than needed

Troubleshooting

  • Every task fails right away: Check Delivery → Last response in the run details. A 401 usually means the callback key was pasted in place of the signing secret, or the previous secret was removed right after a rotation
  • A task stays working: The worker returns 2xx but never reports. Check the status report API response codes in the worker's logs
  • Reports get 401: Make sure it's the callback key starting with tm_task_, and not a key from before a regeneration
  • Reports get 409: The task already finished or was cancelled. No need to send more
  • Reports get 413: The body is over 100 KB. Shrink result and resend
  • Sending the same request doesn't create a new task: The same input within 2 minutes returns the existing task. Add something like a date to input to tell them apart