Create a Product from an HSX program
product.instruments.compose
Check the HSX program, save it as the first Build with its price fixed, and set up the Product. A browser session also gets a start key, shown once. Any other caller makes keys with the CLI: hyperscale key create --product <productId>. Callers: the founder's CLI login, or an agent token with blueprints:write, or a personal access token with blueprints:write.
POST /v1/products/instruments-compose
Principal user_session. Idempotency required. Environment sandbox.
Input
| Field | In | Type | Description |
|---|---|---|---|
X-Hyperscale-Environment | header | sandbox | live | |
Idempotency-Key required | header | string | |
X-Hyperscale-Rehearsal | header | "true" | `true` runs the request through admission and execution, keeps nothing, and answers OperationRehearsal with 200 in place of the operation's own output. Needs a signed-in caller. Identity operations, such as sign-in, sessions, team members and agent grants, refuse it with invalid_request. |
tenantId required | body | string | Organization tenant ID |
name required | body | string | |
program required | body | object | |
partyBindings required | body | object | |
expectedPlanDigest | body | string | planDigest from the reviewed composer.plan. The transaction recomputes the plan and refuses with plan_stale, returning the new plan, when it differs. |
catalogRevisionId | body | string | Immutable tenant catalog revision pinned into a Product build |
extensionIds | body | booking | cards | collections | commerce | escrow | financing | lending | marketplace | money | obligations | purchase | rental | reporting | wallet[] | Catalog extensions the program resolves from. Provenance only, recorded on the Build. |
metadata | body | object | |
icon | body | object | |
label | body | string | The founder's name for this version, such as Dispute window 14 days |
note | body | string | What changed in this version and why |
Request sample
curl --request POST 'https://hyperscale0.ai/v1/products/instruments-compose' \
--header "Authorization: Bearer $HYPERSCALE_API_KEY" \
--header 'X-Hyperscale-Environment: sandbox' \
--header "Idempotency-Key: $(uuidgen)" \
--header 'Content-Type: application/json' \
--data-raw '{"tenantId":"ten_sandbox_example0001","name":"Sandbox account","program":{"source":"x"},"partyBindings":{}}'Errors
| Code | HTTP | Cause |
|---|---|---|
agent_approval_denied | 403 | A human denied the agent's proposed operation. |
agent_approval_required | 202 | An agent proposed a protected live operation. |
agent_reason_required | 400 | An agent sent a change without saying why, or the reason is shorter than 8 or longer than 280 characters. |
agent_result_already_delivered | 409 | A caller already claimed this approval's one-time result. |
csrf_token_invalid | 403 | A browser session mutation did not carry a current Hyperscale CSRF token. |
environment_mismatch | 403 | The session was issued for sandbox or live and the request targets the other environment. |
idempotency_conflict | 409 | The request reused an Idempotency-Key for a different logical mutation. |
idempotency_required | 400 | The operation is a mutation and Hyperscale requires a replay key before executing it. |
internal_error | 500 | Hyperscale hit an unexpected server-side condition and hid internal implementation details from this audience. |
invalid_request | 400 | The request does not match the API reference. |
missing_required_permission | 403 | The signed-in member's current role does not grant the permission required for this operation. |
missing_required_scope | 403 | The credential is valid but does not grant one or more operation scopes. |
not_found | 404 | No Hyperscale route matched the requested path and method. |
rate_limited | 429 | The credential sent more than its limit in the current 60-second window: 600 requests per API key or session, 1,200 per IP address. Every response carries `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` (Unix seconds). |
request_body_too_large | 413 | The JSON request body exceeded Hyperscale's accepted payload size. |
resource_not_found | 404 | The requested resource does not exist in the credential's visible scope. |
state_conflict | 409 | The operation conflicts with the current durable state of the resource. |
state_constraint_failed | 422 | The request is syntactically valid but violates a business-state constraint. |
tenant_mismatch | 403 | The request mixed tenant-owned identifiers across tenant boundaries. |
tenant_not_found | 404 | The credential resolved to a tenant Hyperscale cannot find. |
unauthorized | 403 | The request did not include a valid Hyperscale bearer credential for this environment. |
agent_token_expired | 401 | The agent token is real, but its grant ended. A grant lasts 30 days, or 7 days unused. `details.expiresAt` holds the date in ISO 8601 and `details.agentName` names the agent. |
agent_token_replaced | 401 | The founder ran `hyperscale agent setup` again for this agent, which replaced this token with a newer one. `details.agentName` names the agent and `details.replacedAt` holds the date. |
capability_not_available_in_sandbox | 400 | The commercial catalog marks the requested capability as unavailable in sandbox. |
capability_not_configured | 400 | This deployment lacks something the request depends on. The requested capability is not in the commercial catalog, identity email delivery is off (`details.capability` is `identity_email`), the deployment names no organization verification provider (`details.capability` is `organization_verification` and `details.configKey` names the setting), or a named provider has no live adapter (`details.provider` names it). |
catalog_revision_conflict | 409 | The stored catalog revision named by the request no longer matches the digests admitted when it was published. |
cross_tenant_transfer_forbidden | 409 | An account on this transfer belongs to another tenant, or it has no tenant and is not a platform account. `details.accountId` names it. No intent or ledger effect was created. |
currency_mismatch | 400 | The currency on the request does not match the settlement currency of the account or transfer it targets. Hyperscale never converts between currencies, so the two must be identical. |
device_login_expired | 401 | The bearer is the founder's own device login from `hyperscale auth login`, not an agent token. Its access token lasts an hour and the CLI refreshes it; the login itself ends after 30 days, or 7 days unused. `details.expiresAt` holds when it ended in ISO 8601. |
empty_composition | 400 | The Product has no enabled sandbox capabilities, so a key for it would admit no operations. No key was created. |
instrument_instance_not_found | 404 | No record of this instrument matches the `instanceId` in this Product, company and environment. `details.instrumentId` and `details.instrumentInstanceId` name what was looked up. |
insufficient_balance | 409 | The source (debit) account's available balance is below the transfer amount, so the ledger refused the transfer. No money moved. The transfer was not created and its charge/transfer rows are recorded as failed. |
invalid_credentials | 401 | Hyperscale could not accept the account or credential request. Signup, login, and bearer authentication use one 401 for every case they reject, so the response exposes no credential-specific reason or registered-email verdict. The single exception is a key that is real but homed in the other environment: because the presented secret already matched a stored key, the message names the mismatch instead of leaving a working credential looking unknown. A bearer that matched nothing gets text that fits its prefix (an agent token, a refresh token, a personal access token, a session or no Hyperscale token at all), which says only what the caller sent, never whether a credential like it exists. |
ledger_transfer_rejected | 409 | The ledger refused the transfer for a reason other than insufficient balance. No money moved. `details` carries `transferId`, the debit-side `accountId`, `amount` and `currency`. The ledger's own reason stays in the server log. |
ledger_unavailable | 503 | The ledger could not be reached, did not answer in time, or returned incomplete evidence for the accounts this request reads. `details.reason` is `request_timeout` when the call timed out. |
operation_environment_invalid | 400 | The operation does not run in the requested environment, or the request environment does not match the credential's environment. The message names the operation and the environment it refused. Sandbox funding, the sandbox clock and sandbox platform firing are sandbox-only. Product promotion is live-only. |
operation_not_finalized | 409 | The operation in `details.operationId` has started but has no final receipt. It is still running, or its outcome is unknown after a failure past the point where it may have committed. `details.error` carries the underlying failure when there is one. |
plan_stale | 409 | The plan the mutation recomputed differs from the reviewed one: the current Build, the program's changes, the price, the capabilities or the toolchain moved. details carries both plans. |
product_build_refuted | 409 | The Build's conservation certificate refuted at least one property: an action's moves do not balance, a state or hold strands money, a clock has no deadline owner, or a division names no rounding rule. details.refuted names each property with its counterexample, and details.certificate carries the whole certificate. |
product_capability_already_enabled | 409 | Creating the Product tried to enable a capability that already has an enabled row on that Product, so no Product was created. |
product_context_forbidden | 400 | `account.create` received a Product for a role that is not Product-scoped. The Product comes from `productId`, or from the API key's Product when `productId` is absent. |
product_context_mismatch | 400 | The request names a Product that does not own the resource it acts on. A transfer's `productId` differs from the source account's Product, or `sandbox.clock.advance` names a Product other than the API key's Product. |
product_context_owner_mismatch | 400 | `account.create` named a Product owner (`owner.type` is `product`) whose `owner.id` differs from the account's Product. |
product_context_required | 400 | The account role is Product-scoped (`customer_balance`, `customer_investment`, `product_pool`, `product_credit`, `product_revenue` and others), but the request or the stored account carries no Product. |
product_not_found | 404 | No Product matches the `productId` in this tenant and environment, or a resource the request names belongs to a different Product than the API key's. A missing, foreign or ambiguous resource binding also reports this code. |
product_scope_required | 400 | The API key carries no Product binding, so Hyperscale cannot decide which Product the request reads. On hosted MCP, the capability query did not match exactly one Product. |
product_scope_unsupported | 400 | A Product API key requested a resource that has no Product-scoped read model, so Hyperscale cannot limit the read to the key's Product. The message names the resource. |
product_status_forbidden | 403 | The Product is not active. Account operations need an `active` Product. Promotion refuses when the sandbox Product or its live counterpart is `disabled` or `closed`. |
sandbox_environment_required | 400 | This operation runs only in sandbox. That covers Product creation and composition, enabling capabilities, composition-scoped API keys, rehearsal Products and provider webhook simulation. |
session_expired | 401 | The session behind this request has ended. A console session ends 30 days after sign-in, or after 12 hours with no request. An admin or customer session ends 24 hours after sign-in, or after 15 minutes with no request. A personal access token ends at the `expiresAt` set when it was created. |
session_required | 401 | This operation needs a person signed in through the browser or the `hyperscale` CLI. A Product API key cannot call it. The request carried no session, or its token matched no live session. |
session_revoked | 401 | The session or personal access token behind this request was ended before its expiry. Sign-out, `user.session.revoke`, a password reset, a role change, removal from the team, user suspension and `personal_access_token.revoke` all end sessions. |
tenant_status_forbidden | 409 | The company is suspended or closed, so every credential bound to it is refused, reads included. `details.tenantId` names the company. |