Join a sandbox Product through a join link
customer.join
Redeems a join link: creates a sandbox test customer from the first name, activates its enrollment in the link's Product, opens its customer_balance account, funds it with the link's test money, and signs it in. The session is bound to that one customer and Product, and ends no later than the link does. An unknown token is not found, an expired link is refused as expired, and a link that has admitted maxJoins people is refused as full. Each call makes a new customer; the same person joining twice is two customers. The session token is a one-time secret surfaced only in this response.
POST /v1/customer-identity/join
Principal public. 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. |
token required | body | string | The secret in a join link's URL path, after /j/. Whoever holds it can join the link's Product as a sandbox customer until the link expires or fills. |
firstName required | body | string | The only thing a guest gives. It becomes the display name. |
Request sample
curl --request POST 'https://hyperscale0.ai/v1/customer-identity/join' \
--header "Authorization: Bearer $HYPERSCALE_API_KEY" \
--header 'X-Hyperscale-Environment: sandbox' \
--header "Idempotency-Key: $(uuidgen)" \
--header 'Content-Type: application/json' \
--data-raw '{"token":"demo_join_token_0000000000000001","firstName":"Aisha"}'Errors
| Code | HTTP | Cause |
|---|---|---|
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. |
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_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. |
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. |
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. |
join_link_expired | 410 | The join link's expiry has passed, so it admits no one new. |
join_link_full | 409 | The join link has admitted as many people as its `maxJoins` allows. |
join_link_not_found | 404 | No sandbox join link matches the token. Hyperscale stores only the token's SHA-256, so a lost link cannot be recovered. |
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_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. |
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. |