---
name: diemsum
description: >-
  Buy Venice LLM inference programmatically through DiemSum, a pay-per-use x402
  marketplace. Use this skill when an agent needs to call a chat/completion model
  and pay for it with an x402 + ERC-7710 payment instead of an API key. Covers
  discovery, quote-first pricing, ERC-7710 payment signing, retrieval, and
  multi-turn conversations.
homepage: https://diemsum.com
---

# DiemSum — agent skill

DiemSum resells unused Venice inference credits over **x402** with **ERC-7710**
delegations. There is **no buyer API key**: access is gated by payment. You
discover an offer, ask for a quote, sign an x402 payment that satisfies the
quote, and get the model output back.

Point your agent at this file (`/skill.md`) and follow the contract below. The
machine-readable spec is `/openapi.yaml` (OpenAPI 3.1); `/docs` is the HTML
guide. Everything here is exactly what the deployed API does.

## TL;DR flow

```
GET  /api/v1/offers                      → list buyer-callable model offers
POST /api/v1/inference  (no payment)     → 402 + quote + PAYMENT-REQUIRED header
POST /api/v1/inference  (PAYMENT-SIGNATURE) → 200 + completion + receipt
```

`POST /api/v1/inference` is called **twice** for one paid completion: once to get
a quote (returns `402`), then again with the **identical body** plus a
`PAYMENT-SIGNATURE` header carrying a base64 x402 payload.

## Base URL

- Production: `https://diemsum.com`
- Local dev: `http://localhost:3000` (run `X402_VERIFY_MODE=stub npm run dev` to
  stub facilitator verify/settle so the full path completes with no real money).

## Step 0 — Bootstrap a spending mandate (headless agents)

A headless buying agent has no human at the keyboard to click MetaMask per
purchase. Instead it asks its human owner **once** for a scoped, recurring
ERC-7715 permission (an `erc20-token-periodic` USDC budget, e.g. 100 USD/day)
whose delegate is the **agent's own address**. After that the agent redelegates
from the stored permission for every quote and buys autonomously within the
budget — a 100-USD/day root can fund a 1-USD purchase 100 times a day with no
new human approval.

**When to bootstrap.** Run this flow when any of these is true:

- no valid saved permission for this base URL exists yet;
- the saved permission has expired (`permissionExpiresAt` is in the past);
- the per-period budget is too small for the quote you need;
- the permission was revoked, or the facilitator redeemer set changed, so a
  payment attempt is rejected / re-challenged with `402` at pay time.

Otherwise, skip to Step 4 and reuse the saved permission.

Runnable reference: `docs/examples/agent-permission-bootstrap.mjs`.

### 0a — Request the mandate

```http
POST /api/v1/agent-permissions/request
Content-Type: application/json

{
  "agentAddress": "0xYourAgentDelegateAddress",
  "periodAmountUsd": 100,
  "periodDurationSeconds": 86400,
  "expirySeconds": 2592000,
  "agentLabel": "My research agent",
  "justification": "Autonomous DiemSum inference, up to 100 USDC/day."
}
```

Keys accept snake_case or camelCase. Only `agentAddress` and `periodAmountUsd`
are required. `periodAmountUsd` is min `0.01`, max `1000` (deployment-capped by
`DIEMSUM_AGENT_PERMISSION_MAX_PERIOD_USD`). `periodDurationSeconds` defaults to
`86400` (min `3600`, max `7776000`). `expirySeconds` — the permission lifetime —
defaults to `2592000` (min `3600`, max `31536000`). `justification` (≤280 chars)
and `agentLabel` (≤80 chars) are shown to the human.

Success is `201`:

```json
{
  "request": {
    "requestId": "aperm_...",
    "status": "pending",
    "agentAddress": "0xyouragentdelegateaddress",
    "chainId": 8453,
    "tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
    "asset": "USDC",
    "periodAmountUsd": 100,
    "periodAmountBaseUnits": "100000000",
    "periodDurationSeconds": 86400,
    "expirySeconds": 2592000,
    "justification": "Autonomous DiemSum inference, up to 100 USDC/day.",
    "agentLabel": "My research agent",
    "requestExpiresAt": "2026-07-07T01:00:00.000Z",
    "createdAt": "2026-07-07T00:00:00.000Z",
    "updatedAt": "2026-07-07T00:00:00.000Z"
  },
  "approvalUrl": "https://diemsum.com/permit/aperm_...",
  "poll": { "url": "https://diemsum.com/api/v1/agent-permissions/aperm_...", "intervalSeconds": 5 }
}
```

Errors: `400 {code:"invalid_agent_permission_request"}`,
`429 {code:"rate_limited"}`, `503 {code:"agent_permission_store_failed"}`.

### 0b — Hand the approval URL to your human owner

Print `approvalUrl` and give it to your human. They open `/permit/{requestId}`,
connect MetaMask, and grant the permission. **The MetaMask prompt shows the
delegate (to) address — it must equal your `agentAddress`.** That verification is
the security boundary: the request/poll URLs are unauthenticated capability URLs
keyed by the high-entropy `requestId`, and the granted context is useless without
the agent private key that owns the delegate address. DiemSum rejects stored
grants whose chain, token, budget, period, or DelegationManager differ from the
request. Tell your human to open the link in a **desktop browser with the
MetaMask extension** — MetaMask Mobile cannot grant ERC-7715 permissions yet, so
from a phone the only available action is decline.

### 0c — Poll until granted

Poll `GET /api/v1/agent-permissions/{requestId}` **on the base URL you called**
every `poll.intervalSeconds` (5s). Treat the echoed absolute `poll.url` as
informational — a correctly configured deployment echoes the canonical origin,
but you already know where you reached the API. Persist the `requestId` before
polling and back off on `429` (honor `Retry-After`) so a restart or a
rate-limited window never orphans a mandate your human already signed.

```http
GET /api/v1/agent-permissions/{requestId}   → { request, grant }
```

`grant` is `null` until `request.status === "granted"`. Stop when status becomes
`granted` (save it), `declined`, or `expired` (a pending request past
`requestExpiresAt` is reported `expired`). When granted, `grant` carries:

```json
{
  "permissionContext": "0x...",
  "delegationManager": "0x...",
  "chainId": 8453,
  "permissionType": "erc20-token-periodic",
  "permissionData": { "tokenAddress": "0x8335...2913", "periodAmount": "100000000", "periodDuration": 86400, "startTime": 1783382580, "justification": "…" },
  "granterAddress": "0xhumanpayeraddress",
  "agentAddress": "0xyouragentdelegateaddress",
  "grantedAt": "2026-07-07T00:03:00.000Z",
  "permissionExpiresAt": "2026-08-06T00:03:00.000Z"
}
```

`permissionData` is the wallet-echoed grant payload — its exact keys are
wallet-dependent (`startTime` is the real grant-time unix second, and a
`justification` string is usually present). Treat it as informational; the
fields you rely on are the top-level ones above.

### 0d — Save the artifact locally

Persist the grant to `~/.diemsum/permissions.json` with mode `600` so only the
agent can read it. Recommended per-permission artifact shape:

```json
{
  "requestId": "aperm_...",
  "permissionContext": "0x...",
  "delegationManager": "0x...",
  "chainId": 8453,
  "permissionType": "erc20-token-periodic",
  "permissionData": { "tokenAddress": "0x8335...2913", "periodAmount": "100000000", "periodDuration": 86400, "startTime": 1783382580, "justification": "…" },
  "granterAddress": "0xhumanpayeraddress",
  "agentAddress": "0xyouragentdelegateaddress",
  "tokenAddress": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  "periodAmountBaseUnits": "100000000",
  "periodDurationSeconds": 86400,
  "grantedAt": "2026-07-07T00:03:00.000Z",
  "permissionExpiresAt": "2026-08-06T00:03:00.000Z",
  "baseUrl": "https://diemsum.com"
}
```

The example stores these in a `{ "version": 1, "permissions": [ … ] }` array and
looks one up by `baseUrl` + `agentAddress`, skipping the bootstrap when a
non-expired match exists.

### 0e — Spend loop (per purchase)

For each purchase, **quote first** (Step 2), then sign a **child redelegation**
from the stored `permissionContext`:

- delegate the child to the quoted `accepts[0].extra.facilitatorAddresses` (the
  facilitator redeemers), with an **exact-amount** ERC-20 transfer scope whose
  amount equals `accepts[0].amount` (atomic USDC) for the quoted token;
- set both the delegator and the payment `buyerAddress` to `granterAddress` (the
  human payer whose funds the budget spends);
- encode `[child, ...root]` from the stored permission as the payment context and
  send it as the `PAYMENT-SIGNATURE` header (Step 4).

`docs/examples/agent-permission-bootstrap.mjs` does exactly this with
`createx402DelegationProvider` + `x402Erc7710Client`, the same mechanism as
`src/lib/erc7715-payment.ts` `buildRedelegatedPaymentRequest`.

### Safety contract

- **Never** request broad wallet authority, private keys, seed phrases, or
  unrestricted token approvals. Request **only** the narrow periodic USDC budget
  the agent actually needs.
- Treat the agent key as a real spending key for that root budget: the root
  `erc20-token-periodic` grant is scoped by token, amount, period, and expiry;
  the DiemSum quote path adds the per-purchase payee/redeemer scope through the
  child redelegation.
- Only spend against DiemSum x402 quotes, and only within the granted scope
  (token, per-period amount, period, expiry).
- If a payment is rejected or re-challenged, re-run the bootstrap **only** when
  one of the "When to bootstrap" conditions holds — do not escalate scope to
  work around a mismatch.

## Step 1 — Discover offers

```http
GET /api/v1/offers
```

Returns only operator-approved, seller-active, launch-gated offers:

```json
{
  "offers": [
    {
      "offerId": "offer_...",
      "sellerAddress": "0x...",
      "sellerDisplayName": "Acme Capacity",
      "model": "venice-llama-3_3-70b",
      "pricingMode": "quick-sell",
      "billingMode": "exact-usage",
      "launchClassification": "exact-usage",
      "inputUsdPer1M": 0.81,
      "outputUsdPer1M": 0.81,
      "maxOutputTokens": 1024,
      "asset": "USDC",
      "chainId": 8453,
      "routeStatus": "live",
      "updatedAt": "2026-06-08T00:00:00.000Z"
    }
  ]
}
```

Pick an `offerId`. `GET /api/v1/market` is a richer variant that also returns the
Venice reference price and a `savingsPct` per offer.

## Step 2 — Request a quote (expect 402)

Send the request you actually want. **Exactly one** of `prompt` or `messages[]`
is required. Body accepts snake_case or camelCase keys.

```http
POST /api/v1/inference
Content-Type: application/json

{
  "offerId": "offer_...",
  "messages": [{ "role": "user", "content": "Explain x402 in one sentence." }],
  "maxOutputTokens": 512,
  "buyerAddress": "0xYourBuyerOrPayerAddress"
}
```

Response is `402 Payment Required`:

```json
{
  "error": "Payment required",
  "code": "payment_required",
  "quote": { "quoteId": "quote_...", "requestHash": "sha256:...", "quotedMaxUsd": 0.0123, "expiresAt": "..." },
  "paymentRequirements": { "x402Version": 2, "accepts": [ { "scheme": "exact", "network": "eip155:8453", "amount": "12345", "asset": "0x8335...2913", "payTo": "0xSeller...", "maxTimeoutSeconds": 300, "extra": { "assetTransferMethod": "erc7710", "facilitatorAddresses": ["0x..."], "facilitatorEndpoint": "https://...", "chainId": 8453, "quoteId": "quote_...", "requestHash": "sha256:...", "redeemerCaveatMode": "required" } } ] }
}
```

`quotedMaxUsd` is the amount that settles on-chain when you pay (the x402
`exact` scheme transfers the quoted amount verbatim). Tune `max_output_tokens`
down to lower the quote; the receipt reports the turn's usage-based value as
`usage.usageValueUsd` for your own accounting.

Useful response details:

- The same `paymentRequirements` envelope is base64-encoded in the
  **`PAYMENT-REQUIRED`** response header.
- Headers `X-DiemSum-Quote-Id` and `X-DiemSum-Request-Hash` echo the quote.
- Quotes have a **5-minute TTL**.
- `503 {code:"facilitator_config_missing"}` means the deployment is not
  payment-capable. `429 {code:"rate_limited"}` carries `Retry-After`.

## Step 3 — Build the ERC-7710 PAYMENT-SIGNATURE

Build an x402 payload that satisfies `accepts[0]` **exactly** (same amount,
asset, `payTo`, network). The direct-agent path uses an ERC-7710 delegation from
your payer to the quoted facilitator redeemer addresses, then encodes:

```json
{ "x402Version": 2, "accepted": <the accepts[0] you are paying>, "payload": { "...erc7710 delegation + authorization..." } }
```

Base64-encode that JSON → that string is the `PAYMENT-SIGNATURE` header value.

Three supported buyer identities:

1. **Direct agent (ERC-7710 / EIP-7702)** — your agent owns an EOA, upgrades or
   validates it for 7702, and signs a direct delegation to the facilitator
   redeemers. Runnable reference implementations live in the repo:
   <https://github.com/osobot-ai/venice-x402-marketplace/blob/main/docs/examples/direct-x402-erc7710.ts>
   and
   <https://github.com/osobot-ai/venice-x402-marketplace/blob/main/docs/examples/agent-quickstart.mjs>.
2. **Browser human (ERC-7715)** — a MetaMask user grants an Advanced Permission;
   the dapp redelegates it per-quote. That is the `/chat` UI path, not the
   headless agent path.
3. **Agent holding a delegated ERC-7715 permission from its human** — the agent
   bootstrapped a scoped recurring `erc20-token-periodic` budget once (Step 0),
   whose delegate is the agent address, and redelegates from the stored root
   permission for each quote. Runnable reference:
   `docs/examples/agent-permission-bootstrap.mjs`.

## Step 4 — Pay and retrieve

Re-send the **identical** body from Step 2 with the payment header:

```http
POST /api/v1/inference
Content-Type: application/json
PAYMENT-SIGNATURE: <base64 x402 payload>

{ ...the exact same body you quoted... }
```

The server verifies the payment, reserves the quote, calls Venice behind the
seller key, settles, and returns `200`:

```json
{
  "quote": { "quoteId": "quote_...", "conversationId": "conv_...", "turnId": "turn_...", "transcriptHash": "sha256:..." },
  "inferenceRequest": { "inferenceRequestId": "inf_...", "status": "completed" },
  "receipt": { "receiptId": "rcpt_...", "usage": { "promptTokens": 0, "completionTokens": 0 } },
  "completion": { "content": "…model output…" },
  "nextTurn": { "conversationId": "conv_...", "parentInferenceRequestId": "inf_...", "parentReceiptId": "rcpt_...", "priorTranscriptHash": "sha256:..." }
}
```

### Rules that bite

- **`quoted_request_mismatch` (400):** the paid body must reproduce the quoted
  request. Re-send the same body. The server re-derives `requestHash` and also
  rejects a changed `offerId`/`model`, `maxOutputTokens`, `policyPreset`, or
  `buyerAddress`.
- **No raw prompt retention.** DiemSum never stores your prompt text — only
  `sha256:` hashes — which is why multi-turn must re-send the full transcript
  client-side. Completion text from a **paid** request is retained server-side
  (never exposed through public receipts) so the exact output can be
  re-delivered if settlement has to be retried.
- **Settlement withholding.** If Venice succeeds but settlement fails, output is
  withheld and the request is marked `withheld`. Retry with the **same**
  `PAYMENT-SIGNATURE` to resume settlement and retrieve the stored output
  (Venice is not re-run).

## Step 5 — Status (optional)

```http
GET /api/v1/quotes/{quoteId}          → quote + envelope | 404 | 410 (expired)
GET /api/v1/inference/{inferenceRequestId} → { request, quote } | 404
```

## Step 6 — Multi-turn conversations

DiemSum keeps no transcript server-side. For a follow-up, re-send the **full**
`messages[]` (including the prior assistant content you kept) **plus** the
lineage from the previous turn's `nextTurn`:

```json
{
  "offerId": "offer_...",
  "messages": [
    { "role": "user", "content": "…" },
    { "role": "assistant", "content": "…prior reply you kept…" },
    { "role": "user", "content": "the next question" }
  ],
  "conversationId": "conv_...",
  "parentInferenceRequestId": "inf_...",
  "parentReceiptId": "rcpt_..."
}
```

The follow-up gets its own quote/receipt with a fresh `turnId` but the same
`conversationId`. Then repeat Steps 2–4.

## Errors quick reference

| Status | code | meaning |
| --- | --- | --- |
| 402 | `payment_required` | quote issued; pay and retry |
| 400 | `quoted_request_mismatch` | paid body ≠ quoted body |
| 400 | `transcript_hash_mismatch` | supplied `transcriptHash` ≠ server hash |
| 410 | `quote_expired` | quote older than 5 min; re-quote |
| 429 | `rate_limited` | back off using `Retry-After` |
| 503 | `facilitator_config_missing` | deployment not payment-capable |

## More

- OpenAPI spec: `/openapi.yaml` · human walkthrough: `/docs`
- Runnable quickstart: `docs/examples/agent-quickstart.mjs`
- Agent permission bootstrap (Step 0 mandate + autonomous spend): `docs/examples/agent-permission-bootstrap.mjs`
- Direct x402 + ERC-7710 snippet: `docs/examples/direct-x402-erc7710.ts`
