atto.

Attodynamo · Developers

Agent-to-Atto API · v0Private beta

Send a physical task. Get back a verified record.

The Agent-to-Atto API lets any software hand a real-world job to Attodynamo's execution layer and get back an atto: a sealed, two-sided verified record that the work happened.

One contract in front of every execution model. An AI agent, a property manager's dashboard, a NOC script: if you can make an HTTPS request, you can create an atto. Nothing in the request assumes an LLM caller.

Request access or email [email protected] with your name, company, and what you want to automate.

How a request moves

CALLERS AI agentany LLM runtime PM dashboardhuman-operated tool NOC / ops scriptcurl, cron, anything POST /v1/attos Bearer atto_… key GATEWAY api.attodynamo.ai/v1 stateless edge function picks the model forwards to its adapter prefixes ids: homie_ · farm_ GET /v1/models lists what is live homie_… farm_… m03_… MODELS · same four adapter endpoints each 01 · Homiehome services · live adapter atto-agentsatto-mandatesatto-attosatto-record engine orders · prosSMS · paymentscustomer YES the atto follows the job, whatever moved it 02 · FarmAI data centers · example adapter atto-agentsatto-mandatesatto-attosatto-record engine (simulated) sites · smart handstechnician sign-offsite telemetry recorded about four minutes after authorization 03 · next modelfleets, warehouses, … implement the four adapter endpoints,add one registry entry.Nothing changes for callers.
The gateway holds no state. It reads the model from the request (an explicit field or the id prefix), forwards the call to that model's four adapter endpoints, and prefixes the ids it returns so the next call routes itself. Each model keeps its own engine; only the adapter is shared shape.

The atto object

Six pillars, nine statuses, one record

the requestservice · place · window mandate checkor principal APPROVE dispatch; an operatoraccepts the job operator marksthe job complete side 1 + side 2pro + customer · tech + telemetry sealedsha256 of its own content Scope Authorize Coordinate Execute Verify Record service, location_id,window, success criteria amount, mandate,decision, payment mode operators, window,dependencies, dispatch started, completed,proof on file two timestamps, method,evidence summary, every timestamp,content_hash scoped authorized awaiting_approval → coordinated executing verified recorded is_atto: true declined cancelled principal said nountil an operator is assigned
A request is not an atto until it is executed, verified by two independent sides, and sealed. Verification is two-sided in every model; what the two sides are is the model's choice.
BlockWhat it holdsFilled by
scopeThe catalog service, the place (location_id), the time window, urgency, success criteria, your description.The request.
authorizeAmount, currency, the mandate it was checked against, remaining monthly budget, the decision, the payment mode.The mandate check, or the principal's approval.
coordinatedispatch, operators (opaque refs, never names), window, dependencies.Dispatch; an operator accepting, dropping or rescheduling.
executestarted_at, completed_at, the operator, proof on file.The operator marking the job complete.
verifyTwo timestamps, method, evidence.Homie: pro completion + customer SMS "YES". Farm: technician sign-off + site telemetry.
recordThe sealed summary: service, amount, every timestamp, sealed_at, content_hash.Sealing, the moment both verifications exist.

There are no other top-level fields. Operators and dependencies live in coordinate, evidence in verify, the sealed summary in record.


Models

One contract. Many physical worlds.

ModelDomainPlacesVerificationStatus
01 · HomieHome serviceshomespro completion + customer SMSlive
02 · FarmAI data centerssitestechnician sign-off + site telemetryexample
03 · nextFleets, warehouses, telecom——planned

Every id the API returns is prefixed with its model (homie_…, farm_…). Send it back as-is and the request routes itself. GET /v1/models lists what is live.


Access

Two credentials, one direction

Principal

The person or organisation whose place and money it is. Creates agents and mandates, reads and approves their own attos. Homie: a customer's sign-in. Farm: the site operator's token.

Agent key atto_…

Issued once by POST /v1/agents; only its hash is stored. Creates and reads attos. Cannot create agents or widen a mandate: authority flows from the principal down, never up.

Mandate

What the agent may do: which places, which categories, $ per job, $ per month, above what amount to ask the principal, valid until when. The catalog names the price; the request never does.


Endpoints

EndpointAuthWhat it does
GET /v1/modelsnoneThe models and whether each is available.
POST /v1/agentsprincipalRegister an agent; the key is returned once.
POST /v1/mandatesprincipalBound what the agent may spend, where, on what.
POST /v1/attosagent keySend a task. 201 authorized, 202 awaiting approval, 200 idempotent replay.
GET /v1/attos/{id}agent or principalThe full atto as it fills. GET /v1/attos lists the last 50.
GET /v1/attos/{id}/recordagent or principalThe sealed record only, with is_atto.
POST /v1/attos/{id}principal / agent{ action: approve | decline | cancel }.

Base URL https://api.attodynamo.ai/v1. Every response is JSON. Every error is { error, message, details? } with a meaningful HTTP status. Send Idempotency-Key on POST /v1/attos and a retry returns the same atto.


Examples

From a key to a sealed record

1 · See which models are live

GET /v1/models
Request
curl https://api.attodynamo.ai/v1/models
Response 200
{
  "api": "Agent-to-Atto API",
  "version": "v0",
  "tagline": "Send a physical task. Get back a verified record.",
  "models": [
    { "key": "homie", "model": "01", "name": "Homie", "domain": "Home services",   "places": "homes", "status": "live",    "available": true },
    { "key": "farm",  "model": "02", "name": "Farm",  "domain": "AI data centers", "places": "sites", "status": "example", "available": true }
  ]
}

2 · Register an agent

POST /v1/agents

The principal does this once, signed in as themselves. The key is shown once; only its hash is kept.

Request
curl -X POST https://api.attodynamo.ai/v1/agents \
  -H "Authorization: Bearer $PRINCIPAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "model": "homie", "name": "Property manager bot" }'
Response 201
{
  "agent_id": "homie_b7e2c1a0-5d3f-4e8a-9b21-0c6d7e8f9a10",
  "name": "Property manager bot",
  "api_key": "atto_x7KqPmR2nVbT9cWdYeZf3gHj4kLm5nPq6rSt7uVw",
  "key_prefix": "atto_x7KqPm",
  "status": "active",
  "warning": "Store api_key now. It is shown once; only its hash is kept."
}

3 · Give it a mandate

POST /v1/mandates

What the agent may spend, where, on what, and above what amount the principal wants to be asked.

Request
curl -X POST https://api.attodynamo.ai/v1/mandates \
  -H "Authorization: Bearer $PRINCIPAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "homie_b7e2c1a0-5d3f-4e8a-9b21-0c6d7e8f9a10",
    "location_ids": ["c0ffee12-7a1b-4c2d-8e3f-1a2b3c4d5e6f"],
    "allowed_categories": ["Plumbing", "Electrical"],
    "max_per_job": 250,
    "max_per_month": 1000,
    "approval_threshold": 150,
    "valid_days": 30
  }'
Response 201
{
  "mandate": {
    "mandate_id": "homie_d41d8cd9-8f00-4b20-a9e9-800998ecf842",
    "agent_id": "homie_b7e2c1a0-5d3f-4e8a-9b21-0c6d7e8f9a10",
    "location_ids": ["c0ffee12-7a1b-4c2d-8e3f-1a2b3c4d5e6f"],
    "allowed_categories": ["Plumbing", "Electrical"],
    "max_per_job": 250, "max_per_month": 1000, "approval_threshold": 150,
    "valid_from": "2026-11-03T18:00:00Z", "valid_to": "2026-12-03T18:00:00Z",
    "status": "active"
  }
}

4 · Send a physical task

POST /v1/attos

The agent speaks from here on. It names a catalog service and a place; the price comes from the catalog. Within the mandate, the job is authorized and dispatched in the same call.

Request
curl -X POST https://api.attodynamo.ai/v1/attos \
  -H "Authorization: Bearer $ATTO_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unit-3b-faucet-2026-11-03" \
  -d '{
    "mandate_id": "homie_d41d8cd9-8f00-4b20-a9e9-800998ecf842",
    "scope": {
      "service_id": "PL-KT-001",
      "location_id": "c0ffee12-7a1b-4c2d-8e3f-1a2b3c4d5e6f",
      "window": "asap",
      "success_criteria": "faucet no longer drips when closed; no leak under sink",
      "description": "kitchen faucet drips from the spout when off"
    },
    "requester": { "system": "pm-dashboard", "unit": "3B" }
  }'
Response 201 · authorized
{
  "atto_id": "homie_4a1d2e3f-6b7c-4d8e-9f01-23456789abcd",
  "order_id": "9c2f1e0d-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
  "status": "authorized",
  "requester": { "system": "pm-dashboard", "unit": "3B", "agent_id": "homie_b7e2c1a0-…", "agent_name": "Property manager bot", "key_prefix": "atto_x7KqPm" },
  "scope": {
    "service": { "id": "PL-KT-001", "category": "Plumbing", "subcategory": "Dripping kitchen faucet" },
    "location_id": "c0ffee12-7a1b-4c2d-8e3f-1a2b3c4d5e6f",
    "window": { "from": "2026-11-03T21:00:00Z", "to": "2026-11-03T21:30:00Z", "requested": "asap" },
    "urgency": "asap",
    "success_criteria": "faucet no longer drips when closed; no leak under sink",
    "requested_at": "2026-11-03T18:02:11Z"
  },
  "authorize": {
    "amount": 129, "currency": "USD",
    "mandate_id": "homie_d41d8cd9-8f00-4b20-a9e9-800998ecf842",
    "max_per_job": 250, "max_per_month": 1000, "monthly_remaining_after": 871,
    "decision": "within_mandate", "authorized_at": "2026-11-03T18:02:12Z",
    "payment": { "mode": "pay_after_service" }
  },
  "coordinate": { "dependencies": [], "operators": [], "dispatch": { "dispatched_at": "2026-11-03T18:02:13Z", "invited": 3 } },
  "execute": {}, "verify": {}, "record": {},
  "created_at": "2026-11-03T18:02:11Z", "updated_at": "2026-11-03T18:02:13Z"
}

5 · Read it back as it fills

GET /v1/attos/{atto_id}

Poll until the status is recorded, declined or cancelled. This is the same atto the next day, sealed.

Request
curl https://api.attodynamo.ai/v1/attos/homie_4a1d2e3f-6b7c-4d8e-9f01-23456789abcd \
  -H "Authorization: Bearer $ATTO_AGENT_KEY"
Response 200 · recorded
{
  "atto_id": "homie_4a1d2e3f-6b7c-4d8e-9f01-23456789abcd",
  "order_id": "9c2f1e0d-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
  "status": "recorded",
  "scope": { "service": { "id": "PL-KT-001", "category": "Plumbing", "subcategory": "Dripping kitchen faucet" }, "location_id": "c0ffee12-…", "window": { "from": "2026-11-03T21:00:00Z", "to": "2026-11-03T21:30:00Z", "requested": "asap" } },
  "authorize": { "amount": 129, "currency": "USD", "decision": "within_mandate", "authorized_at": "2026-11-03T18:02:12Z", "payment": { "mode": "pay_after_service" } },
  "coordinate": {
    "dispatch": { "dispatched_at": "2026-11-03T18:02:13Z", "invited": 3 },
    "operators": [{ "role": "pro", "operator_ref": "7e9b0a1c-…", "accepted_at": "2026-11-03T18:09:40Z" }],
    "dependencies": [],
    "window": { "from": "2026-11-03T21:00:00Z", "to": "2026-11-03T21:30:00Z" },
    "accepted_at": "2026-11-03T18:09:40Z"
  },
  "execute": { "operator_ref": "7e9b0a1c-…", "completed_at": "2026-11-03T21:41:05Z", "proof_on_file": true },
  "verify": {
    "method": "two_sided_sms",
    "pro_confirmed_at": "2026-11-03T21:41:05Z",
    "customer_confirmed_at": "2026-11-03T21:52:30Z",
    "verified_at": "2026-11-03T21:52:30Z",
    "evidence": [{ "type": "photo", "on_file": true }]
  },
  "record": {
    "atto_id": "4a1d2e3f-6b7c-4d8e-9f01-23456789abcd",
    "type": "physical_service_job",
    "service": { "category": "Plumbing", "subcategory": "Dripping kitchen faucet" },
    "amount": 129, "currency": "USD",
    "scoped_at": "2026-11-03T18:02:11Z", "authorized_at": "2026-11-03T18:02:12Z",
    "executed_at": "2026-11-03T21:41:05Z", "verified_at": "2026-11-03T21:52:30Z",
    "verification": { "pro_confirmed_at": "2026-11-03T21:41:05Z", "customer_confirmed_at": "2026-11-03T21:52:30Z", "proof_on_file": true },
    "sealed_at": "2026-11-03T21:52:30Z",
    "hash_alg": "sha256",
    "content_hash": "3f1a9c0b7d2e4f6a8b1c3d5e7f9a0b2c4d6e8f1a3b5c7d9e0f2a4b6c8d0e1f3a"
  },
  "created_at": "2026-11-03T18:02:11Z", "updated_at": "2026-11-03T21:52:30Z"
}

6 · Over the threshold: the principal is asked

202 awaiting_approval

A $220 job against a $150 threshold parks the request. Homie texts the principal; Farm waits for an API call. Budget is reserved while it waits.

Response 202
{
  "atto_id": "homie_8b2c3d4e-1f2a-4b3c-9d4e-5f6a7b8c9d0e",
  "order_id": null,
  "status": "awaiting_approval",
  "authorize": {
    "amount": 220, "currency": "USD", "approval_threshold": 150,
    "decision": "pending_principal", "approval_requested_at": "2026-11-03T18:10:00Z"
  },
  "coordinate": {}, "execute": {}, "verify": {}, "record": {}
}
What the principal receives (Homie, SMS)
homie here — Property manager bot wants to book Toilet base is leaking at Home for $220. reply APPROVE K7M2P4 to go ahead or DECLINE K7M2P4 to skip it.
Or approve through the API (principal credential)
curl -X POST https://api.attodynamo.ai/v1/attos/homie_8b2c3d4e-1f2a-4b3c-9d4e-5f6a7b8c9d0e \
  -H "Authorization: Bearer $PRINCIPAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "action": "approve" }'

7 · The same call on another model

POST /v1/attos · Farm

Same shape, different world: a failed NVMe drive in a data center hall. The farm_ prefix on the mandate routes the request; nothing else changes.

Request
curl -X POST https://api.attodynamo.ai/v1/attos \
  -H "Authorization: Bearer $ATTO_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mandate_id": "farm_2f7b9c1d-3e4a-4b5c-8d6e-7f8a9b0c1d2e",
    "scope": {
      "service_id": "FM-HW-001",
      "location_id": "site-sjc1-hall-a",
      "window": "asap",
      "success_criteria": "rebuild complete, SMART healthy"
    }
  }'
Response 200, four minutes later · recorded
{
  "atto_id": "farm_9c2f4e6a-8b0d-4c1e-a2f3-4b5c6d7e8f90",
  "order_id": null,
  "status": "recorded",
  "scope": { "service": { "id": "FM-HW-001", "category": "Hardware", "subcategory": "Replace failed NVMe drive", "parts": ["NVMe 7.68TB U.2"] }, "location_id": "site-sjc1-hall-a", "site": { "name": "SJC1 · Hall A", "region": "us-west" } },
  "authorize": { "amount": 180, "currency": "USD", "decision": "within_mandate", "payment": { "mode": "invoice" } },
  "coordinate": {
    "dispatch": { "pool": "SJC1 · Hall A smart hands", "invited": 2 },
    "operators": [{ "role": "technician", "operator_ref": "tech-3f1a2b", "accepted_at": "2026-11-03T18:03:12Z" }],
    "dependencies": [{ "type": "part", "ref": "NVMe 7.68TB U.2", "status": "reserved" }],
    "window": { "from": "2026-11-03T18:04:12Z", "to": "2026-11-03T18:06:12Z" }
  },
  "execute": { "operator_ref": "tech-3f1a2b", "started_at": "2026-11-03T18:04:12Z", "completed_at": "2026-11-03T18:05:12Z", "ticket_ref": "FARM-7A91C0D2" },
  "verify": {
    "method": "two_sided_signoff",
    "technician_confirmed_at": "2026-11-03T18:05:12Z",
    "site_confirmed_at": "2026-11-03T18:06:12Z",
    "evidence": [{ "type": "ticket", "ref": "FARM-7A91C0D2", "on_file": true }, { "type": "telemetry", "on_file": true }],
    "telemetry": { "check": "post-change health", "result": "pass" }
  },
  "record": { "model": "farm", "amount": 180, "verified_at": "2026-11-03T18:06:12Z", "hash_alg": "sha256", "content_hash": "a7c4…" }
}

8 · When the mandate says no

403 over_max_per_job

Every refusal names the rule and the numbers, so the agent can decide what to do next instead of guessing.

Response 403
{
  "error": "over_max_per_job",
  "message": "This job costs more than the mandate allows per job.",
  "details": { "amount": 950, "max_per_job": 500 }
}

9 · Twelve lines of JavaScript

fetch

Send a task, wait for the record, print the hash. Works in Node 18+ and in the browser.

Code
const API = "https://api.attodynamo.ai/v1";
const headers = { Authorization: `Bearer ${process.env.ATTO_AGENT_KEY}`, "Content-Type": "application/json" };

let atto = await fetch(`${API}/attos`, { method: "POST", headers, body: JSON.stringify({
  mandate_id: "homie_d41d8cd9-8f00-4b20-a9e9-800998ecf842",
  scope: { service_id: "PL-KT-001", window: "asap", success_criteria: "no drip" },
}) }).then((r) => r.json());

while (!["recorded", "declined", "cancelled"].includes(atto.status)) {
  await new Promise((r) => setTimeout(r, 15_000));
  atto = await fetch(`${API}/attos/${atto.atto_id}`, { headers }).then((r) => r.json());
}
console.log(atto.status === "recorded" ? `atto ${atto.record.content_hash}` : `not an atto: ${atto.status}`);

Mandate rules

Checked on every POST /v1/attos, in this order. A refusal names the rule and the numbers.

  1. The mandate belongs to the calling agent, is active, and now is inside its validity window.
  2. scope.location_id is one of the mandate's places and the place is still active.
  3. The service exists in the model's catalog and is active.
  4. If the mandate lists categories, the service's category is one of them.
  5. The catalog price is at most max_per_job.
  6. The catalog price fits in what is left of max_per_month this calendar month, with waiting approvals already reserved.
  7. Above approval_threshold, the request parks as awaiting_approval and the principal is asked.

Payment: the mandate authorizes spend; the API moves no money. Each model keeps its own settlement, and payment never gates recorded.


Errors

HTTPerrorWhen
401unauthorized · invalid_api_key · invalid_tokenNo or bad credential.
403agent_revoked · principal_required · agent_key_requiredWrong kind of credential for the call.
403mandate_expired · location_not_in_mandate · category_not_allowed · over_max_per_job · over_monthly_budgetThe mandate said no. details carries the numbers.
404service_not_found · atto_not_found · mandate_not_foundNot in the catalog, or not visible to this caller.
409not_awaiting_approval · cancel_requires_human · already_recordedThe request is past that point.
503model_unavailableThe model exists but is not configured on this gateway.

Get access

Private beta

Keys are issued by hand while Model 01 runs in the Bay Area. Tell us who you are and what you want to automate, and we will set you up with a principal account and a mandate.