executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
. Default `7d`. Values above `90d` are **clamped** to 90 days (the response `lookback` then reads `\"90d\"`).\n- **OR** `startDate=YYYY-MM-DD` **and** `endDate=YYYY-MM-DD` (inclusive, UTC days). The range may span at most 90 days and `endDate` must not be before `startDate`. Send both: if only one is given it is ignored and `lookback` applies.\n\n### Response (selected keys)\n\n```json\n{\n \"lookback\": \"7d\",\n \"byDate\": [{ \"date\": \"2026-04-20\", \"USD\": 0.5, \"DIEM\": 10.25 }],\n \"byModel\": [\n {\n \"modelName\": \"GLM 5.1\",\n \"unitType\": \"tokens\",\n \"modelType\": \"LLM\",\n \"totalUsd\": 0.4,\n \"totalDiem\": 12.5,\n \"totalUnits\": 50000,\n \"breakdown\": [\n { \"type\": \"Output\", \"usd\": 0.3, \"diem\": 10, \"units\": 35000 },\n { \"type\": \"Input\", \"usd\": 0.1, \"diem\": 2.5, \"units\": 15000 }\n ]\n }\n ],\n \"byModelDaily\": [{ \"date\": 1705276800000, \"GLM 5.1\": 5.5, \"Kimi K2.6\": 3.2 }],\n \"byModelDailyUsd\": [{ \"date\": 1705276800000, \"GLM 5.1\": 0.4, \"Kimi K2.6\": 0 }],\n \"topModels\": [\"GLM 5.1\", \"Kimi K2.6\"],\n \"byKey\": [\n { \"apiKeyId\": \"key_abc123\", \"description\": \"Production Key\",\n \"totalUsd\": 0.8, \"totalDiem\": 15, \"totalUnits\": 75000 },\n { \"apiKeyId\": null, \"description\": \"Web App\",\n \"totalUsd\": 0, \"totalDiem\": 4, \"totalUnits\": 25000 }\n ],\n \"byKeyDaily\": [{ \"date\": 1705276800000, \"Production Key\": 8.5, \"Web App\": 2 }],\n \"byKeyDailyUsd\": [{ \"date\": 1705276800000, \"Production Key\": 0.8, \"Web App\": 0 }],\n \"topKeyNames\": [\"Production Key\", \"Web App\"]\n}\n```\n\n- `byDate` has one entry per day in the window (ascending, zero-filled). `USD` totals include `BUNDLED_CREDITS` but not `EARNED_CREDITS`: spend paid from earned credits is left out of every analytics total.\n- `byModel` / `byKey` list **every** model and key, sorted by total spend (USD + DIEM). `breakdown` (Input / Output / Cache Read / Cache Write …) is present only when a model has more than one token type.\n- `byModelDaily` / `byKeyDaily` are chart rows: `date` (Unix ms) plus one field per series with USD + DIEM combined. `byModelDailyUsd` / `byKeyDailyUsd` are the same rows with only the USD (incl. bundled credits) portion. These two `*Usd` arrays are returned but not yet in the published schema.\n- `topModels` / `topKeyNames` are the charted series: the top **50** by spend, plus a trailing `\"Other\"` series that sums everything past 50 (only when there are more than 50). The spec text still says \"top 8\"; the API returns 50.\n- `apiKeyId: null` / `description: \"Web App\"` is usage from the Venice web app. Keys without a description show as `Key ...\u003clast 6 of id>`; duplicate descriptions get a ` (...\u003clast 6 of id>)` suffix so series don't collide.\n- Credit purchases are excluded; only debits are aggregated.\n\n## Recipes\n\n### Abort before calling inference if the account is empty\n\n`/billing/balance`'s `canConsume` ignores per-key limits and the account's API tier, so gate on `accessPermitted` from `GET /api_keys/rate_limits` instead (works with the key you are about to use, including `INFERENCE` keys):\n\n```ts\nconst { data } = await fetch(`${base}/api_keys/rate_limits`, { headers }).then(r => r.json())\nif (!data.accessPermitted) throw new Error('Venice balance exhausted — top up before continuing')\n```\n\n### Monthly CSV export\n\n```bash\ncurl -D headers.txt \"https://api.venice.ai/api/v1/billing/usage-history?startTimestamp=2026-04-01T00:00:00Z&endTimestamp=2026-05-01T00:00:00Z&pageSize=1000\" \\\n -H \"Authorization: Bearer $VENICE_ADMIN_KEY\" \\\n -H \"Accept: text/csv\" \\\n -o billing-april-001.csv\n```\n\nRead `x-next-cursor` from `headers.txt` and re-request with `?cursor=\u003ctoken>` (and\nno other parameters) until the header is absent.\n\n### Top-models chart\n\n```ts\nconst a = await fetch(`${base}/billing/usage-analytics?lookback=30d`, { headers }).then(r => r.json())\n// chart(a.byModelDaily, { series: a.topModels, xField: 'date' })\n```\n\n## Errors\n\n| Code | Meaning |\n|---|---|\n| `400` | `/billing/usage-history`: a `cursor` sent with any filter, an unknown parameter, a malformed timestamp, `pageSize` outside 10–1000, `endTimestamp` not later than `startTimestamp`, or an invalid/expired cursor. `/billing/usage-analytics`: malformed `lookback` / date, `endDate` before `startDate`, or a calendar range over 90 days (`lookback` over 90d is clamped, not rejected). |\n| `401` | Invalid key, or a non-ADMIN key on any billing endpoint. |\n| `402` | No `Authorization` header at all — the x402 auth challenge. |\n| `410` | `/billing/usage` — always. Sunset 2026-09-16. |\n| `429` | `/billing/usage` over 60 requests/min per IP; or the generic API error budget. |\n| `500` | Internal error. |\n| `504` | `/billing/usage-analytics` query timed out — shorten `lookback` or the date range. |\n\n## Gotchas\n\n- Every billing endpoint needs an **ADMIN** key, including `usage-analytics` (older docs said any key worked — that changed on 2026-07-21).\n- Don't call `/billing/usage` at all. It returns no data; treat any `410` from it as \"migrate now\".\n- `/billing/usage-history` takes `startTimestamp` / `endTimestamp` (full ISO datetimes with `Z`) and `pageSize`; `/billing/usage-analytics` takes `lookback` or `startDate` / `endDate` as plain `YYYY-MM-DD` dates. Don't mix the formats.\n- `VCU` is not accepted by `/billing/usage-history`'s `currency` filter; use `DIEM`.\n- `inferenceDetails` is `null` for non-inference entries (e.g. subscription charges).\n- One inference request can appear as several ledger rows (e.g. separate input and output token SKUs) that repeat the same `inferenceDetails`. Sum `amount` across rows, but dedupe by `requestId` before summing token counts.\n- The analytics endpoint is **cached 10 min** — sudden spikes lag in the dashboard by that window.\n- `byModelDaily.date` is a **Unix milliseconds integer**; `byDate.date` is a **`YYYY-MM-DD` string**.\n- Usage from the Venice web app has `apiKeyId: null` — don't drop it when reconciling.\n- For x402 (wallet) balance, don't use these endpoints — use `GET /x402/balance/{walletAddress}`.\n","licenseDocuments":[]}}
venice-billingVenice billing and usage analytics - GET /billing/balance, GET /billing/usage-history (keyset-paginated per-request ledger, JSON or CSV), and GET /billing/usage-analytics (aggregated by date/model/key). All require an ADMIN key. GET /billing/usage was sunset on 2026-09-16 and now always returns 410. Covers the DIEM/EARNED_CREDITS/BUNDLED_CREDITS/USD consump…
Independent GitHub snapshot · Automated classification · Original documentation governs
Read-only endpoints for account-level billing and analytics. usage-analytics is tagged Beta in the spec — its schema/behavior may change.
| Endpoint | Purpose |
|---|---|
GET /billing/balance | Current canConsume flag, remaining DIEM, earned credits, bundled credits and USD, epoch allocation. |
GET /billing/usage-history | Per-request ledger with keyset (cursor) pagination. JSON or CSV. |
GET /billing/usage-analytics | Aggregated breakdowns: by date, model, API key. |
GET /billing/usage | Retired. Sunset 2026-09-16; every request returns 410. |
All three live endpoints require Bearer auth with an ADMIN key — an INFERENCE key gets 401 ("Admin API key required"). They do not accept x402 wallet auth; for wallet balances use venice-x402. A request with no Authorization header at all gets a 402 x402 discovery body rather than a 401 — ignore its payment options and send an ADMIN Bearer key; a bad key gets 401.
GET /billing/usageis gone. Since 2026-09-16 it returns410 Goneto every request, authenticated or not, with no usage data. The body'serrorstring names the replacement and the parameter renames, and the response carriesDeprecation: @1783555200,Sunset: Wed, 16 Sep 2026 00:00:00 GMT, andLink: </api/v1/billing/usage-history>; rel="successor-version", <https://docs.venice.ai/api-reference/endpoint/billing/usage-history>; rel="deprecation"; type="text/html". The route is rate limited to 60 requests/minute per IP (then429, whose message repeats the migration pointer). Migrate toGET /billing/usage-history:page+limit→pageSize+nextCursor,startDate/endDate→startTimestamp/endTimestamp.
Each request is charged to a single currency, picked in this order:
DIEM — from staked DIEM; the allocation resets every epoch (UTC day).EARNED_CREDITS — USD-denominated earned credits.BUNDLED_CREDITS — USD-denominated credits included with paid Venice subscription plans.USD — prepaid fiat balance.Prepaid (quoted) charges such as queued jobs and images use the first currency that covers the whole cost. Token-priced requests are billed after they run, to the first currency with a positive balance, which can leave it slightly negative.
Legacy VCU (the old name for DIEM) is no longer a consumable currency; it is rejected by /billing/usage-history's currency filter.
A per-key USD consumption limit covers USD, BUNDLED_CREDITS and EARNED_CREDITS together; a DIEM limit covers DIEM (see venice-api-keys).
GET /billing/balancecurl https://api.venice.ai/api/v1/billing/balance \
-H "Authorization: Bearer $VENICE_ADMIN_KEY"
{
"canConsume": true,
"consumptionCurrency": "DIEM",
"balances": { "diem": 90.5, "usd": 25, "bundledCredits": 10, "earnedCredits": null },
"diemEpochAllocation": 100
}
canConsume is true when the account's spendable DIEM, earned credits, bundled credits and USD total at least $0.10. It does not account for per-key limits or the account's API tier — the real admission check runs on each inference request. Use GET /api_keys/rate_limits → accessPermitted for a check that includes those.consumptionCurrency is the first currency with a positive balance, in the order DIEM, EARNED_CREDITS, BUNDLED_CREDITS, USD, or null. The spec enum also lists VCU, but it is never returned.balances.diem is null when the account isn't staking DIEM; otherwise the remaining DIEM this epoch.balances.usd, balances.bundledCredits and balances.earnedCredits are null when not positive.diemEpochAllocation is the total DIEM for the current epoch — balances.diem / diemEpochAllocation = remaining fraction.GET /billing/usage-historyPer-request ledger with keyset (cursor) pagination, in ascending timestamp order.
curl "https://api.venice.ai/api/v1/billing/usage-history?startTimestamp=2026-06-01T00:00:00Z&endTimestamp=2026-07-01T00:00:00Z&pageSize=1000¤cy=USD" \
-H "Authorization: Bearer $VENICE_ADMIN_KEY" \
-H "Accept: application/json"
A request is either a filtered first page or a bare continuation. Sending
cursor alongside any filter is a 400, and so is any unknown parameter — the
validator is strict.
| Param | Notes |
|---|---|
startTimestamp | Optional. Inclusive lower bound, ISO 8601 UTC with a Z suffix (max 40 chars). First page only. |
endTimestamp | Optional. Exclusive upper bound, same format. Must be later than startTimestamp. Consecutive windows that share a boundary walk the history with no gaps and no overlaps. |
currency | Optional. USD / DIEM / BUNDLED_CREDITS / EARNED_CREDITS. |
pageSize | Integer 10–1000. Default 1000. |
cursor | Opaque continuation token ([A-Za-z0-9_-], ≤ 512 chars) from a previous nextCursor. Carries the filters and page size of the walk it continues, so send it alone. |
A cursor that is expired, tampered with, or was issued to another user returns
400 ("Invalid or expired cursor; restart from the first page"). Page contents
stay stable while new usage is recorded; no result totals are reported.
{
"data": [
{
"timestamp": "2026-06-15T19:05:10.504Z",
"sku": "zai-org-glm-5-1-llm-output-mtoken",
"units": 0.000227,
"pricePerUnitUsd": 2.8,
"amount": -0.06356,
"currency": "DIEM",
"notes": "API Inference",
"inferenceDetails": {
"requestId": "chatcmpl-4007fd29f42b7d3c4107f4345e8d174a",
"promptTokens": 339,
"completionTokens": 227,
"inferenceExecutionTime": 2964
}
}
],
"nextCursor": "AZq3fK9tXhIVDm2j4vN8cQwYt1sB6uEoLxRgPzKaJdHfM5nC7yW0K3w"
}
nextCursor is null on the last page.
sku — billing line item (model + unit type + format).units — for LLMs, millions of tokens (e.g. 0.000227 = 227 tokens).pricePerUnitUsd — rate per unit in USD.amount — negative for a debit.currency — USD, DIEM, BUNDLED_CREDITS, or EARNED_CREDITS.inferenceDetails — null for non-inference entries. When present, requestId is the id returned on the original response; promptTokens, completionTokens, and inferenceExecutionTime (ms) are each nullable when not recorded (tokens are null for non-LLM usage).Send Accept: text/csv. Columns: timestamp, sku, pricePerUnitUsd, units, amount, currency, notes, inferenceDetails.requestId, inferenceDetails.inferenceExecutionTime, inferenceDetails.promptTokens, inferenceDetails.completionTokens.
Content-Disposition is attachment; filename=billing-usage-history-<UTC export stamp>.csv, so each page of a walk downloads under a unique, sort-ordered name. Because CSV has no envelope, the continuation token moves to the x-next-cursor response header, which is absent on the last page.
let url = `${base}/billing/usage-history?startTimestamp=${start}&endTimestamp=${end}`
for (;;) {
const page = await fetch(url, { headers }).then(r => r.json())
handle(page.data)
if (page.nextCursor === null) break
url = `${base}/billing/usage-history?cursor=${encodeURIComponent(page.nextCursor)}`
}
GET /billing/usage-analyticsAggregated summary for dashboards. Cached 10 minutes per user + period.
curl "https://api.venice.ai/api/v1/billing/usage-analytics?lookback=7d" \
-H "Authorization: Bearer $VENICE_ADMIN_KEY"
lookback=Nd — must match ^[1-9]\d*d$. Default 7d. Values above 90d are clamped to 90 days (the response lookback then reads "90d").startDate=YYYY-MM-DD and endDate=YYYY-MM-DD (inclusive, UTC days). The range may span at most 90 days and endDate must not be before startDate. Send both: if only one is given it is ignored and lookback applies.{
"lookback": "7d",
"byDate": [{ "date": "2026-04-20", "USD": 0.5, "DIEM": 10.25 }],
"byModel": [
{
"modelName": "GLM 5.1",
"unitType": "tokens",
"modelType": "LLM",
"totalUsd": 0.4,
"totalDiem": 12.5,
"totalUnits": 50000,
"breakdown": [
{ "type": "Output", "usd": 0.3, "diem": 10, "units": 35000 },
{ "type": "Input", "usd": 0.1, "diem": 2.5, "units": 15000 }
]
}
],
"byModelDaily": [{ "date": 1705276800000, "GLM 5.1": 5.5, "Kimi K2.6": 3.2 }],
"byModelDailyUsd": [{ "date": 1705276800000, "GLM 5.1": 0.4, "Kimi K2.6": 0 }],
"topModels": ["GLM 5.1", "Kimi K2.6"],
"byKey": [
{ "apiKeyId": "key_abc123", "description": "Production Key",
"totalUsd": 0.8, "totalDiem": 15, "totalUnits": 75000 },
{ "apiKeyId": null, "description": "Web App",
"totalUsd": 0, "totalDiem": 4, "totalUnits": 25000 }
],
"byKeyDaily": [{ "date": 1705276800000, "Production Key": 8.5, "Web App": 2 }],
"byKeyDailyUsd": [{ "date": 1705276800000, "Production Key": 0.8, "Web App": 0 }],
"topKeyNames": ["Production Key", "Web App"]
}
byDate has one entry per day in the window (ascending, zero-filled). USD totals include BUNDLED_CREDITS but not EARNED_CREDITS: spend paid from earned credits is left out of every analytics total.byModel / byKey list every model and key, sorted by total spend (USD + DIEM). breakdown (Input / Output / Cache Read / Cache Write …) is present only when a model has more than one token type.byModelDaily / byKeyDaily are chart rows: date (Unix ms) plus one field per series with USD + DIEM combined. byModelDailyUsd / byKeyDailyUsd are the same rows with only the USD (incl. bundled credits) portion. These two *Usd arrays are returned but not yet in the published schema.topModels / topKeyNames are the charted series: the top 50 by spend, plus a trailing "Other" series that sums everything past 50 (only when there are more than 50). The spec text still says "top 8"; the API returns 50.apiKeyId: null / description: "Web App" is usage from the Venice web app. Keys without a description show as Key ...<last 6 of id>; duplicate descriptions get a (...<last 6 of id>) suffix so series don't collide./billing/balance's canConsume ignores per-key limits and the account's API tier, so gate on accessPermitted from GET /api_keys/rate_limits instead (works with the key you are about to use, including INFERENCE keys):
const { data } = await fetch(`${base}/api_keys/rate_limits`, { headers }).then(r => r.json())
if (!data.accessPermitted) throw new Error('Venice balance exhausted — top up before continuing')
curl -D headers.txt "https://api.venice.ai/api/v1/billing/usage-history?startTimestamp=2026-04-01T00:00:00Z&endTimestamp=2026-05-01T00:00:00Z&pageSize=1000" \
-H "Authorization: Bearer $VENICE_ADMIN_KEY" \
-H "Accept: text/csv" \
-o billing-april-001.csv
Read x-next-cursor from headers.txt and re-request with ?cursor=<token> (and
no other parameters) until the header is absent.
const a = await fetch(`${base}/billing/usage-analytics?lookback=30d`, { headers }).then(r => r.json())
// chart(a.byModelDaily, { series: a.topModels, xField: 'date' })
| Code | Meaning |
|---|---|
400 | /billing/usage-history: a cursor sent with any filter, an unknown parameter, a malformed timestamp, pageSize outside 10–1000, endTimestamp not later than startTimestamp, or an invalid/expired cursor. /billing/usage-analytics: malformed lookback / date, endDate before startDate, or a calendar range over 90 days (lookback over 90d is clamped, not rejected). |
401 | Invalid key, or a non-ADMIN key on any billing endpoint. |
402 | No Authorization header at all — the x402 auth challenge. |
410 | /billing/usage — always. Sunset 2026-09-16. |
429 | /billing/usage over 60 requests/min per IP; or the generic API error budget. |
500 | Internal error. |
504 | /billing/usage-analytics query timed out — shorten lookback or the date range. |
usage-analytics (older docs said any key worked — that changed on 2026-07-21)./billing/usage at all. It returns no data; treat any 410 from it as "migrate now"./billing/usage-history takes startTimestamp / endTimestamp (full ISO datetimes with Z) and pageSize; /billing/usage-analytics takes lookback or startDate / endDate as plain YYYY-MM-DD dates. Don't mix the formats.VCU is not accepted by /billing/usage-history's currency filter; use DIEM.inferenceDetails is null for non-inference entries (e.g. subscription charges).inferenceDetails. Sum amount across rows, but dedupe by requestId before summing token counts.byModelDaily.date is a Unix milliseconds integer; byDate.date is a YYYY-MM-DD string.apiKeyId: null — don't drop it when reconciling.GET /x402/balance/{walletAddress}.