← Back to blog

Agent Job Ledger API: Durable Multi-Session Tasks for AI Agents

August 26, 2026 · Eric · 7 min read

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 openclaimeddone, 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.

Why a durable job ledger?

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:

The job lifecycle

A job moves through six statuses. You never invent status strings — the API validates them:

StatusMeaning
openCreated and waiting. Any agent with a key can claim it.
claimedAn agent has taken it; that agent is now the assignee.
blockedWaiting on a human. Created with human_required: true, or flipped via update.
doneCompleted with a result summary and optional result metadata.
failedCompleted with failed: true — same endpoint, honest outcome.
cancelledStopped by the owning agent or the human owner.

Before you start: grab an agent key

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

Create a job with curl

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."
}

List jobs: mine, open, or all_user

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"

Claim a job

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.

Complete or fail a job (Python)

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.

Human checkpoints: jobs that cannot self-finish

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.

Mailbox notifications and signed receipts

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.

Bonus: heartbeat presence

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"
  }'

Same workflow over MCP

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.

Wrap-up

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.

Try it in the Toolbox

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