A sandbox product is a private executable flow owned by exactly one project: a named input/output contract plus a graph of typed nodes, authored in the console's studio without writing code. Its nodes call the products already assigned to that project through the same governed admission and charging path as direct consumption.
A sandbox product is not a published product: it never enters the
organization or global catalogue, and (unless it is chat-compatible) never
appears in /v1/models. Creating, saving or running one publishes nothing.
/v1/sandbox/{id}/invoke/v1/sandbox/{id}/mcpContracts#
The contract is a JSON Schema subset: an object whose properties are typed
string, number, boolean or object, plus a required list — no nesting,
no combinators, no $ref. Image and audio references are object properties
with format: "image" or "audio". A select field is a string property with
an enum of 1 to 100 unique options; a value outside the list is refused with
422 input_type before any charge, and an empty string counts as not supplied.
A select may carry format: "voice", "aspect_ratio", "resolution" or
"duration"; the format is an annotation naming what the options are and
changes nothing about how a value is validated.
Every read and write requires explicit project consumption authority
(project.invoke — a Project Owner or Consumer grant). Owning-team membership
and organization administration do not reach a sandbox product, and another
project of the same organization receives 404, not 403.
Revisions and activation#
Saving always writes a new revision; a saved revision is never mutated, so an
edit cannot change a run already under way. Saves may be marked draft to retain
incomplete wiring, and carry a baseRevision for optimistic concurrency — a
stale base is refused with 409 revision_conflict.
Activation is separate from saving: it re-validates the whole graph, builds an immutable package (graph, schemas, content digest, runner version, dependency manifest of assigned product ids) and deploys it through the runtime adapter. A failed activation changes nothing — the previously active revision stays active.
HTTP invocation#
curl https://api.aigridapp.com/v1/sandbox/sbx-…/invoke \
-H "Authorization: Bearer $AIGRID_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Accept: application/x-ndjson" \
-d '{"input": {"question": "What does this error mean?"}}'- The project is the key's bound project; the sandbox product must belong to
it, and a selector (
X-AIGrid-Projectand aliases) may only restate it (403 project_mismatchotherwise,404for another project's product). Idempotency-Keyis required here (8–128 characters, unlike chat where it is optional). Repeating a key replays the same run and its charges once.- The body is
{"input": {…}, "revision": n?}. Externally, only the actively deployed revision runs — naming any other is409 revision_not_active; omitrevisionto use the active one. - The answer is the execution record: status, typed output, node results, the
child call ids and
chargedMicro. AFailedexecution is still returned, with the charges already incurred.
An image answers as an image. A plain bearer call that negotiates no content type
— a quick curl — gets the picture itself when the run succeeded and the single output
value is a stored image: the asset's own MIME type, Cache-Control: private, no-store,
X-Content-Type-Options: nosniff and range support. Everything else still answers with
the execution record — Accept: application/json, a streamed NDJSON run, a console
execute, a failed run, or any output that is not exactly one image. The run has already
settled, so there is no second charge either way.
Progress over NDJSON. Send Accept: application/x-ndjson to receive
admitted, node and complete events as the graph executes — completed node
records carry durationMs and, for provider calls, remoteMs. Refusals before
admission remain normal HTTP error responses. A disconnected client never strands
the run: it finishes server-side and settles.
MCP interface#
MCP is opt-in per sandbox product and disabled by default (enabled in the studio or via the console API). When enabled:
/v1/sandbox/{id}/mcpspeaks the stateless Streamable HTTP transport;GET/DELETEanswer405after authorization, as that transport requires.- It authenticates the same bearer keys and the same project authority.
- It exposes one tool,
invoke_product, whose input/output schema comes from the active immutable revision's contract. - Every tool call requires the
Idempotency-Keyheader; a retry with the same key replays the same run and child charges. - Output includes structured values plus native image/audio content where the contract produces them.
MCP does not introduce another identity or wallet — it is the same execution path over a different protocol.
Charging#
There is no aggregate parent fee. Each paid node (a product.chat or media
node) goes through the same admission, reservation, policy and settlement path
as a direct call, produces its own usage record, and charges the project
wallet. An execution's chargedMicro is exactly the sum of its child charges.
Child calls are idempotent per execution and node, so a resumed or replayed run
never charges a completed node twice. If funds run out mid-flow the execution
fails and reports the charges already incurred.
Media nodes#
Administrators can publish image and speech products with a declared capability descriptor (kind, aspect ratios, voices, speed, reference-image support) and a fixed per-request price. Sandbox media nodes call them through the platform's provider connection using OpenRouter's image and speech wire shapes; generated bytes are retained as private assets of the project. Media-only products are invoked through sandbox nodes, not through chat completions — the same reservation, settlement and audit path applies per node.
Bounds#
| Limit | Value |
|---|---|
| Nodes / edges per flow | 50 / 200 |
| Paid product calls per flow | 8 |
| Contract fields | 20 |
| Value size per port | 64 KB |
| Output size per execution | 256 KB |
| Execution time | 5 minutes |
Node results persist after each node, so a restarted server resumes from completed work rather than recharging it.
Next#
- Authentication — the keys these endpoints take
- Errors — the refusal codes above, in one table