Overview
The Billing API reports what your organization spends on Martini. Every number it returns is the organization's own spend in olives, Martini's credit unit. It never returns what Martini pays for a generation or which provider Martini sends a request to; models are named by the Martini model you chose. The two money figures it carries are yours: the nominal value of an olive, and what you paid on an invoice.
It lives under https://api.martini.film/v1/billing and uses the same key as the Generation API. The balance is the pre-flight an operator or an agent runs before submitting a batch. The reporting routes read the account, the organization's price list, spend over time, the ledger, and invoices. Budgets are the one thing the API writes: a hard olive limit on the organization, a project, a member, or an API key.
| Route | Returns | Needs |
|---|---|---|
GET /v1/billing/balance | The olive balance, or a run’s budget. | Any valid key. |
GET /v1/billing/account | Plan, olives, automatic top-up, organization budget, olive value. | billing:read, enterprise grant |
GET /v1/billing/pricing | What the organization pays per model and for agent work. | billing:read, enterprise grant |
GET /v1/billing/costs | Spend as calendar buckets, grouped by up to two keys. | billing:read, enterprise grant |
GET /v1/billing/transactions | The ledger, newest first, cursor-paginated. | billing:read, enterprise grant |
GET /v1/billing/invoices | Invoices, and a short-lived PDF link per invoice. | billing:read, enterprise grant |
GET /v1/billing/budgets | Every budget with where it stands; one by id. | billing:read, enterprise grant |
POST /v1/billing/budgets | Set a budget; PATCH changes it, DELETE removes it. | billing:write, enterprise grant |
Enterprise access
The reporting routes are off by default and turned on per organization under a contract. The reference on this page is public: it describes shapes, not prices, and every number the API returns is served only to a key of the organization that owns it.
The balance is the one exception. GET /v1/billing/balance answers every valid key with no grant and no extra permission — it is operational, not financial reporting, and the same figure every member already sees in the app.
To enable the Billing API for your organization, tell us who you are and what you want to build. The request is filed under your account, so the organizations you belong to come with it.
Checking your session…
Prefer email? Write to hello@martini.film with your organization name and what you want to build.
Without the grant, every reporting route answers 403 BILLING_API_NOT_ENABLED; while the grant cannot be checked, they answer 503 BILLING_API_ACCESS_UNAVAILABLE with Retry-After: 10.
Once enabled, two more presets appear when an admin creates a key under Settings → Martini API: Finance read-only (billing:read and nothing else — a key that can read spend but never generate) and Full access (every family at write). Until then they are shown but cannot be chosen, and a key requested with a billing permission over the API answers 403 BILLING_API_NOT_ENABLED. Existing keys are untouched either way: a permission is fixed when its key is created, so enabling the Billing API never widens a key that is already out.
Authentication
Send the key as Authorization: Bearer <key>. An organization admin creates keys under Settings → Martini API. Requests belong to the key's organization; anything outside it reads as 404, never 403, so ids from another workspace cannot be probed. An optional X-Martini-API-Version: 2026-08-13 header pins the response envelope; the served version is echoed back on every response.
Three kinds of key reach the balance, and each gets the answer that fits it:
| Key | What it reads |
|---|---|
Organization key (mtn_live_, or martini login approved for one organization) | Its own organization. Passing organizationId is accepted only for that organization; any other id is 404. |
Account key (martini login approved "as you") | Any organization its user belongs to: name it with ?organizationId=, or omit it for the personal organization. |
Workflow run key (mtn_run_, minted for one run's sandbox) | The run form of the balance: its own budget and whether the organization covers it — never the organization’s numbers. A run key is refused by every reporting route with 403 RUN_KEY_NOT_ALLOWED. |
The reporting routes take the same organizationId query parameter with the same rules: an organization key reads its own organization, an account key names one of its user's or gets the personal one.
Permissions
A key holds a set of family:level permissions, chosen when it is created and never widened; write implies read. The Billing API is its own family: billing:read reads the reporting routes (account, price list, costs, ledger, invoices, budgets), billing:write additionally sets budgets. The balance needs neither — it answers any valid key — so a Runtime key (generations:write, workflows:write, assets:write) reads it and nothing else on this page.
A Finance read-only key holds billing:read alone: it reads spend and cannot generate, run a workflow, or upload, so it is the key to hand a dashboard, a warehouse loader, or a finance tool. A call the key may not make answers 403 API_KEY_PERMISSION_MISSING with the required permission, the granted set, and the settingsUrl where a key with it is created; GET /v1/me lists key.permissions so a client can check before its first call.
Rate limit
Each key may make 120 requests per sliding minute across /v1/billing, the balance included. Every answer carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds by which the window has fully turned over); a request past the allowance answers 429 RATE_LIMITED with Retry-After. A warehouse sync that pages the ledger at 1000 rows a page stays well inside it; a dashboard that polls the balance every second does too.
Balance
GET /v1/billing/balance
The olive balance of the organization the key acts for. Call it before submitting a batch of generations or workflow runs, so the work is covered before it starts. Needs a valid key and nothing else: no enterprise grant, no permission.
| Parameter | Type | Notes |
|---|---|---|
organizationId | uuid | Optional. For an account key: which of the user’s organizations to read. An organization key accepts only its own id. |
The response has one of two forms, told apart by kind. No form carries both olives and covered, so a client never has to guess which it got.
Organization form
What an organization key or an account key gets.
| Field | Meaning |
|---|---|
organization | The organization the numbers belong to. |
billingEnabled | False when this deployment has no billing configured: olives is null and nothing is charged. Never a zero, never a sum over past charges. |
plan | The subscription plan id and status, and when the current period ends; each null when billing is disabled. |
olives.available | What a new generation or run can spend right now. Active reservation holds (a submitted generation reserves its estimate for up to 20 minutes) are already netted out, so this is the figure the next charge is checked against. |
olives.granted | Everything granted this period: included plus purchased. |
olives.included | The plan’s included allowance for the period. |
olives.purchased | Top-ups and other prepaid grants. |
olives.usedThisPeriod | Consumed since the period started. |
olives.reserved | Olives held by admissions that have not settled yet — work in flight. Informational: available already nets them. Null when this deployment does not record holds. |
olives.unlimited | True when the organization’s olives are not metered; available then bounds nothing and every batch is covered. |
olives.resetsAt | When the included allowance renews, ISO 8601; null when it does not. |
asOf | When the numbers were read from the billing system; at most 10 seconds old. |
With billing disabled:
To check a batch, compare olives.available against your estimate (a run's olives.budget, or the model's price from the price list times the count). unlimited: true or billingEnabled: false covers any batch.
Run form
What a workflow run key gets. An agent executing inside a run can plan against its own budget, and learns whether the organization stands behind it, without the organization's balance ever landing in a run log. The same read answers covered; the numbers stay on the server.
| Field | Meaning |
|---|---|
runId | The run the key was minted for. |
budget.olives | The run’s olive budget; null for an unbudgeted run. |
budget.source | explicit when the caller passed a budget; auto when the run started without one and Martini arms the rail at each action’s gate. |
budget.spent | Generation and agent olives the run has spent so far, as the organization is charged for them. |
budget.remaining | olives minus spent, floored at zero; null for an unbudgeted run. |
covered | Whether the organization’s available olives cover budget.remaining. For an unbudgeted run: whether the organization has any olives left to spend. Always true when billingEnabled is false. |
asOf | When the organization balance behind covered was read. |
Freshness
The balance is read from the billing system and cached for 10 seconds per organization, so a launcher polling every second costs one read; asOf is the read time, so a poller can see the age. When the billing system cannot be reached and no fresh value is cached, the route answers 503 BALANCE_UNAVAILABLE with Retry-After: 10. A value past its cache window is discarded, not served with a flag: a stale number is never presented as current.
Enforcement happens at charge time, not here. A balance that covers a batch when you read it does not reserve anything; a generation submitted in between spends from the same pool. Submit soon after checking, or set an oliveBudget on each run so the total is bounded either way.
From an agent
The MCP tool get_balance answers the same question with the same shape for an agent working through the MCP connector or the CLI. It takes an optional organizationId, or a projectId to read the organization that pays for that project; omitted, it reads the organization the key or session acts for. Inside a workflow run it returns the run form regardless of arguments — the same fence as the route. It is reachable as POST /v1/tools/get_balance with a key like every other tool, and as martini get_balance from the CLI.
Account
GET /v1/billing/account
The status call: the organization's plan, the same olives object as the balance, automatic top-up, the organization-wide budget, and the nominal olive value, in one read. A dashboard that shows “where do we stand” needs nothing else.
| Field | Meaning |
|---|---|
organization | The organization the numbers belong to. |
billingEnabled | False when this deployment has no billing configured: olives and autoTopUp are null, every plan member is null. |
plan | The subscription plan id and status, and when the current period ends. |
olives | The same object the balance returns; see the olives fields above. |
autoTopUp | Automatic top-up as configured in the app: whether it is on, the available figure it fires below, and how many olives it adds. Null when off or unavailable. |
budget | The organization-wide budget when one exists: its limit, period, alerts, and where it stands, as GET /v1/billing/budgets lists it. Null when none is set. |
oliveValueUsd | The public nominal value of one olive in US dollars — the one dollar figure in the API, so a dashboard can convert. Not a price paid and not a rate; the same number for every organization. |
asOf | When the numbers were read from the billing system; at most 10 seconds old. |
Like the balance, the account is read from the billing system and cached for 10 seconds; it answers 503 BALANCE_UNAVAILABLE the same way when that read fails.
Pricing
GET /v1/billing/pricing
What this organization pays, in olives, for every model it can generate with and for agent work. Prices are finished figures: the organization's own where it has an agreement (source: "organization"), the public list price otherwise (source: "list") — and nothing says how a figure was reached. There is no provider, no cost, and no term on this route, only the number a generation will bill.
| Parameter | Type | Notes |
|---|---|---|
organizationId | uuid | Optional. For an account key: which of the user’s organizations to read. |
modelId | string | Optional. One model only: a catalog id (kling-v3) or an agent-work id (opus-5.5, sandbox, transcription). An id the organization cannot use answers 400 INVALID_QUERY. |
Models are sorted by id and named by the Martini catalog id and display name you pick in the app. Each is one of three things, told apart by pricing.kind:
| kind | Meaning |
|---|---|
card | A price card to compute from (below). mediaInputCard is present when a request that carries media inputs prices differently. |
quote | The price depends on the request’s inputs; only the generate-time quote can price it. No card is published. |
direct | The organization settles this model outside Martini and is charged no olives for it. |
Price cards
A card is everything needed to price a request before it is submitted. Start from the tier the request lands in (or olivesPerUnit when there is none), apply the quality and aspect factors, add the surcharges, and respect minimumOlives where the card carries one. The card reads the way the charge does: a tier’s own figure wins over the card’s, and a figure the charge never applies to a model is not on its card. Members a model does not use are absent, not null.
| Field | Meaning |
|---|---|
unit | What one priced unit is: second, image, request, minute, or thousandChars. |
olivesPerUnit | Olives per unit at the default resolution and quality. Null when only resolutionTiers price the model. |
minimumOlives | The least a generation bills, however short. Only the per-minute and per-thousand-character cards carry one; the other units bill no floor. |
resolutionTiers[] | One row per resolution the model prices differently. resolution is the label a request passes to land in it (720p, 1080p, 2K); a model priced by output height also carries maxHeight, and the first tier whose ceiling covers the output wins. A tier may carry its own byQuality, aspectFactors, and surcharges — a block that replaces the card’s surcharges whole at that tier (an empty block: none). |
durationTiers[] | For a per-request model priced by length: the first tier whose maxSeconds covers the output prices it; a longer output bills olivesPerUnit. |
megapixelTiers[] | For a per-image model priced by output size: the first tier whose maxMegapixels covers the output prices it; a larger output bills olivesPerUnit. |
byQuality | Olives per unit by render quality, where a quality has its own rate. |
qualityFactors | Factors on the rate by render quality, where a quality has no rate of its own. |
aspectFactors | Factors on the rate by aspect ratio; the rates are the 16:9 price. A tier’s own aspectFactors win at that tier. |
surcharges | Per-billed-second additions (audioPerSecond, voicePerSecond) and per-input charges (perReferenceImage after includedReferenceImages, up to maxReferenceImages; perThousandReferenceTokens after includedReferenceTokens), on top of the unit rate. A tier’s own surcharges win at that tier. |
videoReferences | How a request that carries video references bills when it differs: its own olivesPerUnit, resolutionTiers, and byQuality, whether trimmed reference seconds bill on top of the generated ones (billsReferenceSeconds), in whole seconds (wholeSeconds), and no less than a fraction of the output (minimumOutputFraction). |
billedExtraSeconds | Seconds billed once per request beyond the generated seconds. |
isMaximum | True when the figure is a ceiling that settles downward once the generation completes. |
chargesBlockedResults | True when a result the provider’s safety filter blocks after generating it bills like a finished one and is not refunded. Absent, a blocked result is refunded. |
Agent work
agentWork is what the organization pays for the work an agent does on its behalf: chat models by the id and name the picker shows, the sandbox, and tools such as transcription. Rates are olives per million units of each bucket the resource is metered in — the finished price, with nothing of its terms.
| Field | Meaning |
|---|---|
kind | model for a chat model, compute for the sandbox, tool for a utility such as transcription. |
id | What a user picks: the chat model id (opus-5.5), sandbox, or transcription. |
name | The display name, as the picker shows it. |
serviceTier | standard or fast. A model with both tiers appears twice. |
olivesPerMillion | Olives per million units of each bucket the resource is metered in: input, cacheRead, cacheWrite, cacheWrite1h, and output tokens for a model; cpuMilliseconds, memoryMilliseconds, and diskMilliseconds for the sandbox; computeSeconds for a tool. |
longContext | Where a model charges more past a context length: thresholdTokens and the inputFactor and outputFactor applied beyond it. Absent otherwise. |
Costs
GET /v1/billing/costs
Olive spend as a cube: calendar buckets over a range, each holding one cell per group. It is the route a finance dashboard or a chargeback report reads; the ledger below is the row level under it.
| Field | Meaning |
|---|---|
range | The resolved [from, to): what was asked, defaulted to the trailing 30 days. |
bucket | day, week, or month, as requested. |
groupBy | The group keys, in the order requested. |
buckets[].start, end | The bucket, [start, end) in UTC: a calendar day, an ISO week from Monday, or a month. |
buckets[].results[].group | The group the cell belongs to, carrying exactly the groupBy members requested. A null member is unattributed: agent work has no model, a grant has no project. |
buckets[].results[].olives | Charges in the bucket. |
buckets[].results[].refundedOlives | Refunds in the bucket. |
buckets[].results[].netOlives | olives minus refundedOlives. |
buckets[].results[].generations | Charged generations in the bucket. Agent work and non-generation rows do not count. |
buckets[].nextCursor | Pass back as cursor for the next page of buckets; null on the last page. |
asOf | When the cube was computed. |
Groups and filters
| Parameter | Type | Notes |
|---|---|---|
organizationId | uuid | Optional. For an account key: which of the user’s organizations to read. |
from | RFC 3339 | Inclusive start. Defaults to 30 days before to. |
to | RFC 3339 | Exclusive end. Defaults to now. The range may cover at most 366 days. |
bucket | day | week | month | Bucket size; calendar-aligned in UTC (weeks start Monday). Default day. |
groupBy | list | Up to two of project, model, actor, user, apiKey, origin, type, billingMode. Each cell’s group carries exactly these members. Omitted: one cell per bucket with an empty group. |
projectIds | list of uuid | Only charges in these projects. |
apiKeyIds | list of uuid | Only charges made with these keys. |
userIds | list of uuid | Only charges by these members. |
models | list | Only charges for these catalog ids (kling-v3). |
origins | list | Only charges from these surfaces: ui, mcp, api. |
types | list | Only rows of these kinds: generation, export, agentWork, refund, topup, adjustment, signupBonus, transfer. |
limit | integer | Buckets per page. Default 31, at most 366. |
cursor | string | The nextCursor of the previous page, with the same filters. |
A list parameter repeats (types=generation&types=export), takes a [] suffix, or joins with commas; at most 50 distinct values each. Filters narrow the rows before they are bucketed, so a group that has no rows in a bucket is absent from that bucket, an empty bucket holds an empty results, and the cells of a bucket are sorted by netOlives, largest first.
Paging
Pages run over buckets, oldest first: limit buckets per page and nextCursor naming the next, null on the last. A page is computed from the ledger rows it covers; one that covers too many answers 400 INVALID_QUERY — lower limit or narrow the range. A cursor belongs to its filters: change a filter and start again without one, or the server answers 400 INVALID_CURSOR.
Transactions
GET /v1/billing/transactions
The organization's olive ledger, newest first: every charge, refund, top-up, and adjustment with who caused it, which project it landed in, and — for a generation — the model the user chose and the surface it came from. It is the route for a warehouse sync: page it to the end once, then from the last createdAt on a schedule.
| Field | Meaning |
|---|---|
transactions[].id | Stable across reads. |
transactions[].createdAt | When the row was written. Rows are ordered by it, newest first. |
transactions[].type | generation, export, agentWork (chat turns, workflow runs and builds, transcription), refund, topup, adjustment, signupBonus, or transfer. |
transactions[].olives | Signed: charges negative, grants and refunds positive. |
transactions[].billingMode | Whose tab the charge landed on, as recorded at the time: personalTab, teamTab, or generatorPays. Null for a grant. |
transactions[].project | The project the charge landed in; null when it has none. |
transactions[].actor | Who caused it: { type: "user", user: { id, alias } } or { type: "apiKey", apiKey: { id, label } }. An alias, never an email. Null when unknown. |
transactions[].generation | For a generation charge or refund: the generation id, the model the user chose, and the surface it came from (ui, mcp, api). Null for agent work, grants, and adjustments. |
transactions[].idempotencyKey | The Idempotency-Key the generation was submitted with, when there was one. |
nextCursor | Pass back as cursor for the next page; null on the last. |
| Parameter | Type | Notes |
|---|---|---|
organizationId | uuid | Optional. For an account key: which of the user’s organizations to read. |
from | RFC 3339 | Only rows at or after this instant. |
to | RFC 3339 | Only rows before this instant. |
types | list | Only rows of these kinds. |
projectId | uuid | Only rows in this project. |
apiKeyId | uuid | Only rows made with this key. |
userId | uuid | Only rows by this member. |
generationId | uuid | Only the rows of this generation: its charge and any refund. |
limit | integer | Rows per page. Default 100, at most 1000. |
cursor | string | The nextCursor of the previous page, with the same filters. |
The ledger is keyset-paginated on createdAt and id, so a row written while you page is never skipped or repeated. The ledger holds no free text: a row's description, its reconciliation notes, and anything about how a charge was computed stay on the server.
Invoices
GET /v1/billing/invoices
The organization's invoices, newest first: id, date, status, and what the organization paid Martini. total is in major units of its currency (ISO 4217, lowercase). With oliveValueUsd it is one of the two money figures in the API, and both are the organization's own. Empty when this deployment has no billing.
| Field | Meaning |
|---|---|
invoices[].id | The invoice id; pass it to the download route. |
invoices[].createdAt | When the invoice was issued. |
invoices[].status | paid, open, void, uncollectible, or draft. A draft is the period in progress; it has no PDF yet. |
invoices[].total | { amount, currency }: what the organization paid Martini, in major units. |
GET /v1/billing/invoices/{invoiceId}/download
A fresh link to one invoice's PDF, good for expiresIn seconds (86400). Ask again rather than storing it, the way run results hand out URLs. An id that is not one of the organization's invoices, or a draft with no PDF yet, answers 404 INVOICE_NOT_FOUND.
Budgets
A budget is a hard olive limit on one scope: the organization as a whole, one project, one member, or one API key, for all time (lifetime) or per calendar month in UTC (month). Every charge is admitted against every budget its scopes carry before any work starts — a generation submitted with a key is checked against the key's budget, the project's, the member's, and the organization's — and a charge that would cross one is refused. A key budget bounds the generations made with the key, through POST /v1/generations and through the generate tools on POST /v1/tools/{name}; what the other tools charge (a render, agent work) counts against the organization, project, and member budgets. Budgets are the one thing the Billing API writes: the reads need billing:read, the writes billing:write.
A project spend cap or a member monthly cap set in the Martini app is a budget here too (a project budget for all time, a member budget per month), and a budget created here with that shape shows in the app. A budget goes where its scope goes: a project's follows the project to another organization and ends when the project leaves for a personal workspace or is deleted; a member's ends with the membership. The organization-wide budget is also account.budget; a key's own budget is key.budget on GET /v1/me, so a client can see its limit before its first call.
GET /v1/billing/budgets
Every budget of the organization with where it stands, the organization-wide one first. GET /v1/billing/budgets/{budgetId} reads one; an id that is not one of the organization's budgets answers 404 BUDGET_NOT_FOUND.
| Field | Meaning |
|---|---|
budgets[].id | The budget id; the path parameter of the single-budget routes. |
budgets[].scope | { type, id, name }: what the budget bounds. type is organization, project, member, or apiKey; id the project, member (user), or key id — the organization id for an organization budget; name the project name, member alias, or key label. |
budgets[].amountOlives | The hard limit. 0 blocks every charge in the scope. |
budgets[].period | lifetime, or month: the calendar month in UTC, after which spent starts again from zero. |
budgets[].alerts[] | { atOlives, channels }: soft thresholds. When settled spend in the period reaches atOlives the organization’s admins are emailed, once per period per threshold. channels is ["email"] in this version. |
budgets[].status.spentOlives | Settled spend against the scope in the current period. |
budgets[].status.reservedOlives | Admitted but not yet settled: generations in flight. |
budgets[].status.remainingOlives | amountOlives minus spent and reserved, floored at zero. |
budgets[].status.periodStart | Start of the current period; null for a lifetime budget. |
budgets[].status.periodEnd | End of the current period, when spent resets; null for a lifetime budget. |
budgets[].status.blocked | True when the next charge against the scope would be refused. |
budgets[].createdAt | When the budget was created. |
budgets[].updatedAt | When its limit or alerts last changed. |
status is read from the same figures the admission uses, so the number a charge is refused on is the number reported here. The second budget above is spent: blocked is true, and the Finance key's next generation is refused until the limit is raised.
POST /v1/billing/budgets
Set a budget. scope is { "type": "organization" } or { "type": "project" | "member" | "apiKey", "id": "<uuid>" } — the project id, the member's user id, or the key id. One budget per scope: a second answers 409 BUDGET_EXISTS; change the existing one instead. A scope that is not the organization's (a project of another workspace, a user who is not a member, a revoked key) answers 404 BUDGET_SCOPE_NOT_FOUND. alerts are optional, at most 10, each atOlives distinct and at most amountOlives; a body that does not fit answers 400 BUDGET_INVALID naming the field. The answer is 201 with the budget as it stands now.
PATCH /v1/billing/budgets/{budgetId} raises or lowers amountOlives, replaces alerts, or both; name at least one. The scope and the period are fixed — delete and create again to change them. A new limit re-arms the period's alerts, so a threshold already reached notifies again once spend reaches it under the new limit. DELETE /v1/billing/budgets/{budgetId} removes the budget: the scope is no longer capped, its alerts no longer fire, and spend already recorded against it stays in the ledger. It answers { "id": "…", "deleted": true }.
When a budget blocks
A charge a budget refuses is answered 402 before any work starts, and nothing is charged. On POST /v1/generations the code is SPEND_CAP_EXCEEDED — the Generation API's one code for every cap — and the message names the scope and its limit:
Martini's own surfaces (the app's generate, chat, uploads, and set creation) answer BUDGET_EXCEEDED for an organization or key budget, with budget naming the one that refused; a project or member budget keeps the codes the app has always shown (PROJECT_SPEND_CAP_EXCEEDED, MEMBER_CAP_EXCEEDED) with the same budget detail.
Either way, GET /v1/billing/budgets shows which budget is blocked. Agent work inside a workflow run is admitted the same way, operation by operation, against the organization, project, and member budgets. Alerts never block: an alerts threshold emails the organization's admins once per period when settled spend reaches it, and the limit itself is what refuses.
Errors
Errors are JSON { "error": "<copy for a human>", "code": "<STABLE_CODE>" }. Branch on code; the copy may change. The balance accepts every key kind, so it never answers RUN_KEY_NOT_ALLOWED or ORGANIZATION_REQUIRED; no billing route answers GENERATION_API_DISABLED: like the tool routes, they do not check the organization's generation-API grant — the reporting routes check the Billing API's own.
| HTTP status | Code | When |
|---|---|---|
401 | INVALID_API_KEY | Missing, malformed, or unknown key. |
401 | API_KEY_REVOKED | The key was revoked. |
401 | API_KEY_EXPIRED | The key passed its expiry; CLI keys expire after 90 days — run martini login again. |
403 | API_KEY_DEFAULTS_MISSING | The key lost its default project. Contact support. |
403 | API_KEY_PERMISSION_MISSING | The key does not hold the permission the route needs: billing:read for the reads, billing:write for the budget writes. The body names the required permission, the granted set, and the settingsUrl where a key with it is created. Reporting routes only. |
403 | RUN_KEY_NOT_ALLOWED | A workflow run key called a reporting route. A run reads its own budget from the balance and nothing else. |
403 | BILLING_API_NOT_ENABLED | The organization has no Billing API grant. Reporting routes only; request access as described above. |
400 | UNSUPPORTED_API_VERSION | Unknown X-Martini-API-Version header. |
400 | INVALID_REQUEST | Balance only: a query parameter is not accepted, or organizationId is not a UUID. |
400 | INVALID_QUERY | Reporting routes: a parameter is not accepted or out of range, from is not before to, the range exceeds 366 days, modelId names no model, or a costs page covers too many ledger rows. The message names the parameter. |
400 | INVALID_CURSOR | A cursor this server did not issue, or one outside the range. Start again without one. |
400 | BUDGET_INVALID | A budget body does not fit: a negative or non-numeric amountOlives, a period other than lifetime or month, more than 10 alerts, an alert above the limit or repeated, a channel other than email, or a PATCH naming no field. The message names the field. |
404 | ORGANIZATION_NOT_FOUND | organizationId names an organization the key’s user is not a member of, or an organization key named an organization other than its own. |
404 | WORKFLOW_RUN_NOT_FOUND | A run key whose run no longer exists. |
404 | INVOICE_NOT_FOUND | The id is not one of the organization’s invoices, or a draft has no PDF yet. |
404 | BUDGET_NOT_FOUND | The id is not one of the organization’s budgets — also the answer to deleting one twice. |
404 | BUDGET_SCOPE_NOT_FOUND | The scope of a new budget is not the organization’s: a project of another workspace, a user who is not a member, or a revoked key. |
409 | BUDGET_EXISTS | The scope already has a budget. Change it with PATCH; GET /v1/billing/budgets lists it. |
429 | RATE_LIMITED | The key spent its allowance on /v1/billing. Wait the Retry-After seconds; the X-RateLimit-* headers on every answer say how close you are. |
503 | BALANCE_UNAVAILABLE | The billing system could not be read and no fresh value was cached. Wait the Retry-After seconds (10) and call again; a stale balance is never served. |
503 | BILLING_API_ACCESS_UNAVAILABLE | The organization’s grant could not be checked. Wait the Retry-After seconds (10) and call again. |
Using the client
@martini-film/client 0.11.0 and later (JavaScript, TypeScript) and martini-client 0.4.0 and later (Python) expose the balance as martini.billing.balance(). Both return the wire object typed on kind; the Python client adds covers(olives) on the organization form, which is true for an unlimited or billing-disabled organization. The reporting routes are account(), pricing(), costs(), transactions(), invoices(), and invoiceDownload() (invoice_download() in Python) on the same billing namespace, in the client release that ships with them; list parameters take arrays, instants take an ISO string or a Date (an aware datetime in Python). Budgets are budgets(), budget(id), createBudget(), updateBudget(), and deleteBudget() (create_budget(), update_budget(), delete_budget() in Python, which take the scope as scope_type= and scope_id=).
For any other language, the complete /v1 surface is described by an OpenAPI 3.1 document at api.martini.film/v1/openapi.json (no authentication); the Billing tag carries every route on this page, each stamped with the permission it requires in x-martini-permission.
Not yet available
The rest of the Billing API is designed and lands behind the same enterprise grant. Until a route ships it is absent, not a placeholder: calling it answers 404.
- Alert channels beyond
email(a webhook, Slack); thechannelslist on an alert is where they will appear.