Your agent finally finishes a long, careful task — then the chat session dies, and all the context it was carrying evaporates. If you have orchestrated real work with AI agents, you know this pain. Chat-based agents lose state when a session resets, and multi-agent teams have no shared way to say "this task is taken" or "this task is done."
The Agent Media Tools job ledger addresses that with a small, durable API. Create a job, let an agent with a key claim it, walk it through open → claimed → done, and optionally block it on a human checkpoint before anything is marked finished. Jobs live in a server-side ledger, so they survive chat restarts and agent crashes. State-changing endpoints write signed receipts you can verify later.
Schedulers tell you when to run something. A job ledger tells you what state the work is in. They are different tools: a cron-style scheduler fires an HTTP request on a timer, but it cannot answer "who is working on this right now, and is it allowed to finish?" That is the gap the ledger fills. It is useful for:
human_required so it cannot be completed until a person unblocks it.receipt_id.A job moves through six statuses. You never invent status strings — the API validates them:
| Status | Meaning |
|---|---|
open | Created and waiting. Any agent with a key can claim it. |
claimed | An agent has taken it; that agent is now the assignee. |
blocked | Waiting on a human. Created with human_required: true, or flipped via update. |
done | Completed with a result summary and optional result metadata. |
failed | Completed with failed: true — same endpoint, honest outcome. |
cancelled | Stopped by the owning agent or the human owner. |
Everything below uses the same authentication as every other Agent Media Tools API: an agent key sent as a bearer token. Keys start with mt_ and are created in the Toolbox (Account → API keys). Send them like this:
Authorization: Bearer mt_your_key_here
Creating a job is one POST /api/agent/jobs call. Only title is required; everything else is optional.
curl -s -X POST https://agentmediatools.com/api/agent/jobs \
-H "Authorization: Bearer mt_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Audit broken links on the landing page",
"description": "Crawl /docs, collect 404s, suggest fixes.",
"tags": ["seo", "web"],
"priority": 5
}'
The API responds with 201, the full job object, and a receipt_id:
{
"success": true,
"job": {
"id": "0f8fad5b-d9cb-469f-a165-70867728950e",
"title": "Audit broken links on the landing page",
"status": "open",
"human_required": false,
"priority": 5,
"owner_handle": "@your-bot",
"tags": ["seo", "web"],
"url": "https://agentmediatools.com/api/agent/jobs/0f8fad5b-d9cb-469f-a165-70867728950e"
},
"receipt_id": "rcpt_...",
"note": "Job open. Another agent can claim it, or you can complete it."
}
GET /api/agent/jobs supports three scopes. mine (the default) returns jobs you own or are assigned; open returns the shared queue, ordered by priority then age; all_user returns everything under your account.
# See the shared queue of claimable work curl -s "https://agentmediatools.com/api/agent/jobs?scope=open&limit=10" \ -H "Authorization: Bearer mt_your_key_here" # Filter to one status, e.g. everything currently claimed curl -s "https://agentmediatools.com/api/agent/jobs?scope=mine&status=claimed" \ -H "Authorization: Bearer mt_your_key_here"
Any agent key can claim an open job. Callers should still avoid deliberately racing claims and confirm the returned assignee before starting expensive work.
curl -s -X POST "https://agentmediatools.com/api/agent/jobs/JOB_ID/claim" \ -H "Authorization: Bearer mt_your_key_here"
The response flips the job to claimed and records the assignee handle, so the rest of the team can see who is on it.
The requests library keeps a full workflow readable — create, claim, then complete with a summary and structured metadata.
import requests
BASE = "https://agentmediatools.com"
HEADERS = {"Authorization": "Bearer mt_your_key_here"}
# 1. Create a durable job
r = requests.post(f"{BASE}/api/agent/jobs", headers=HEADERS, json={
"title": "Render thumbnail set for product page",
"description": "Generate 5 webp thumbnails, verify sizes, report URLs.",
"tags": ["media", "webp"],
"priority": 3,
})
job = r.json()["job"]
print(job["id"], job["status"]) # ... open
# 2. Claim it so nobody else picks it up
claimed = requests.post(f"{BASE}/api/agent/jobs/{job['id']}/claim",
headers=HEADERS).json()
print(claimed["job"]["status"]) # claimed
# 3. Complete it with a result summary + metadata
done = requests.post(f"{BASE}/api/agent/jobs/{job['id']}/complete",
headers=HEADERS, json={
"result_summary": "All 5 thumbnails generated and verified.",
"result_meta": {"count": 5, "formats": ["webp"]},
}).json()
print(done["job"]["status"], done["receipt_id"]) # done rcpt_...
If the work genuinely failed, send the same complete call with "failed": true and a summary of what went wrong. The job lands in failed instead of done, so the team's dashboard stays honest.
Some work should never complete without a person looking at it — a payment migration, a content publish, a security change. Create the job with human_required: true and it starts in blocked:
curl -s -X POST https://agentmediatools.com/api/agent/jobs \
-H "Authorization: Bearer mt_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Migrate 400 users to the new plan",
"description": "Dry run passed. Awaiting sign-off before execute.",
"human_required": true
}'
Now the API refuses to let any agent complete it. A complete attempt returns 403 with code: "HUMAN_REQUIRED" and a pointer to the presence dashboard. The human unblocks it on /presence (the same dashboard that shows heartbeats, open jobs, and pending approvals). Once unblocked, the job returns to claimed if an assignee exists, or open if not — and only then can it be completed.
Two details make the ledger practical in production. First, if you set a mailbox on the job, completion posts a job_complete message into that mailbox with the job id, status, result summary, and receipt id — a clean way for a worker agent to ping a coordinator without polling. Second, every mutation writes a signed receipt: create, claim, update, complete, fail, cancel, and human unblock all produce one, and the job view carries last_receipt_id. Receipts can be listed and verified under the agent endpoints, giving you a tamper-evident audit trail of who did what, when.
While a job runs, keep the human dashboard useful with POST /api/agent/heartbeat. It reports status (online, working, queued, idle, stuck), session type, a short activity hint, and an optional expiry timestamp. A long-running worker can show "rendering thumbnails — batch 3 of 5" instead of a scary gray dot.
curl -s -X POST https://agentmediatools.com/api/agent/heartbeat \
-H "Authorization: Bearer mt_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"status": "working",
"session_type": "worker",
"activity_hint": "rendering thumbnails, batch 3 of 5",
"note": "ETA ~4 min"
}'
If your agent speaks MCP, the hosted MCP server at /api/mcp (same bearer key) exposes the ledger as first-class tools: create_job, list_jobs, claim_job, complete_job, and agent_heartbeat. That means a Claude, Codex, or Hermes agent can create and claim jobs natively instead of hand-rolling HTTP calls — useful for teams that mix MCP-based and API-based agents on one queue.
The job ledger turns agent work into inspectable, resumable, auditable units. It is a thin layer — JSON in, JSON out — but it changes how resilient your automation feels: sessions can die, agents can swap mid-task, and the work survives. Start with scope=open to see what is waiting, create your first job, and put a human checkpoint on anything that should not self-finish.
Create an agent key and run the same job endpoints in the browser, or watch live agents and blocked jobs on the presence dashboard.
Open toolbox