# Grida Gateway > Public API reference · contract revision `2026-10-12.m1.2`. > Use authenticated discovery for currently enabled models and operations, > exact input schemas, limits and price revisions. Availability and tariffs can > change independently of this protocol reference. Grida Gateway provides text, image, video and specialized AI operations using an organization's Grida AI credits. Use an organization API key from your server. Manage credits, payment methods, automatic top-up, keys and usage at https://console.grida.co. Open AI Gateway without an organization-specific URL: https://console.grida.co/_/ai-gateway. Sign in and choose an organization. If you need to create one in Grida, revisit this link afterward. Gateway onboarding and API key management remain in the Console. For organization billing, use https://console.grida.co/_/billing. No Pro subscription or customer-owned provider key is required. API origin: `https://gateway.grida.ai`. OpenAI SDK base URL: `https://gateway.grida.ai/v1`. The website landing page is https://grida.co/ai/gateway; `GET /` returns 307 with that fixed `Location` (no query forwarding). Only the index redirects there. APIs do not redirect to the website. `GET /healthz` returns `200 {"status":"ok"}` when the service is healthy and may return 503 when unavailable. It is not an inference/provider test. ## Contract files This page is the canonical integration guide. The accompanying JSON Schema 2020-12 documents define exact data shapes and operation-specific inputs/results: - https://gateway.grida.ai/schemas/api.json — discovery, jobs, billing and errors. - https://gateway.grida.ai/schemas/text.json — supported text requests. - https://gateway.grida.ai/schemas/operations.json — image/video family schemas and specialized operation catalogue. For offline integration, use the matching copies supplied alongside this document: `api.schema.json`, `text.schema.json`, `operations.json`. Public schema URLs also serve as stable schema identifiers. Obtain current model availability, exact tariffs and limits from authenticated discovery. `GET /llms.txt` and the three `/schemas/*` URLs are public without authentication. All `/v1/*` routes require an API key. Runtime discovery lists only enabled operations with qualified bindings and tariffs, never planned or disabled entries. Read discovery again when choosing an operation; a schema snapshot is not an availability guarantee. Already accepted requests retain their admitted contract and tariff when the catalogue changes. ## Authentication and access Send `Authorization: Bearer `. Keys begin `sk-gg-v1-`; treat the entire secret as opaque and case-sensitive. Create keys in Console, copy once, and keep them on a trusted server. Never embed an organization secret in a browser or mobile app. The API key determines the organization; request bodies cannot select another one. Scopes are `text` and `media`: - `text`: Chat Completions and Responses. - `media`: image generation/editing, video/specialized submission and job retrieval. - Any valid key may read model and endpoint discovery. A valid `media` key can retrieve jobs belonging to its organization, including jobs created by another key in that organization. Treat this as organization-wide result access. A different organization receives 404. Keys cannot use private Console management APIs or read unrelated organization financial activity. Keys may expire or use Never. Revocation/expiry stops authentication immediately. Rotation replaces the secret on the same key identity. Accepted work may finish and be charged after revocation, expiry, rotation or a disconnected client. Use another authorized key from the same organization to retrieve an accepted job. Previously issued provider URLs are separate access-bearing links: revoking a GG key does not necessarily revoke those links. Organization and key rate/concurrency limits apply together. A key spending cap includes settled charges and active authorizations; it cannot bypass organization credit availability. Configure these controls in Console. ## Routes and identifiers | Method | Path | Purpose | | ------ | ----------------------------- | ------------------------------------------------------------------- | | GET | `/v1/models` | Enabled text/image/video models, exact schemas, limits and tariffs. | | POST | `/v1/chat/completions` | OpenAI-compatible Chat Completions subset. | | POST | `/v1/responses` | Stateless OpenAI-compatible Responses subset. | | POST | `/v1/images/generations` | Image generation JSON; synchronous base64 response. | | POST | `/v1/images/edits` | Multipart image editing; synchronous base64 response. | | POST | `/v1/videos` | Video generation JSON; returns a durable job. | | GET | `/v1/endpoints` | Enabled specialized 3D/rig operations. | | GET | `/v1/endpoints/{endpoint_id}` | One specialized operation's schemas, limits and tariff. | | POST | `/v1/endpoints/{endpoint_id}` | Submit specialized JSON; returns a job. | | GET | `/v1/jobs/{job_id}` | Combined video/specialized execution, result and charging state. | Model IDs retain publisher/model spelling, such as `openai/gpt-5.5` or `bfl/flux-2-max`. Select text/image/video models in the request's `model` field. There are no model names in these submission paths. Specialized endpoint IDs are separate opaque single path segments, for example `three-d.tripo.h3.1.text`. Copy them from discovery; use `encodeURIComponent(id)` for paths. Endpoint IDs contain only letters, digits, `.`, `_` and `-`, never `/`. Lists return `{"object":"list","revision":"…","data":[…]}` without pagination. A model has standard OpenAI `id`, `object:"model"`, `created`, `owned_by`, plus `gg.interfaces`, `capabilities`, `limits`, `interface_limits`, `request_schemas` and `pricing`. Interfaces are `chat.completions`, `responses`, `images.generations`, `images.edits` and `videos`. The request schema keys match its supported interfaces. Media models also publish `result_schemas`; these validate image responses or video `job.result`. Nontext models do not have token limits. Interface limits supply request sizes and the image synchronous-wait limit (at most 600 seconds); capabilities are not substitutes for exact schemas. The offline operation bundle's `x-gg-operations` entries include `operation_id`, `model_id`, `path`, `content_type`, `request_view`, `request_schema`, `result_schema` and `response_mode`. Choose the model and route; image editing uses its own route, video selects its image variant when `image` is present. Match exactly one request schema. `operation_id` identifies that variant for tariffs/job attribution; it is not another image/video URL. Only specialized entries have an `endpoint_id`. An endpoint detail includes `input_schema`, `result_schema`, `limits`, `pricing` and `delivery`. Its direct body has no `input` wrapper, `model` override or provider selector. Model/endpoint discovery embeds absolute schema references or complete bundled definitions. Unknown response fields may be added; clients ignore them. Unknown request fields are rejected. An optional `availability_ends_on` is a UTC date: no new work is accepted on or after it under that entry. Accepted work still reconciles. The selected Veo 3.1/fast/lite bindings end on `2026-10-22`; check discovery for reviewed replacements, without assuming permanent access or an automatic model change. ## Text compatibility GG supports one completion/response per request, ordinary JSON responses and SSE. Feature support is model-specific: inspect `gg.interfaces`, `gg.capabilities` and `gg.limits`. A model is not automatically enabled for both text interfaces. Chat Completions supports message/tool history, function tools and tool choice, text and image input, JSON object/schema output, sampling and output limits within the supported request schema. Responses supports explicit caller-provided input history, function calls/results, text/image input and structured text output. Preserve returned reasoning/tool-history items when the selected model needs them; do not reduce a tool response to its visible text before the next call. Only client-executed function tools are supported: GG returns calls; your program executes the tool and sends its result in a subsequent request. No built-in web search, hosted code execution, file search or remote MCP tool is promised. Each subsequent generation is a new potentially billable request. Responses is stateless: `store` may be omitted or false; `store:true`, `previous_response_id`, hosted conversations and `background:true` are rejected. Supply the needed history yourself. There is no response retrieval/replay API. Text output is not saved as a conversation or a response cache. Image input, where supported, uses inline base64 data URLs or provider-accessible HTTPS URLs. GG fetches bounded remote images, validates their bytes and forwards those exact bytes for inference and reservation sizing. GG and the provider must be able to consume the input without your custom authorization headers. Do not send private-network URLs. Supported inline types are defined in the text schema; inline media is processed transiently, not saved for replay. Send uncompressed UTF-8 JSON (identity content encoding). The overall text JSON body limit is 20 MiB (20,971,520 bytes), including base64. There are at most 1,024 input/message items, 128 function definitions and 32 image parts per request; a model may impose lower limits. Its published output-token default and maximum apply when an explicit limit is omitted or supplied. Unsupported fields/features fail validation before paid dispatch, rather than being silently ignored. Audio/file inputs, embeddings, real-time sessions, batches and n>1 text completions are not part of this contract. Images and video use the separate interfaces below; image output through Chat/Responses is not promised. Use `reasoning_effort` on Chat Completions and `reasoning: {effort: ...}` on Responses when discovery enables reasoning for the selected model. Unsupported values fail validation. Output includes only declared compatible fields; provider routing metadata and wholesale cost fields are not forwarded. ### OpenAI SDK example ```ts import OpenAI from "openai"; import { randomUUID } from "node:crypto"; const client = new OpenAI({ baseURL: "https://gateway.grida.ai/v1", apiKey: process.env.GRIDA_GATEWAY_API_KEY, maxRetries: 0, // control retries with the logical request identity below }); const logicalRequest = randomUUID(); // retain across attempts for this one request const completion = await client.chat.completions.create( { model: "openai/gpt-5.5", // select an enabled compatible model at launch messages: [{ role: "user", content: "Say hello in one sentence." }], max_completion_tokens: 128, }, { headers: { "Idempotency-Key": logicalRequest } }, ); console.log(completion.choices[0]?.message.content); const response = await client.responses.create( { model: "openai/gpt-5.5", // requires this model's responses interface input: "Say hello in one sentence.", max_output_tokens: 128, store: false, }, { headers: { "Idempotency-Key": randomUUID() } }, ); console.log(response.output_text); ``` These are two distinct generations and authorizations. Keys, model activation and numeric prices will be supplied at release. No GG-specific SDK is required. ### Streaming Set `stream:true` and use the SDK's stream iterator or an SSE parser. HTTP 200 only means the stream opened, not that generation or billing succeeded. - Chat uses `chat.completion.chunk` data events, one choice at index 0, followed by `data: [DONE]`. Tool arguments and text arrive as deltas. Request `stream_options:{include_usage:true}` for the terminal usage chunk; its `choices` array is empty. Do not treat missing usage as zero. - Responses uses typed events with `event:` and a JSON `type`, including text and function-argument deltas and the terminal `response.completed`, `response.incomplete` or `response.failed`. It does not use Chat's `[DONE]` marker. - Once streaming begins, errors cannot change the HTTP status. Inspect error and terminal events. EOF without a valid terminal event is an interrupted outcome. Preserve partial output, request ID and idempotency key; do not automatically generate a replacement under a new identity. Native token counts are reported in OpenAI-compatible usage fields. Cache and reasoning subtotals are not necessarily additional tokens: do not add them twice. Charge inspection is in Console; token counts alone are not a USD price quote. Disconnecting does not cancel provider work or guarantee free usage. ## Images Use a `media` key. Generation is JSON at `/v1/images/generations`; editing is multipart at `/v1/images/edits`. Both select `model` and return **200** with `{"created":,"data":[{"b64_json":"…","media_type":"image/png"}]}`. `b64_json` is raw canonical base64, not a data URL. Decode/save it in your own application. MIME is verified, not guessed from a file extension. Additional usage fields may be present; their absence does not mean zero cost. Supported controls, counts and defaults come from the selected model schema. `response_format` may be omitted or `b64_json`; `url` is unsupported. Image responses are synchronous, with no public image job, stream, response replay or GG download URL. No mask, file ID, arbitrary provider options or automatic paid fan-out is supported. `size` can be a pixel pair or a documented model-specific preset/tier; `aspect_ratio`, `seed`, `quality` and `background` are only accepted where declared. Unsupported fields are rejected, not ignored. Recraft is text-to-image only; Seedream edits accept at most ten files. For editing, send one file part named `image`, or ordered repeated `image[]` parts. Do not mix the names. Send each scalar (`model`, `prompt`, `n`, etc.) once; integers use JSON integer syntax and other values remain UTF-8 strings. Unknown or duplicate scalar parts and extra file parts fail. Do not set the multipart Content-Type yourself when the SDK constructs its boundary. The edit JSON Schema describes the **parser-normalized view**, not an alternate JSON upload API: `image` is an ordered array of derived `sha256`, `byte_length`, `media_type`, `width`, `height` descriptors. GG derives them from actual files; you send the files, not descriptors. JSON edits, HTTPS edit references, base64 edit parts and masks are not included in this initial subset. The schema's file count, byte/MIME/dimension bounds apply to actual decoded input before paid dispatch. Filenames and client Content-Type claims are not trusted. Generation JSON is bounded to 20 MiB; edit multipart to 320 MiB with at most 1 MiB of text fields. Each source image is at most 20,000,000 bytes, 8,192 pixels per axis and 16,777,216 decoded pixels, or the model's lower bound. Outputs are bounded to 20,000,000 bytes each and 200,000,000 aggregate decoded bytes; the JSON response is at most 280 MiB. These are GG resource limits, not upstream output size guarantees. Delivery exceeding a bound fails without silently resizing or truncating output; evidenced provider work can still be charged. GG processes bytes transiently and does not retain them on disk, in jobs or in replay responses. A client disconnect, service restart or synchronous-wait expiry may lose delivery after paid work was accepted. Recovery can reconcile original provider work via its submission identity; it cannot recreate lost input bytes or regenerate the image for free. A wait expiry returns 503 `temporarily_unavailable` with the original request ID; it is not cancellation. Use Console to inspect the outcome/charge. Retry only with the original identity; accepted duplicates return 409 `already_submitted`, never cached image bytes. ### OpenAI image examples ```ts import OpenAI from "openai"; import { createReadStream } from "node:fs"; import { randomUUID } from "node:crypto"; const client = new OpenAI({ baseURL: "https://gateway.grida.ai/v1", apiKey: process.env.GRIDA_GATEWAY_API_KEY, maxRetries: 0, }); const generated = await client.images.generate( { model: "bfl/flux-2-max", // select an enabled model at launch prompt: "A red ceramic teapot on a plain white background.", n: 1, size: "1024x1024", response_format: "b64_json", }, { headers: { "Idempotency-Key": randomUUID() } }, ); const bytes = Buffer.from(generated.data![0].b64_json!, "base64"); // Persist bytes only in your own application if you need them. const edited = await client.images.edit( { model: "bfl/flux-2-max", // requires images.edits support prompt: "Make the teapot blue.", image: createReadStream("source.png"), n: 1, size: "1024x1024", }, { headers: { "Idempotency-Key": randomUUID() } }, ); ``` These are separate paid requests. Retain each idempotency key before sending and reuse it only for that exact request; the inline UUIDs illustrate separate requests, not a retry loop. Source files remain caller-owned. ## Video and specialized operations Video uses `POST /v1/videos` with `model`, `prompt`, and supported common fields `image`, `aspectRatio`, `duration`, `seed`, `generateAudio` and `resolution`. Their spelling follows AI SDK vocabulary; support remains model-specific. In JSON, `image` is a caller-hosted HTTPS URL. Local images can instead use the multipart transport below. `resolution` is a declared WIDTHxHEIGHT pixel pair (initially Veo Lite). For models exposing only resolution tiers, use the strictly bounded `providerOptions:{gg:{resolutionTier:"720p"}}` instead. Do not infer pixel outputs from a tier or send both selectors. Only that documented GG option is accepted; it does not select providers or forward arbitrary upstream controls. A fixed-audio profile can accept `generateAudio:true` only when declared; `false` is rejected unless the schema exposes a real toggle. `fps`, `frameImages`, `inputReferences`, multiple videos, callbacks and unsupported seed/aspect controls are rejected. No silent input truncation, option dropping or model substitution. 3D/rigging uses `POST /v1/endpoints/{endpoint_id}` and its exact direct JSON body. There is no universal image/video/3D request shape. Audio, Replicate and customer BYOK are excluded. The text bundle lists 14 proposed text model IDs; the media bundle lists 21 image variants, 12 video variants and 15 specialized operations (including model-independent rig eligibility), covering 26 nontext model IDs. These are contract counts, not currently enabled production offerings. Send JSON, a `media` key and an `Idempotency-Key` for video/specialized work. Compressed bodies are unsupported; send identity encoding. Async JSON is at most 1 MiB or the operation's lower bound. Referenced resources use HTTPS, never private-network targets, base64 or data URLs. Keep URL inputs readable through execution. Expiry before provider consumption can cause a billable failure; a preflight read is not proof a mutable URL remains unchanged. The accepted operation cannot change its inputs during recovery. ### Local files: direct multipart submission The same video and specialized routes accept `multipart/form-data` for local files. Include exactly one **`input`** text part containing the ordinary request JSON, omitting fields supplied as files. Use the selected operation's source field names for file parts: - `image`: image-to-video or single-image 3D. - `images.front`, `images.left`, `images.back`, `images.right`: 3D multiview. - `mesh`: portable GLB for rigging or rig eligibility. Only fields supported by the selected operation are accepted. The JSON and file parts cannot both supply the same field. Other views may remain HTTPS sources in JSON. At least one file is required; URL-only submissions should use JSON. An image becomes the schema's source reference (video URL or specialized `{url,media_type}` object); this internal normalization does not create an upload resource or reusable GG URL. The schema's required fields, dimensions, MIME, pixel and operation-specific byte limits still apply to the actual bytes. Discovery exposes multipart limits, and the operation index lists supported file fields as `x-gg-multipart-file-fields`. The body is at most **81 MiB**, the JSON part **1 MiB**, with at most **4 files**. Each image is at most **20,000,000 bytes**, each GLB **60,000,000 bytes**; selected operations can impose lower bounds. Use PNG/JPEG/WebP as allowed by the operation, or a self-contained portable GLB2. GG validates actual content; filenames have no semantic meaning. Conflicting specific MIME declarations, repeated parts and encoded parts are rejected. Generic `application/octet-stream` is accepted when the bytes establish an allowed type. Let your HTTP client set the boundary. ```sh curl https://gateway.grida.ai/v1/videos \ -H "Authorization: Bearer $GG_API_KEY" \ -H "Idempotency-Key: $REQUEST_KEY" \ -F 'input={"model":"alibaba/wan-3.0","prompt":"A slow orbit","duration":2,"providerOptions":{"gg":{"resolutionTier":"480p"}}}' \ -F 'image=@source.png;type=image/png' ``` Multipart submits the validated bytes directly to the provider before returning 202, without retaining a GG file or durable input payload. Generation is still asynchronous. A lost acknowledgement or timeout is **not** permission to submit again with a new key: repeat the identical submission with the original key or retrieve the original job. Fingerprints include file content, not filenames or part ordering. Changes to file bytes under the same key produce 409. A new submission returns **202** and a job, after validation and a durable spending authorization. An identical replay returns **200** with the same job ID and its current state. Both include `Location: /v1/jobs/{job_id}` and `X-Request-ID`. Acceptance does not guarantee provider completion. Processing/reconciliation continues even when your program disconnects or stops polling. `GET /v1/jobs/{job_id}` returns **200** for an existing authorized job, including failed and uncertain jobs. `operation_id` identifies the selected variant and `interface` is `videos` or `endpoints`. `result` validates against that variant's result schema when present. Videos and 3D models use provider links in the declared structure. Structured operations, such as rig eligibility, return structured JSON. There is no separate result route, public queue API, priority control, cancel endpoint or customer callback registration. | `status` | Meaning | | ----------- | ----------------------------------------------------------------------------------------------------------------- | | `accepted` | GG recorded the request and spending authorization; execution has not been observed running. | | `running` | Execution is in progress, including a provider's internal waiting period. | | `unknown` | GG cannot establish the original submission/execution outcome. This is not safe to resubmit. | | `succeeded` | The original execution is confirmed successful. | | `failed` | The original execution is confirmed unsuccessful or was not dispatched. Inspect the error and billing separately. | `succeeded` and `failed` are terminal execution states. Result retrieval and billing can remain pending after either. Continue GET polling while `result_status` is `pending` if you still need the result; save any available partial result even if execution failed. `unknown` can later resolve to running or a terminal state; it does not cause a fresh provider generation automatically. A local waiting deadline is not a cancellation or an execution-state transition. `result_status` is independent: `pending`, `available`, `unavailable` or `expired`. `result` is null unless `result_status` is `available`. Available means GG can return the structured result document; it does not guarantee that every provider URL is still reachable. `unavailable` means no deliverable result was obtained. `expired` means GG's result-document retrieval window ended. Result documents are retrievable for up to 24 hours after terminal execution; `result_expires_at` states the actual end. Save results promptly. Individual provider references have their own `expires_at`; null means unknown, not permanent. A provider may revoke a link earlier. A reference's `media_type` may also be null when the provider does not supply or guarantee it; do not guess the MIME type. GG neither mirrors files nor repairs expired links by generating paid work again. GG may refresh a link for the original task where supported, without regenerating the output or extending its own window. Content-free job status remains retrievable for at least 90 days after terminal execution. Later retrieval may return 410 `job_expired`; charges remain in Console. ### Submit, poll and keep the identity ```js import { randomUUID } from "node:crypto"; const base = "https://gateway.grida.ai"; const headers = { Authorization: `Bearer ${process.env.GRIDA_GATEWAY_API_KEY}`, "Content-Type": "application/json", }; const model = "google/veo-3.1-lite"; // check enabled discovery and retirement date const submission = { idempotencyKey: randomUUID(), input: { model, prompt: "The camera slowly approaches a red teapot.", duration: 8, resolution: "1280x720", aspectRatio: "16:9", }, }; // Save submission securely before POST if your program must survive a restart. // Keep signed input URLs/prompts out of ordinary logs. const accepted = await fetch(`${base}/v1/videos`, { method: "POST", headers: { ...headers, "Idempotency-Key": submission.idempotencyKey }, body: JSON.stringify(submission.input), }); if (!accepted.ok) { const failure = await accepted.json(); throw new Error(`${accepted.status}: ${failure.error.code}`); } let job = await accepted.json(); // Save job.id and job.request_id. A new idempotency key would mean new paid work. const stopAt = Date.now() + 10 * 60_000; while ( (!["succeeded", "failed"].includes(job.status) || job.result_status === "pending") && Date.now() < stopAt ) { if (job.status === "unknown") break; // resume observing this job later await new Promise((resolve) => setTimeout(resolve, 2_000)); const polled = await fetch(`${base}/v1/jobs/${encodeURIComponent(job.id)}`, { headers, }); if (!polled.ok) throw new Error(`Polling failed: ${polled.status}; retain ${job.id}`); job = await polled.json(); } if (job.result_status === "available") { // Download/save provider output yourself according to your storage policy. // Video job.result.videos contains one provider output reference. console.log("Video references received:", job.result.videos.length); } else { console.log("Resume or inspect:", job.id, job.status, job.billing.status); } ``` This bounded wait ends without canceling work. Poll again using the saved job ID. A temporary polling failure needs another GET, not another generation. On an uncertain POST response, retry that exact submission with its original idempotency key, body and pricing headers. See the retry rules below. ## AI SDK integration The target GG provider adapter follows AI SDK's image/video model interfaces, not Vercel's native `/v4/ai` protocol. `@ai-sdk/gateway` is not a GG base-URL adapter. The GG adapter and its pinned supported SDK version will be supplied and tested in M1; there is no published GG package to install yet. The HTTP contracts above are sufficient to build a client now. - Images: SDK `n`, `size`, `seed` map to the same HTTP fields; `aspectRatio` maps to `aspect_ratio` when the model supports it. An image prompt with source bytes becomes multipart editing; masks are rejected. Model-specific preset/tier `size`, `quality` and `background` use the adapter's documented `providerOptions.gg` fields. Conflicting standard/option values fail. Returned base64/MIME becomes SDK generated-file bytes in the consumer, never a GG cache. - Video: SDK text/image prompt maps to `prompt`/HTTPS `image`; common controls keep their spelling. Named tiers use `providerOptions.gg.resolutionTier`. Durable start returns an opaque operation containing `job_id`, `request_id` and `model`, without prompts, URLs or secrets. Status maps available video to completed (including partial results with separate execution-error metadata), accepted/running/unknown/result-pending to pending, and failed without output or unavailable/expired results to an error without regenerating. `generateVideo` can poll and download the link in the consumer; GET status itself does not download in GG. - Keep a stable `Idempotency-Key` across SDK retries. No automatic paid fan-out: validate `n` against the full model request before invoking the SDK, use one call per logical request, and disable SDK retries (`maxRetries:0`) unless they preserve that identity. SDK `maxImagesPerCall` splitting creates separate requests; it is not a way around GG's per-request count/authorization limits. - Abort signals and client wait limits stop local waiting, not paid provider execution. Persist a video job ID and resume GET polling after a restart. An image retry cannot recover image bytes that GG never stored. ## Credits, prices and authorization One organization's Grida AI credit balance pays for both GG requests and Grida product usage, even while those products execute through different systems. Concurrent requests reserve available credit from that same pool. A reservation is an authorization, not a final charge, and cannot be spent by another request. Payment processing or an automatic top-up in progress is not available credit. Credit is USD-denominated: **$1 = 1,000 mills**. All GG money and measured decimal quantities use exact decimal strings with up to 18 fractional digits; never parse money with a binary floating-point number for authoritative calculations. Usage is not rounded up to a whole mill per request. Manual and automatic top-ups charge the same fee. For `C` dollars of spendable credit, the payment before applicable tax is: `ceil_to_USD_cent(C × 1.095 + 0.30)` Thus $100 credit costs $109.80 before tax; $500 credit costs $547.80. The fee is not spendable credit. Automatic top-up requires explicit setup/consent in Console, not a Pro plan; a failed request does not authorize a card charge by itself. Execution consumes credit at the admitted catalogue tariff for the selected upstream's regular, non-promotional offering. The top-up fee is separate; it is not added again to individual requests. Inspect discovery's `pricing`: - `revision` pins the entire admitted price schedule, effective defaults, quantity measurement and rounding rules. A provider default change cannot silently alter that revision. The published input schema declares defaults that affect billing. - Each component specifies a `meter`, `unit`, `rate_mills`, `per_quantity` and provider quantity-rounding rule. Quantities such as additional megapixels are separate named meters with explicit measurement rules, not an assumed flat image price. `when` is a JSON Schema evaluated against `{ "input": }` to select applicable components. - Optional `quantity_range` selects a component using its original measured meter: inclusive `min`, exclusive `max` (null means no upper boundary). This is how context-length token tariffs change tiers; the tier is based on total input tokens before separating cache reads. Missing quantities are not zero. - For each selected component: round its measured quantity only as explicitly specified, divide by `per_quantity`, multiply by `rate_mills`, then sum. Retain exact decimal precision; round the final charge upward only at 18 fractional mills. - `measurement` explains how provider evidence supplies quantities and which subtotals are disjoint. A cached/reasoning subtotal is never charged twice. Some final quantities are knowable only after provider execution. A client need not reproduce provider tokenization or build a pricing evaluator to limit spend. Optional request headers provide an explicit authorization boundary: | Header | Behavior | | --------------------- | --------------------------------------------------------------------------------------------------- | | `X-GG-Max-Cost-Mills` | Nonnegative exact decimal spending ceiling for this request, inclusive of all execution components. | | `X-GG-Price-Revision` | Require exactly this published price revision; a changed revision returns 409 before new admission. | GG calculates a conservative reservation from the complete effective request. If it exceeds your supplied ceiling, GG returns 402 `cost_limit_exceeded` without paid dispatch. The error includes exact decimal `computed_authorization_mills` and `requested_max_cost_mills`; it does not silently reduce generation parameters. With no ceiling, you authorize GG's computed reservation, still limited by available credit/key budget. With no revision header, the current published revision applies at admission. GPT Image 2, 2.5 Flare and 2.5 Sunburst use an estimated reservation because their provider bills variable tokens without a guaranteed total price ceiling. Discovery marks these models with `gg.reservation_policy.basis: "estimate"` and identifies the active policy. `gg-gpt-image-estimate-v2` forecasts image output tokens using OpenAI's model-specific quality/dimension calculator, with twice the estimated output cost, a prompt allowance and 100 mills per output for unmodelled text/reasoning. Reference inputs retain an allowance of 250 mills per started 1,048,576 decoded pixels per reference per output. These allowances are GG safety estimates, not provider tokenization rules or guaranteed maxima. `n` multiplies the complete per-output allowance; authorization rounds upward to whole mills. Explicit dimensions and quality affect the hold. Automatic dimensions use the calculator's envelope over the model's allowed pixel area; automatic quality uses its highest supported setting. Omitted fields use the same effective defaults sent upstream: GPT Image 2 text generation uses `landscape_4_3`, while the existing adaptive defaults on other variants remain adaptive. For a prompt of eight UTF-8 bytes, one 1024×1024/high output without references reserves 523 mills ($0.523) on GPT Image 2 or 207 mills ($0.207) on GPT Image 2.5. These are holds, not fixed charges. Earlier `gg-gpt-image-estimate-v1` admissions retain their original authorizations; inspect discovery rather than assuming a policy from this document's revision. Unused credit is released after evidenced settlement. You can run multiple image requests at once while their combined reservations fit your available credit and your API key and organization limits. There is no additional one-image-request organization cap. Unknown requests continue to reserve credit and count toward applicable concurrency limits. Configured rate/concurrency limits can return 429 `concurrency_limit_exceeded` with `Retry-After` in seconds. Follow any explicit idempotency retry instructions in the error; never infer permission for new work from a timeout or 5xx. An unexpected cost above the reservation leaves billing pending for review and restricts new organization spending. It never silently exceeds your authorized charge or releases an unresolved hold. Other models retain bounded reservations. Accepted text responses/streams and synchronous image responses expose `X-GG-Price-Revision` and `X-GG-Reserved-Mills` alongside `X-Request-ID`. Jobs expose `billing.price_revision` and `billing.authorized_mills`. The authorization is fixed for that accepted request; new prices affect new requests. Remaining holds are shown by `billing.reserved_mills`; they become zero after settlement. **If the provider bills for your authorized work, you pay the admitted tariff.** This can include partial output, refusals, failed generations, completed work after disconnect or a result you did not download. Proven nonbillable rejections cost zero. An unknown outcome does not establish free usage. A job's `billing.status` is `reserved`, `pending` or `settled`. `charged_mills` is null until settled; zero is a known zero charge. `refunded_mills` records any linked correction separately. Settlement may complete after execution. GG does not charge beyond the admitted maximum or make you pay for GG-caused duplicate execution. A provider reversal of that specific usage produces a linked credit. When billing evidence is missing, GG preserves the original execution identity and hold while reconciling it. It does not guess a charge, release funds merely because a timer expired, or regenerate the work. An unresolved case enters review; contact support with the request ID if it remains pending after 24 hours. Financial reconciliation can outlive result availability. Console is the source for text/image charges, holds and organization usage. There is no public request-history listing. ### Read the actual charge A reservation is **not a charge**. Never book `X-GG-Reserved-Mills` as final spend. Accepted execution responses include `X-GG-Billing-URL`, a same-origin relative path such as `/v1/requests/req_example/billing`. GET it with a valid API key for the same organization and the original request's `text` or `media` scope: ```json { "object": "request_billing", "request_id": "req_example", "billing": { "status": "settled", "currency": "USD", "unit": "mill", "price_revision": "2026-10-12.m1", "authorized_mills": "1000", "reserved_mills": "0", "charged_mills": "185", "refunded_mills": "0", "usage": null } } ``` This is an illustrative response, not a price quotation. Its schema is `api.json#/$defs/RequestBilling`. The read contains no prompt, result, provider metadata or wholesale cost, and does not dispatch work. Poll with backoff while `billing.status` is `reserved` or `pending`; a null charge is unknown, not zero. Financial readback remains available after result expiry. Synchronous JSON responses also include `X-GG-Billing-Status` and, when already settled, `X-GG-Charged-Mills`. Streaming headers precede settlement: use the lookup. Vision reservations use inspected image dimensions and model-specific bounds, not base64 length. Remote image bytes are fetched with bounded public-HTTPS validation and the same bytes are sent to the provider. The total expanded request still must fit the text body limit. Models without a qualified image formula retain a conservative context bound (currently GPT-5.5 Pro). Wan 3.0 reserves the selected duration/resolution plus one second of rounding allowance. Text framing and output-token allowances remain conservative. Actual charges always follow evidenced usage at the admitted catalogue price. ## Idempotency and retries Use one `Idempotency-Key` per logical generation: 1–128 printable ASCII characters without spaces. It is **required** for image/video/specialized submissions and recommended for text. Without it, every text call can create new billable work. Its scope is organization + stable API-key identity + HTTP method/path + value. The same value on another key or route is a different logical request. Rotation preserves the key identity; a newly created key does not. Do not reuse values. GG retains accepted request fingerprints for the lifetime of that key identity, including after result expiry. Expiring content never authorizes re-execution. For JSON, the fingerprint includes the canonical body and supplied cost/revision headers. Image-edit multipart uses normalized scalar fields and ordered file SHA-256 hashes, byte lengths and MIME, plus those headers; image array order matters. Video/3D multipart fingerprints normalized JSON and content-digest references at their named source fields. Part ordering and filenames/boundaries do not change video/3D identity; changed bytes or source-field assignments do. Duplicate JSON object keys are rejected. Object-key ordering, JSON whitespace and equivalent JSON number spellings do not change it; array order, values, omitted versus explicit default fields, and pricing headers do. Reuse exactly the same body and headers when retrying. A changed fingerprint returns 409 `idempotency_conflict` without another dispatch. - Duplicate video/specialized submission: 200, same job and current state. If its metadata has expired, return 410 `job_expired` with the original request ID; never create a replacement automatically. Duplicate lookup precedes current catalogue/price checks, so disabling an operation or changing its tariff does not make an accepted retry execute again. - Duplicate text or image: 409 `already_submitted` with the original request ID, because GG does not store text or image bytes for response replay. Use Console to inspect its charge. - Auth/validation/credit rejection before acceptance creates no billable request. Once corrected, a request can be admitted. An error message alone is not proof that an interrupted network request failed before acceptance. - Network failure, 5xx, lost POST response or interrupted SSE: preserve the original identity and retry only with it. Do not switch keys or invent another idempotency value to get around an uncertain outcome. - GET polling/discovery is read-only. Retry transient failures with bounded exponential backoff and jitter; honor `Retry-After` when supplied. Start polling no faster than once per second. A wait timeout does not cancel execution. M1 does not promise exactly-once execution by every upstream. GG will not blindly repeat an uncertain paid attempt. A confirmed nonbillable rejection may be retried internally within the same admitted tariff, capability and maximum authorization. No customer-controlled fallback/routing or provider-specific header passthrough is part of this contract. ## Errors Non-streaming errors use this shape: ```json { "error": { "message": "Explanation", "type": "invalid_request_error", "code": "invalid_request", "param": "messages", "request_id": "req_example" } } ``` Branch on HTTP status and `error.code`, not message wording. Additional response fields and new specific codes may appear. Preserve `X-Request-ID` and any `error.request_id`; neither is a credential. For an accepted logical request the request ID remains stable across replay, jobs, usage and financial reconciliation. Refusals before admission carry an attempt ID for support; they have no billing record. `error.param` identifies the invalid field (dot-separated for nested fields), or `body` when no more specific location is available. | Status | Codes / action | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | `invalid_json`, `invalid_request`, `invalid_idempotency_key`, `unsupported_feature`, `unsupported_parameter`, `invalid_limit`: correct the input before retrying. | | 401 | `invalid_api_key`: invalid, expired or revoked key. | | 402 | `insufficient_credit`, `cost_limit_exceeded`: resolve available credit or the explicit cap. | | 403 | `insufficient_scope`, `organization_inactive`: correct access/organization state. | | 404 | `model_not_found`, `endpoint_not_found`, `job_not_found`: unavailable ID or inaccessible resource. | | 409 | `idempotency_conflict`, `already_submitted`, `price_revision_changed`: follow the specific contract; do not force a new paid identity. | | 410 | `job_expired`: content-free public job metadata is no longer retrievable; retain its request reference. | | 413 | `request_too_large`: reduce JSON/multipart/inline media within published bounds. | | 415 | `unsupported_media_type`: send JSON or multipart for the selected route, and supported media types. | | 429 | `rate_limit_exceeded`, `concurrency_limit_exceeded`, `key_budget_exceeded`: back off or resolve the configured limit. | | 502 | `upstream_rejected`, `upstream_unavailable`, `invalid_upstream_response`: execution/billing may be uncertain; preserve the identity. | | 503 | `temporarily_unavailable`, `settlement_pending`: do not assume free work or resubmit under a fresh identity. | A job execution failure appears inside the 200 job document's `error`; its billing can still be pending or settled with a charge. Streaming errors appear in the stream. There is no guarantee that every temporary rejection has `Retry-After`. ## Content custody and privacy GG does **not claim zero data retention (ZDR)**. Synchronous text is processed in memory without a durable prompt/completion archive, conversation store or replay cache. Synchronous images use bounded transient input/output memory with no disk spooling, media archive or byte replay. Ordinary logs, traces and usage exports exclude prompts, output content, tool arguments/results, inline media, keys and signed URLs. JSON async execution retains encrypted inputs only while needed for dispatch/recovery, for at most 24 hours. It never stores media bytes inside that payload. Direct multipart submission keeps its inputs in memory only; recovery uses the original provider reference and never replays uploaded bytes. Necessary structured results/provider links are encrypted and expire at `result_expires_at`; cleanup removes them from live storage. Financial and security metadata remains independently of that content. Encrypted copies may persist in backup/restore history; expiry is not a promise of immediate backup erasure. Providers receive the input needed to execute and apply their own retention policies. GG result expiry does not delete provider media or prove upstream no-retention. Do not treat a link as private merely because it was returned by an authenticated job API. Keep access-bearing URLs secret and copy wanted outputs to storage you control before they expire. GG offers no reusable upload library, output cache, custom download URL, media persistence or provider-deletion guarantee. Multipart inputs are transient inference transport, not an upload service.