Skip to content

Orders

Account Scope Required, please include X-Auth-Account header with your Account ID. Index/show require the billing:read OAuth scope; create / update / destroy are not available via OAuth (there is intentionally no billing:write scope) — they require a session or API-key credential.

Ordering flows through the cart/billing system and charges the account's default saved payment method off-session. There is no Stripe checkout redirect.

Two order types

POST /api/orders provisions WordPress sites. POST /api/orders/domain registers or transfers a domain through the same cart/billing flow. Both always return a 202 cart envelope and are OAuth-blocked (require a session or API-key credential).

List all orders for an account

GET /api/orders

Params (optional)
  • page: Integer | page number (default: 1)
  • per_page: Integer | records per page (default: 50, max: 100)
Returned Params
  • orders: Array
    • id: String | uuid
    • status: String
    • created_at: DateTime
    • updated_at: DateTime
    • location: String | location short_name, or null for orders with no location (e.g. domain-registration orders)
    • account: Object
      • id: String
      • name: String

View an Order

GET /api/orders/:id

Poll this endpoint until status reaches a terminal value (completed, failed, cancelled).

Returned Params
  • order: Object
    • id: String | uuid
    • status: String
    • created_at: DateTime
    • updated_at: DateTime
    • location: String | location short_name, or null for orders with no location (e.g. domain-registration orders)
  • tasks: Array
    • id: Integer
    • status: String
  • site: Object | present when the order provisioned a site
    • id: String
    • name: String
    • domain: String
    • variant: String
    • location: String
    • ssh_data: Object | null when the site has no SFTP credentials
      • ipaddr: String
      • username: String
      • password: String
      • port: Integer
  • subscription: Object | present when the order has a backing subscription
    • id: Integer | the subscription's internal numeric id — see the warning below
    • status: String
    • bucket_type: String
    • bucket_year: Integer
    • bucket_month: Integer
    • stripe_id: String
  • domain: Object | present when the order registered or transferred a domain
    • name: String
    • action: String | register or transfer
    • registration: Object | the domain registration record — see Domain Registration. null during the async window, until the registrar call succeeds
    • provisioning_status: String | present only while registration is null; mirrors the order status
  • payment: Object
    • status: String | pending, processing, succeeded, awaiting_authentication, or failed — see the note below
    • method_type: String | the resolved Stripe payment-method type, or null. Commonly card or sepa_debit, but this is an open set — delayed bank debits alone include us_bank_account, acss_debit, bacs_debit, au_becs_debit and customer_balance, and others such as ideal are possible. Treat any unrecognized value as valid
    • hosted_invoice_url: String | null
    • amount_charged_cents: Integer | null until the Stripe invoice has been mirrored
    • credit_applied_cents: Integer | null until the Stripe invoice has been mirrored
  • account: Object | See accounts#show
  • user: Object | See users#show

subscription.id is not the id /api/subscriptions/:id accepts

The subscription.id returned here is the subscription's internal numeric id, while GET /api/subscriptions/:id looks the subscription up by its guid — the same value that List Subscriptions returns as id. Feeding this id straight into the subscriptions endpoint returns 404 with no explanation. Match the subscription by another field, or list the account's subscriptions and pick it up from there.

payment.status here has five values, not three

On order show, payment.status is derived from the order's own status: it may be pending (nothing written to the payment intent yet), processing (charge in flight), succeeded (fully paid), awaiting_authentication (3DS/SCA challenge parked for recovery — direct the user to hosted_invoice_url), or failed (declined, bounced, or cancelled). The 202 cart envelope returned by POST /api/orders uses a different, three-value enum for the same key — do not reuse one parser for both.


Order a new Site

POST /api/orders

Charges the billing account's default payment method off-session and asynchronously provisions a WordPress site. There is no synchronous success branch — the endpoint always returns 202 Accepted with a polling envelope.

Location and plan names can be found by querying the /api/about endpoint.

Pre-flight gate

Requires the billing account to be ready to charge — a Stripe customer ID, a saved default payment method, and a complete billing contact. Failing the gate returns 400 with code: "no_default_payment_method" and no order is created. Accounts on a billing plan that does not settle through Stripe are invoiced externally and skip this gate entirely, so they never return no_default_payment_method.

Params
  • site: Object (required)
    • name: String | accepted but not used — see the note below
    • variant: String (required) | vanilla, extendify
    • location: String (required) | Location short_name (e.g. "pdx")
    • plan: String (required) | Product short_name (e.g. "basic")
    • term: String (required) | monthly or annual
  • callback: Object | optional outbound notification POSTed when the order's async task completes — see Callbacks
    • authorization: String | full Authorization header value. Example: Bearer 12345
    • url: String | fully qualified URL

You cannot name the site

site.name is accepted by the endpoint and then ignored. The site is named after a domain ordered in the same request; when no domain is ordered, it gets a generated label built from the account's machine name. Read the site's real name back from the order once it has provisioned.

Returned Params (always 202 Accepted)
  • status: String | "accepted"
  • cart: Object
    • token: String
    • status: String | active, processing, checked_out
    • rollup_status: String | pending, processing, completed, ...
    • poll_url: String | absolute URL, e.g. https://your-instance/api/carts/<token>
  • payment: Object
    • status: String | processing, succeeded, awaiting_authentication
    • method_type: String | the resolved Stripe payment-method type, or null. Commonly card or sepa_debit, but this is an open set — delayed bank debits alone include us_bank_account, acss_debit, bacs_debit, au_becs_debit and customer_balance, and others such as ideal are possible. Treat any unrecognized value as valid
    • hosted_invoice_url: String | null
  • orders: Array
    • id: String | uuid
    • status: String | pending
    • poll_url: String | absolute URL, e.g. https://your-instance/api/orders/<id>

Poll cart.poll_url (or each order's poll_url) until the orders materialize and reach a terminal state. orders is empty during the async window — the order id is discovered by polling the cart.

Payment status enum (cart envelope)

In this envelope payment.status has exactly three values: processing (PI in flight / off-session charge), succeeded (finalized), or awaiting_authentication (3DS/SCA required or a recoverable decline — direct the user to hosted_invoice_url). For SEPA, the method confirms as processing and settles in 1–5 business days. GET /api/orders/:id uses a wider five-value enum for the same key.

On a billing plan that does not settle through Stripe the envelope's payment.status stays processing and never reaches succeeded — the terminal signal for those accounts is orders[].status reaching completed.

Errors

Every 4xx carries a machine-readable code alongside errors. Nothing is charged and no order is created.

  • 400 missing_account | the X-Auth-Account header is absent
  • 400 account_cannot_order | the account is not permitted to create orders
  • 400 unknown_variant | variant is not a known variant type
  • 400 unknown_location | location is not a known public location
  • 400 over_capacity | the variant has no inventory at that location — retryable
  • 400 unknown_term | term is not monthly or annual
  • 400 unknown_product | plan is not orderable on the account's billing plan
  • 400 no_price_for_plan | no price for plan at the requested term
  • 400 item_add_failed | the cart rejected the site item
  • 400 no_default_payment_method | billing account not ready to charge
  • 422 cart_pay_failed | the off-session charge could not be initiated

Payment could not be initiated (422)

If the cart is valid but Cart::PayService fails to start the off-session charge, the response is 422 with code: "cart_pay_failed". (A pre-flight failure is 400; a payment-initiation failure is 422.)


Register or Transfer a Domain

POST /api/orders/domain

Registers or transfers a domain through the same cart/billing flow as POST /api/orders, charging the account's default saved payment method off-session, then asynchronously registering (or transferring) the domain and creating a renewal subscription and DNS zone. Always returns 202 Accepted with the shared cart envelope; poll /api/orders/:id (which carries a domain block) until status is terminal.

Not available via OAuth and requires a user-scoped credential. A system Account-bearer API key (no user) returns 401 user_required. Requires the X-Auth-Account header and the domain-registration feature flag — when the flag is off, returns 503 feature_disabled. Trial accounts return 403 trial_account.

Params
  • domain: Object (required)
    • name: String (required) | fully-qualified, e.g. example.com
    • action: String (optional) | register (default) or transfer
    • authcode: String | required for transfers when the TLD requires an authcode
    • contact: Object | inline registrant; required unless contact_source is given
      • first_name: String (required)
      • last_name: String (required)
      • organization: String (optional)
      • street_address: String (required)
      • post_code: String (required)
      • city: String (required)
      • state: String (optional/required per TLD)
      • country: String (required) | 2-letter ISO code
      • email: String (required)
      • phone: String (required) | E.164, e.g. +1.5555555555
      • extra_properties: Object | TLD-specific registry fields (e.g. .it entityType)
    • contact_source: Object | reuse an owned registration's contacts instead of contact
      • registration_id: Integer (required) | an owned DomainRegistration that has an owner contact
    • role_contacts: Object (optional) | admin / billing / tech; each:
      • source: String | same_as_owner (default), existing, or new
      • source_registration_id: Integer | when source is existing
      • first_name, last_name, ... | the same contact fields as above, when source is new
  • callback: Object (optional) | same callback contract as POST /api/orders — see Callbacks
    • authorization: String | full Authorization header value. Example: Bearer 12345
    • url: String | fully qualified URL

Provide a registrant via either an inline contact object or contact_source.registration_id (to reuse the contacts of an owned registration), not both.

Returned Params (always 202 Accepted)

Identical envelope to POST /api/orders:

  • status: String | "accepted"
  • cart: Object | { token, status, rollup_status, poll_url }
  • payment: Object | { status, method_type, hosted_invoice_url }
  • orders: Array | [{ id, status, poll_url }] (empty during the async window)
Errors

Authorization / gating:

  • 401 user_required | system Account-bearer key (no user)
  • 503 feature_disabled | domain registration not enabled
  • 403 trial_account | trial accounts cannot order domains

Validation (400, no charge / no order created):

  • missing_account | the X-Auth-Account header is absent
  • account_cannot_order | the account is not permitted to create orders
  • invalid_action | action not register or transfer
  • invalid_domain | name failed normalization/validation
  • unsupported_tld | no configured TLD for the name
  • pricing_unavailable | no orderable price for the account's billing plan
  • authcode_required | transfer of a TLD that requires an authcode, none supplied
  • incomplete_contact | inline contact missing required fields
  • invalid_contact | inline contact has a bad value (e.g. country not 2 letters)
  • invalid_contact_source | contact_source.registration_id not found or has no owner contact
  • no_default_payment_method | billing account not ready to charge

Processing (422):

  • domain_unavailable | registry says the name is not registerable (register only)
  • domain_availability_unknown | the availability check itself failed, so we cannot say whether the name is registerable — retry (register only)
  • domain_add_failed | cart rejected the domain (e.g. already owned)
  • domain_contact_invalid | registrant contact rejected (body carries violations)
  • domain_contact_needs_augmentation | TLD needs extra contact fields (body carries needs_augmentation)
  • cart_pay_failed | payment could not be initiated (any contacts created for this request are torn down)

domain_unavailable is not the same as domain_availability_unknown

Only domain_unavailable is a statement about the name — the registry said it cannot be registered. domain_availability_unknown means the availability check itself failed, so nothing is known about the name: retry rather than telling the customer it is taken or offering them alternatives.


Cancel an Order

Cancels an in-progress order.

DELETE /api/orders/:id

Returns 202.

PATCH is a no-op

PATCH /api/orders/:id is reserved and returns 400.


Carts

Poll cart-level state during the async window after POST /api/orders, a site plan change, or any other paid endpoint that answers with the same cart envelope.

Account Scope Required (X-Auth-Account). Scope: billing:read. Carts are scoped to your own account; an unknown token returns 404.

GET /api/carts/:token

The cart token comes from the create response (cart.token / cart.poll_url). The response shape mirrors POST /api/orders so you can reuse the same parser:

Returned Params
  • status: String | "accepted"
  • cart: Object
    • token: String
    • status: String
    • rollup_status: String
    • poll_url: String
  • payment: Object
    • status: String
    • method_type: String | the resolved Stripe payment-method type, or null. Commonly card or sepa_debit, but this is an open set — delayed bank debits alone include us_bank_account, acss_debit, bacs_debit, au_becs_debit and customer_balance, and others such as ideal are possible. Treat any unrecognized value as valid
    • hosted_invoice_url: String | null
  • orders: Array
    • id: String | uuid
    • status: String
    • poll_url: String