API

Give your AI a body.

One bearer token, one POST, and a human goes and looks. What comes back is structured evidence your agent can act on — not a photograph somebody has to interpret.

Base URLhttps://tagteam-api.successagenticlabs.com/v1

Quickstart

Register, throw one Tag, read the result. Three calls, and the third one is the product. Everything else in this reference is detail you can reach for later.

POST/v1/corners/registerPublic

Create a Corner and receive an API key.

Request
{
  "name": "Atlas Property Agent",
  "email": "dev@example.com",
  "webhook_url": "https://your-agent.example/hooks"
}
Response
{
  "id": "cor_...",
  "api_key": "irltag_live_...",
  "webhook_secret": "whsec_..."
}

The key and the webhook secret are returned exactly once. We store a hash, so we cannot re-send, recover or read them back to you on a support call.

POST/v1/tags/recipeAPI keyIdempotent

Throw a Tag from a Recipe — the shortest correct request.

Request
{
  "recipe": "PROPERTY_OCCUPANCY_CHECK",
  "location": { "lat": 33.749, "lng": -84.388 }
}

Expansion happens on the server and produces exactly the Tag POST /v1/tags would have produced — same funding path, same guardrails, same verification, same payout. This endpoint saves typing, not rules.

GET/v1/tags/:id/resultAPI key

The structured answer your agent consumes.

Response
{
  "outcome": "verified",
  "checklist": [{ "item": "storefront", "present": true }],
  "verification": { "place": true, "time": true, "content": true },
  "evidence": [{ "sha256": "…", "url": "https://…signed…" }]
}

A null content verdict is reported as null. It is never rendered as a pass.

Pass ?max_age_minutes=N to have the response judge its own freshness. Age is measured from capture — when the world was observed — not from upload or verification, and is computed on every read rather than stored.

Evidence older than max_age_minutes is returned anyway, flagged rather than withheld. A six-hour-old observation may be fine for one decision and useless for another, and that is your call.

Agent identity and authority

The question an enterprise asks about an autonomous agent is not "can it do this?" but "is it allowed to?". An API key here is an identity: it has an owner, its own ceilings, the permissions it holds, what it has spent, and a record. Every limit is enforced server-side inside the transaction that charges the card — an agent can lower its own ceilings and can never raise them.

GET/v1/agents/meAPI key

Who this key is, what it may do, and what it has spent.

Response
{
  "agent": {
    "agent_id": "retail-intel-9f3a2b",
    "owner": { "name": "Acme Intelligence" },
    "authority": {
      "max_tag_cents": 10000,
      "daily_cents": 100000,
      "approval_over_cents": 25000,
      "scopes": ["quote", "throw_tag"]
    },
    "record": {
      "missions": 0,
      "trust_score": null,
      "basis": "insufficient_data"
    },
    "spend_today_cents": 0
  }
}

trust_score is null until at least 20 missions have completed. Null means "not enough history to say", never "untrustworthy" — a score invented from three missions is a number a buyer would act on and it would mean nothing.

A key created before agent identity existed returns agent: null and says so. It is not treated as an agent with no limits.

POST/v1/missions/evaluateAPI key

Would this mission be allowed? Reserves nothing, charges nothing.

Request
{ "amount_cents": 9000, "recipe": "RETAIL_SHELF_CHECK" }
Response
{
  "decision": "requires_approval",
  "reason": "above_approval_threshold",
  "checks": [
    { "name": "agent_active", "status": "pass", "detail": "…" },
    { "name": "per_mission_ceiling", "status": "pass", "detail": "…" },
    { "name": "human_approval", "status": "fail", "detail": "…" }
  ],
  "message": "This mission costs $90.00, above the $50.00 threshold…"
}

This runs the identical function that gates the money, in dry-run mode. One implementation on purpose: a preview that could disagree with the real decision would be worse than no preview, because an agent would plan around it.

Three outcomes: approved, blocked, requires_approval. Blocking checks all run before the approval question — asking a person to approve something that would be refused anyway is how an approval queue becomes a rubber stamp.

PUT/v1/agents/{agent_id}/authorityAPI key

Narrow what an agent may do.

Request
{ "max_tag_cents": 5000, "scopes": ["quote", "throw_tag"] }

An API key can only ever tighten: lower a ceiling, drop a scope, add a Recipe restriction. Every widening move is refused with 403.

Removing a ceiling counts as raising it, because null means no limit. Raising an approval threshold counts as widening too — a higher threshold means more happens without a person.

Loosening is done by a person signed in to the dashboard. A key that could raise its own limit would not be a limit.

POST/v1/approvals/{id}/decideCorner sign-in

A person approves or rejects a held mission.

Request
{ "decision": "approve", "note": "Confirmed with the client." }

A mission above its agent’s approval threshold is not created and not charged. It is held, and the agent receives 202 with an approval id rather than an error.

Approval throws the Tag through the ordinary path, re-running every check at that moment. The world moves while a request waits, and approving a week-old mission without re-checking budgets, kill switches and cards would make this queue a hole in every other control.

An API key cannot approve a mission — including its own. That is the entire purpose of the threshold.

Plan before you spend

Three endpoints that create nothing and charge nothing. An agent that cannot estimate has to guess or ask a human, and both are worse than asking us — so these answer "what would it cost, how long would it take, and can you even do it here?" before anything is committed.

POST/v1/capabilities/searchAPI key

Describe the work in your own words; find out which Recipe does it.

Request
{
  "intent": "check whether this restaurant is still open",
  "location": { "lat": 33.749, "lng": -84.388 }
}
Response
{
  "matches": [
    {
      "slug": "BUSINESS_OPEN_CHECK",
      "confidence": 1,
      "matched_on": ["restaurant", "still", "open"],
      "default_bounty_cents": 3500,
      "checklist": ["The storefront, showing the entrance", "…"]
    }
  ],
  "ambiguous": false,
  "unrecognised_terms": [],
  "honest_answer": "BUSINESS_OPEN_CHECK is the closest fit."
}

This is lexical matching against a hand-written vocabulary, not a language model. It is built to be honest about that rather than to disguise it.

Nothing fits? You get an empty matches array and a plain explanation — never the closest guess. A wrong Recipe spends real money observing something you did not ask about.

Two Recipes fitting equally well returns both with ambiguous: true. "Check the signage" genuinely could be two operations; you know which you meant and we do not.

unrecognised_terms lists the words that meant nothing to us, so you can see what was actually weighed rather than assuming it was understood.

POST/v1/quotesAPI key

What a Tag would cost and how long it would plausibly take.

Request
{
  "recipe": "PROPERTY_OCCUPANCY_CHECK",
  "location": { "lat": 33.749, "lng": -84.388 },
  "urgency": "standard"
}
Response
{
  "price": {
    "bounty_cents": 7500,
    "platform_fee_cents": 1500,
    "total_charge_cents": 9000,
    "partner_payout_cents": 7500,
    "exact": true
  },
  "estimated_completion": {
    "median_minutes": null,
    "basis": "no_history",
    "observations": 0,
    "note": "No Tag has been completed near here yet…"
  },
  "coverage": { "servable": false, "partners_nearby": 0 }
}

The price is exact, not an estimate: it comes from the same function that charges the card, so a quote cannot disagree with the invoice.

The timing is history or it is null. Where too few Tags have completed nearby, median_minutes comes back null with basis "no_history" rather than a plausible number — an agent planning around an invented estimate is worse off than one told we do not know.

A quote reserves nothing. It does not hold a price, a Partner or capacity, and nothing is charged until a Tag is thrown.

GET/openapi.jsonPublic

The machine-readable OpenAPI 3.1 description of this API.

Generated from the running route table, so it cannot describe an endpoint that does not exist — and a build check fails if a public endpoint ships without a description, or if a description outlives the endpoint it documents.

Covers the agent-facing API. Administrator, Partner and dashboard routes are deliberately outside it, each excluded for a stated reason rather than by being forgotten.

Point any OpenAPI client generator at it.

GET/v1/capabilitiesAPI key

Every Recipe, its proof requirements, and whether this place can be served.

Pass lat and lng to ask about a place; omit both to describe the platform.

An uncovered area returns servable: false and still lists every Recipe. The work is supported; the location is not — and conflating those would send you to a different provider when you needed a different city.

Each Recipe carries its version, checklist and prohibited actions, so an agent can read what a Partner will and will not do before choosing one.

Authentication

One bearer token on every request. Keys are stored as a peppered SHA-256 hash — a database dump does not yield working credentials.

GET/v1/corners/meAPI key

Your plan, quota and spending limits.

PATCH/v1/corners/me/limitsAPI key

Lower your own spending limits.

Limits can be lowered through the API; raising them needs a human. That asymmetry is deliberate — if this key leaks, the attacker must not be able to raise its own ceiling.

GET/v1/limitsAPI key

Per-Recipe spending ceilings.

PUT/v1/limits/recipesAPI key

Set a ceiling for one Recipe.

Throwing Tags

A Tag is one unit of physical work at one place, funded before anyone is asked to go there. If the charge fails you get a 402 and no open Tag is created — a Partner must never do real work for money that is not there.

POST/v1/tagsAPI keyIdempotent

Throw a Tag, stating the work yourself.

Request
{
  "title": "Photograph storefront and street number",
  "description": "Photograph the storefront, the visible street number, and the entrance.",
  "category": "verification",
  "bounty_cents": 7500,
  "geo": { "lat": 33.749, "lng": -84.388, "radius_m": 150 },
  "required_proof": { "photos": 3, "checklist": ["storefront"], "notes_required": true },
  "expires_at": "2026-09-13T18:00:00Z",
  "urgency": "standard"
}
GET/v1/tagsAPI key

List your Tags.

GET/v1/tags/:idAPI key

Where one Tag is now.

POST/v1/tags/:id/decisionAPI keyIdempotent

Approve or reject submitted proof.

Approving a Tag whose PLACE or TIME lock failed returns 422. Those are facts about the world, not opinions about quality, and only an administrator can override them — which creates a permanent audit record.

POST/v1/tags/:id/cancelAPI keyIdempotent

Cancel before anyone claims it.

POST/v1/tags/:id/refundAPI keyIdempotent

Refund an expired or unclaimed Tag.

Recipes

Twelve standard jobs whose requirements are stated in advance and can therefore be verified against. Recipes are versioned, never overwritten: a Tag records the slug and the version, so one thrown three weeks ago is still judged against the requirements it was actually given.

GET/v1/recipesAPI key

Every published Recipe.

GET/v1/recipes/:slugAPI key

One Recipe and its requirements.

POST/v1/tags/recipeAPI keyIdempotent

Throw a Tag from a Recipe.

Proof and verification

Proof is captured inside a server-issued capture session. The server records the location fixes itself and stamps the time from its own clock — a client that says it was somewhere is not evidence that it was.

GET/v1/tags/:id/evidence-packetAPI key

The durable record, including what it does not prove.

GET/v1/tags/:id/ledgerAPI key

Every cent for one Tag, with Stripe object ids.

Campaigns and schedules

A campaign of 200 locations is 200 ordinary Tags. Combining them would be easier and would destroy the only thing worth paying for: you could no longer say which of the 200 places was visited, by whom, or whether that specific one was paid for.

POST/v1/campaignsAPI keyIdempotent

Create a campaign across many locations.

GET/v1/campaignsAPI key

List campaigns.

GET/v1/campaigns/:idAPI key

Campaign progress and spend.

GET/v1/campaigns/:id/locationsAPI key

Per-location status.

POST/v1/campaigns/:id/cancelAPI keyIdempotent

Stop a campaign.

POST/v1/schedulesAPI keyIdempotent

Recurring Tags with a hard budget.

A schedule stores a rule and a single next run. Creating a year of Mondays up front would mean charging a year of money up front, and would let one malfunctioning call commit you to 52 weeks of spending.

GET/v1/schedulesAPI key

List schedules.

GET/v1/schedules/:idAPI key

One schedule and its runs.

POST/v1/schedules/:id/pauseAPI key

Pause a schedule.

POST/v1/schedules/:id/resumeAPI key

Resume a schedule.

Supply and coverage

Ask before you spend. Both of these answer honestly about places we cannot serve — a confident yes that produces an expired Tag costs more trust than a plain "not yet".

GET/v1/coverageAPI key

Whether we can actually serve a place.

Disputes

Opening a dispute stops both the payout and the refund until a human resolves it.

POST/v1/disputesAPI keyIdempotent

Freeze a Tag pending human review.

GET/v1/disputes/:idAPI key

Where a dispute stands.

Operational status

Call this before a long autonomous run. Knowing the money engine is paused is more useful than discovering it on your next POST /v1/tags.

GET/v1/statusPublic

Which capabilities are currently accepting work.

Response
{ "accepting_tags": true, "accepting_claims": true, "paying_out": true, "auto_approval": true }
GET/healthzPublic

Liveness.

GET/readyzPublic

Readiness, including database and payment configuration.

Idempotency

Required on every endpoint that moves money. Retrying is safe; not having a key is not.

Header
Idempotency-Key: 7f2c0e6a-2b41-4f0e-9a3d-1c2f4e6a8b0d
  • Same key, same body → the original response, with Idempotent-Replay: true.
  • Same key, different body → 409 IDEMPOTENCY_CONFLICT.
  • Key still in flight → 409 IDEMPOTENCY_IN_PROGRESS. Retry in a few seconds.

Tag lifecycle

This table is generated from the same constant the server enforces, so it cannot drift from reality. A transition that is not listed returns 409 INVALID_TRANSITION — and the database refuses it too, with a trigger, so a bug in a future handler cannot move money sideways.

FromMay become
draftfundingcancelled
fundingopencancelled
openclaimedexpiredcancelledrefund_pending
claimedsubmittedexpireddisputed
submittedin_reviewdisputed
in_reviewverifiedopendisputed
verifiedpaidrefund_pending
paidfinalNothing. The Tag is finished and no further money may move.
cancelledfinalNothing. The Tag is finished and no further money may move.
expiredrefund_pending
disputedverifiedrefund_pending
refund_pendingrefunded
refundedfinalNothing. The Tag is finished and no further money may move.

Two states exist that a simplified diagram usually omits, and both matter for money: funding is where the card is charged before a Tag is ever visible, and refund_pending is how every funded Tag reaches exactly one financial resolution — paid or refunded, never neither.

Webhooks

Your agent does not poll. Every delivery is signed with the secret shown once at registration; verify it before trusting the body.

EventWhat it means
tag.claimedA Partner accepted it. The clock is running.
tag.submittedProof is in and being verified.
tag.verifiedAll three locks held.
tag.paidThe Partner was paid. The result is final.
tag.rejectedVerification failed. Nobody was paid.
tag.expiredNobody claimed it in the window. Your money is being returned.
tag.refundedThe refund settled.
tag.disputedMoney is frozen until a human rules.

Deliveries retry with backoff on the Contender and Champion plans. A delivery that never succeeds does not change what happened — the Tag's state and its ledger are the record, and both are readable over the API.

Errors

Every error has the same shape and a stable machine-readable code. Agents branch on the code; humans read the message.

Shape
{ "error": { "code": "SPENDING_LIMIT", "message": "…", "details": { } } }
CodeHTTPWhat to do
VALIDATION_FAILED400Fix the request. Do not retry it unchanged.
UNAUTHORIZED401Check the API key.
PAYMENT_REQUIRED402The card failed. A human must fix billing.
QUOTA_EXCEEDED402Plan quota exhausted for the month.
FORBIDDEN403Not yours, or not permitted.
SPENDING_LIMIT403A ceiling was hit. Limits are raised by a human, not by the API. Stop.
NOT_FOUND404Wrong id, or not yours.
INVALID_TRANSITION409The Tag is not in a state where that makes sense.
ALREADY_CLAIMED409Another Partner got there first.
IDEMPOTENCY_CONFLICT409Same key, different body. Use a new key.
IDEMPOTENCY_IN_PROGRESS409Still running. Retry in a few seconds.
DISPUTE_FREEZE409Money is frozen until a human resolves the dispute.
VERIFICATION_FAILED422The Locks did not pass. Read details.failures.
UNSAFE_TASK422The request describes work we will not send a person to do.
RATE_LIMITED429Back off.
KILL_SWITCH503Operations paused this capability. Check GET /v1/status. Do not loop.

Rate limits and spending controls

Two different things, often confused. A rate limit protects the service. A spending limit protects you.

  • Rate limiting is per API key, per minute. Over it you get 429 RATE_LIMITED — back off and retry.
  • Spending ceilings exist per Tag, per day, per month, per hour, per campaign, per schedule and per Recipe. Over one you get 403 SPENDING_LIMIT and no charge is made.
  • Every ceiling is enforced inside the transaction that charges the card. The SDK and the MCP server are interfaces, not authorities — neither can raise a limit, and neither touches the database.

A 403 SPENDING_LIMIT is not a retryable error. It means a human decision is required, and an agent that loops on it will simply keep being refused.

Next

Wire it into an agent framework with MCP · See what it costs · Get an API key