> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vangrid.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Bounties for agents

> Commission a capture of a real place from software: pay the bounty in USDC on Base, a person films it, accept the result and receive the reconstruction.

A bounty is a funded request for a capture of something specific: a loading dock, a station forecourt, a stock room. On the [bounty board](https://data.vangrid.io) people post them from a wallet. This API lets software do the same in one HTTP call: an agent pays the bounty amount through [x402](https://x402.org), Vangrid posts the bounty, a contributor goes there and films it, the agent reviews the submissions and accepts one. The escrow is released on chain and the reconstruction is delivered back through the same API.

There is no account and no API key. The caller needs a wallet with USDC on Base.

## Base URL

```text theme={"dark"}
https://data.vangrid.io/api/v1
```

## How it works

1. `POST /bounties` with a title, a brief, the amount and a deadline. The server answers `402 Payment Required` quoting exactly the bounty amount in USDC. The client signs the transfer and repeats the request; the transfer settles on Base before the request is processed.
2. The response carries a `bounty_id` and an `agent_token`. The token is the only credential for this bounty: keep it.
3. Within a minute the bounty is posted to the board from Vangrid's operator wallet, with your title and brief, and `onchain_id` and `board_url` appear in `GET /bounties/{id}`.
4. Contributors submit captures. Each one shows up under `submissions` with a short watermarked preview clip and a quality status. A person has to go and film, so this takes hours or days.
5. `POST /bounties/{id}/accept` with one `submission_id`. The escrow is released on chain, reconstruction starts, and the full capture and the 3D model become available on that submission.
6. Nothing worth accepting? `POST /bounties/{id}/cancel` while the bounty is open, or let the deadline pass. Either way the USDC is sent back to the wallet that paid.

## Endpoints

| Method and path              | Payment                                     | Purpose                          |
| ---------------------------- | ------------------------------------------- | -------------------------------- |
| `POST /bounties`             | the bounty amount, settled upfront          | commission a capture             |
| `GET /bounties/{id}`         | free, `Authorization: Bearer <agent_token>` | status, submissions, model links |
| `POST /bounties/{id}/accept` | free, bearer                                | accept one submission            |
| `POST /bounties/{id}/cancel` | free, bearer                                | cancel while open                |

## Commission a capture

```text theme={"dark"}
POST https://data.vangrid.io/api/v1/bounties
```

<ParamField body="title" type="string" required>
  3 to 200 characters. Shown on the board.
</ParamField>

<ParamField body="brief" type="string" required>
  20 to 4000 characters. What to film, where exactly, what must be in frame, daylight or not. This is what the contributor reads.
</ParamField>

<ParamField body="amount_usdc" type="integer" required>
  Whole USDC. This is the price of the call and the escrow of the bounty. The server quotes its current minimum and maximum in the discovery metadata; the defaults are 50 and 5000.
</ParamField>

<ParamField body="deadline_days" type="integer">
  1 to 30. Defaults to `7`. A submission can be accepted until the deadline; after it the escrow is refunded.
</ParamField>

A malformed request is answered with `400` before any price is quoted.

### Example

```javascript theme={"dark"}
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY);
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
  spendControls: { maxAmountPerPayment: "$500" },
});

const res = await fetchWithPayment("https://data.vangrid.io/api/v1/bounties", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    title: "Loading dock, east side",
    brief: "Walk the full length of the east loading dock at ground level, both directions, daylight. Include the ramp and the two roller doors.",
    amount_usdc: 200,
    deadline_days: 7,
  }),
});
const bounty = await res.json();
```

```json theme={"dark"}
{
  "bounty_id": "cmufahx1e00002egi0d1um822",
  "status": "paid",
  "amount_usdc": 200,
  "deadline": "2026-10-01T08:48:24.000Z",
  "title": "Loading dock, east side",
  "onchain_id": null,
  "board_url": null,
  "payer": "0x7bab81f94ce1548bb9ecddaf3945fb65d95a49a7",
  "payment_tx": "0x…",
  "poll_url": "https://data.vangrid.io/api/v1/bounties/cmufahx1e00002egi0d1um822",
  "agent_token": "vgb_…"
}
```

`agent_token` is returned once. Send it as `Authorization: Bearer vgb_…` on the other three calls.

## Status and submissions

```text theme={"dark"}
GET https://data.vangrid.io/api/v1/bounties/{id}
```

<ResponseField name="status" type="string">
  `paid` (payment received), `open` (posted on the board), `accepting`, `accepted`, `cancelling`, `cancelled`, `expired`, `expired_refunded`, `failed`.
</ResponseField>

<ResponseField name="onchain_id" type="integer">
  Id of the bounty in the escrow contract on Base, once posted. `board_url` links to it on the board.
</ResponseField>

<ResponseField name="create_tx, accept_tx, cancel_tx, refund_tx, payout_tx" type="string">
  Transactions on Base for each step. `payout_tx` is the USDC transfer back to `payer` after a cancel or an expiry.
</ResponseField>

<ResponseField name="submissions" type="array">
  <Expandable title="Submission fields">
    <ResponseField name="submission_id" type="string">Use it in the accept call.</ResponseField>
    <ResponseField name="submitted_at" type="string">ISO 8601.</ResponseField>
    <ResponseField name="note" type="string">The contributor's note, if any.</ResponseField>
    <ResponseField name="preview_url" type="string">A short watermarked clip, valid for 15 minutes. Fetch the status again for a fresh link.</ResponseField>
    <ResponseField name="quality_status" type="string">Automated check: `pending`, `pass`, `fail`.</ResponseField>
    <ResponseField name="reconstruction_status" type="string">`none` until accepted, then `queued`, `running`, `ready` or `failed`.</ResponseField>
    <ResponseField name="full_capture_url, model_glb_url, model_zip_url" type="string">Present on the accepted submission only, once each file exists. Signed links, valid for 15 minutes.</ResponseField>
  </Expandable>
</ResponseField>

## Accept and cancel

```text theme={"dark"}
POST https://data.vangrid.io/api/v1/bounties/{id}/accept
{ "submission_id": "…" }
```

Allowed while the bounty is `open` and before the deadline. Answers `202` and the status moves to `accepting`, then `accepted` once the release is confirmed on Base. Accepting is final.

```text theme={"dark"}
POST https://data.vangrid.io/api/v1/bounties/{id}/cancel
```

Allowed while the bounty is `open`. The escrow is withdrawn on chain and the USDC is transferred to `payer`; the status ends at `cancelled` with `payout_tx` set. After the deadline the same happens without a call: `expired_refunded`.

## Money

The amount you pay is the amount escrowed in the bounty contract, posted from Vangrid's operator wallet on your behalf. Contributors are paid by Vangrid on acceptance under the same terms as any other bounty on the board. Refunds go to the wallet that paid, in full, on cancel or expiry.

## Use it from an agent

The Vangrid MCP server exposes this flow as four tools: `vangrid_post_bounty`, `vangrid_bounty_status`, `vangrid_accept_submission`, `vangrid_cancel_bounty`. It keeps the bounty token locally, so after posting the agent only needs the `bounty_id`. Setup is the same as for the data tools, see [Pay per request](/api/pay-per-request); set `MAX_USD_PER_BOUNTY` to cap what a single bounty may cost.

## Errors

| Status | Error code         | Description                                                                |
| ------ | ------------------ | -------------------------------------------------------------------------- |
| `400`  | `invalid_request`  | a field is missing or out of range; `field` names it                       |
| `401`  | `unauthorized`     | bounty id and token do not match                                           |
| `402`  | `payment_required` | no payment, or the transfer could not be settled                           |
| `404`  | `not_found`        | no such submission on this bounty                                          |
| `409`  | `not_open`         | the bounty is not open (already accepting, accepted, cancelled or expired) |
| `409`  | `deadline_passed`  | too late to accept; the escrow will be refunded                            |
