convert_sandbox

Convert a sandbox to a customer

act entity tenant-private since 2026-10-01

Turns a public sandbox into a paying customer's organization, after the deal is signed. The sandbox keeps its id and becomes a customer org in an account (an existing one, or a new one named here), on a plan, with a first admin who is invited by magic link or passkey. Its test data and its test API key stay in test mode, and the key stops expiring; live mode starts empty. The account and org steps are done at once and the rest of the onboarding (sign-in organization, invite, entitlements, welcome mail) finishes as for a provisioned account; get_onboarding_status reports it. Accepts the sandbox's org id or its test twin's. Send an Idempotency-Key header. Staff who provision accounts only.

Contract

shape
entity
authority
act
evidence
none
subscription
false
permission
any one of: staff.provision (the staff role staff_provision held in the Internal org, tradehq_role.staff_role); a deactivated caller is refused first
idempotency
keyed
emits
sandbox.converted, account.created
MCP tool
convert_sandbox
decision_set
approve, reject, edit, request_info
REST
POST https://api.containertradehq.com/v1/cap/convert_sandbox

Through MCP this call returns an approval request first; see Approvals.

Idempotency: keyed by the caller's Idempotency-Key, which is required: the same key and inputs replay the first result, the same key with other inputs is 409 idempotency_key_reused

Inputs

nametyperequiredvalidation
org_idorg_idrequiredtradehq-org-id; 1 to 20 characters
The sandbox: its org, or its test twin, answered the same.
account_idaccount_idoptionaltradehq-account-id
An existing account the customer org joins. Send this or account_name, not both.
account_nametextoptional1 to 200 characters
A new account to make for the customer: the commercial relationship's name. Send this or account_id, not both.
org_nametextrequired1 to 120 characters
The customer org's name, the legal entity's. Its test twin becomes "{org_name} (test)".
admin_emailtextrequired3 to 254 characters
The first admin's email address. They are invited, and sign in with it by magic link or passkey.
admin_nametextoptional1 to 200 characters
The first admin's name, for the welcome mail.
planone_ofoptionalone of base
The plan the customer org is on: base (the one tier today, and the default).

Response

  • org_id · string · The customer org: the sandbox's own id, unchanged, now of type customer.
  • twin_org_id · string · Its test twin, which kept the sandbox's data and test key.
  • account_id · string · The account the customer org is in.
  • account_created · boolean · Whether this call made the account.
  • provisioning_id · string · The onboarding's ledger row, which get_onboarding_status reads.
  • plan · string · The plan the customer org is on.
  • key_id · string · The sandbox's test API key, kept, which no longer expires. Never the secret.
  • converted_at · string · When the sandbox was converted.
Full JSON Schema
{
  "type": "object",
  "properties": {
    "org_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 20,
      "pattern": "^[A-Za-z0-9_-]{1,20}$",
      "format": "tradehq-org-id",
      "description": "The customer org: the sandbox's own id, unchanged, now of type customer.",
      "x-data-class": "operational"
    },
    "twin_org_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 20,
      "pattern": "^[A-Za-z0-9_-]{1,20}$",
      "format": "tradehq-org-id",
      "description": "Its test twin, which kept the sandbox's data and test key.",
      "x-data-class": "operational"
    },
    "account_id": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
      "format": "tradehq-account-id",
      "description": "The account the customer org is in.",
      "x-data-class": "operational"
    },
    "account_created": {
      "type": "boolean",
      "description": "Whether this call made the account.",
      "x-data-class": "operational"
    },
    "provisioning_id": {
      "type": "string",
      "pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$",
      "format": "uuid",
      "description": "The onboarding's ledger row, which get_onboarding_status reads.",
      "x-data-class": "operational"
    },
    "plan": {
      "type": "string",
      "enum": [
        "base"
      ],
      "description": "The plan the customer org is on.",
      "x-data-class": "operational"
    },
    "key_id": {
      "type": "string",
      "description": "The sandbox's test API key, kept, which no longer expires. Never the secret.",
      "x-data-class": "operational"
    },
    "converted_at": {
      "type": "string",
      "format": "date-time",
      "description": "When the sandbox was converted.",
      "x-data-class": "operational"
    }
  },
  "required": [
    "org_id",
    "twin_org_id",
    "account_id",
    "account_created",
    "provisioning_id",
    "plan",
    "key_id",
    "converted_at"
  ],
  "additionalProperties": false
}

Refusals

  • not_a_sandbox · 409 · the org is not a public sandbox, so there is nothing to convert
  • sandbox_expired · 409 · the sandbox is past its expiry, or purged, and is being deleted
  • sandbox_already_converted · 409 · the sandbox was already converted, under another key; nothing was changed
  • account_choice · 422 · the call names both an existing account and a new one, or neither, or an account that does not exist
  • admin_email_invalid · 422 · the first admin's address is not an email address a person signs in with
  • idempotency_key_reused · 409 · this Idempotency-Key already converted a sandbox with other inputs
  • admin_already_provisioned · 409 · the existing account already has a provisioning for that admin email
  • retry_key_mismatch · 409 · the Idempotency-Key was already used with a different request
  • retry_key_in_flight · 409 · a request with this Idempotency-Key is still running
  • retry_key_lost · 409 · another request took over this Idempotency-Key first; nothing was applied

The platform refusals apply to every call: version, authentication, authority, validation and rate limits, each in the same envelope.

Surfaces

DesktopF
MobileD5
APIF
MCPF*
VoiceD5
PushF
OneBoxN
OutboundN
  • Mobile: degraded: composition
  • MCP: approval required
  • Voice: degraded: composition
  • OneBox: no document originates a sandbox
  • Outbound: crosses to no counterparty

Events

Related

list_orgs