# Create a checkout and place an order (https://developer.godaddy.com/en/docs/api-users/shopping/guides/purchase)

---
title: Create a checkout and place an order
description: >-
  Create a checkout session with selected products, update it with buyer and
  payment details, complete the checkout, and retrieve the resulting order.
keywords: >-
  checkout session, POST /checkout-sessions, PUT /checkout-sessions, complete
  checkout, GET /orders, line_items, payment instrument,
  shopping.checkout:execute, shopping.order:read, order retrieval
agentNotes:
  permissions:
    - Shopping Checkout
    - Shopping Orders
  scopes:
    - 'shopping.checkout:execute'
    - 'shopping.order:read'
  rateLimit: >-
    Rate-limited per credential. Go to /docs/api-users/rate-limits for current
    values.
  idempotent: false
  destructive: true
  failureRecovery: >-
    Completion charges the selected instrument. Poll GET /orders/:orderId until
    the order becomes visible.
related:
  guides:
    - title: About the Shopping API
      href: /docs/api-users/shopping
    - title: Search the catalog
      href: /docs/api-users/shopping/guides/search-catalog
  concepts:
    - title: Handle errors
      href: /docs/api-users/errors
    - title: Rate limits
      href: /docs/api-users/rate-limits
  apis:
    - title: Shopping Checkout reference
      href: /docs/references/rest/shopping/create-checkout
    - title: Shopping Orders reference
      href: /docs/references/rest/shopping/get-order
---

## Overview

Use this Shopping API v1 workflow to create a checkout, select payment, accept agreements, complete the checkout session to place the order, and retrieve its details.

A checkout session moves through states (`incomplete` → `ready_for_complete` → `completed`) as you add required data. All write operations require `shopping.checkout:execute` scope. Order retrieval requires `shopping.order:read` scope.

Go to [About the Shopping API](https://developer.godaddy.com/docs/api-users/shopping) for required headers and the checkout state machine.

## Prerequisites

The following prerequisites are required before you can create a checkout and place an order:

* at least one variant ID from the product catalog
* a PAT with `shopping.checkout:execute` and `shopping.order:read` scopes
* an eligible saved payment instrument on the GoDaddy account
* `jq` and `uuidgen` for the curl examples

Go to [Search the catalog](https://developer.godaddy.com/docs/api-users/shopping/guides/search-catalog) to get a variant ID.

## Authenticate

These examples use a `$GODADDY_PAT` with `shopping.checkout:execute` and `shopping.order:read` scopes.

Go to [About the Shopping API](https://developer.godaddy.com/docs/api-users/shopping#authentication) for the full list of required scopes.

## Create a checkout session

`POST /v1/shopping/checkout-sessions` creates a checkout session with one or more line items. Pass variant IDs from the catalog search results.

The following procedure creates a checkout session.

* Create a session with a selected variant:

  ```bash tab="curl"
  export CREATE_IDEMPOTENCY_KEY="$(uuidgen)"
  CREATE_RESPONSE="$(curl --fail-with-body --silent --show-error -X POST "https://api.godaddy.com/v1/shopping/checkout-sessions" \
    -H "Authorization: Bearer $GODADDY_PAT" \
    -H "Request-Id: $(uuidgen)" \
    -H "Idempotency-Key: $CREATE_IDEMPOTENCY_KEY" \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg id "$VARIANT_ID" '{context:{currency:"USD"},line_items:[{item:{id:$id},quantity:1}]}')")"
  export SESSION_ID="$(jq -er '.id' <<<"$CREATE_RESPONSE")"
  export LINE_ITEM_ID="$(jq -er '.line_items[0].id' <<<"$CREATE_RESPONSE")"
  export PAYMENT_INSTRUMENT_ID="$(jq -er '.payment.instruments | (map(select(.selected == true))[0] // .[0]).id' <<<"$CREATE_RESPONSE")"
  export PAYMENT_HANDLER_ID="$(jq -er '.payment.instruments | (map(select(.selected == true))[0] // .[0]).handler_id' <<<"$CREATE_RESPONSE")"
  export PAYMENT_INSTRUMENT_TYPE="$(jq -er '.payment.instruments | (map(select(.selected == true))[0] // .[0]).type' <<<"$CREATE_RESPONSE")"
  ```

  ```js tab="Node"
  const checkoutId = crypto.randomUUID();
  const res = await fetch("https://api.godaddy.com/v1/shopping/checkout-sessions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GODADDY_PAT}`,
      "Request-Id": crypto.randomUUID(),
      "Idempotency-Key": checkoutId,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      context: { currency: "USD" },
      line_items: [{ item: { id: process.env.VARIANT_ID }, quantity: 1 }],
    }),
  });
  const checkout = await res.json();
  if (!res.ok || checkout.ucp?.status === "error") throw new Error(checkout.messages?.[0]?.code ?? `HTTP ${res.status}`);
  const sessionId = checkout.id;
  const lineItem = checkout.line_items[0];
  const writableLineItem = {
    id: lineItem.id,
    item: { id: lineItem.item.id },
    quantity: lineItem.quantity,
    ...(lineItem.input ? { input: lineItem.input } : {}),
  };
  const paymentInstrument = checkout.payment?.instruments?.find((item) => item.selected)
    ?? checkout.payment?.instruments?.[0];
  if (!paymentInstrument) throw new Error("No eligible saved payment instrument");
  ```

  ```python tab="Python"
  import os, uuid, requests

  idem_key = str(uuid.uuid4())
  res = requests.post(
      "https://api.godaddy.com/v1/shopping/checkout-sessions",
      headers={
          "Authorization": f"Bearer {os.environ['GODADDY_PAT']}",
          "Request-Id": str(uuid.uuid4()),
          "Idempotency-Key": idem_key,
      },
      json={
          "context": {"currency": "USD"},
          "line_items": [{"item": {"id": os.environ["VARIANT_ID"]}, "quantity": 1}],
      },
  )
  checkout = res.json()
  if not res.ok or checkout.get("ucp", {}).get("status") == "error":
      raise RuntimeError(checkout.get("messages", [{}])[0].get("code", f"HTTP {res.status_code}"))
  session_id = checkout.get("id")
  line_item = checkout["line_items"][0]
  writable_line_item = {
      "id": line_item["id"],
      "item": {"id": line_item["item"]["id"]},
      "quantity": line_item["quantity"],
      **({"input": line_item["input"]} if line_item.get("input") else {}),
  }
  payment_instrument = next(
      (item for item in checkout.get("payment", {}).get("instruments", []) if item.get("selected")),
      next(iter(checkout.get("payment", {}).get("instruments", [])), None),
  )
  if not payment_instrument:
      raise RuntimeError("No eligible saved payment instrument")
  ```

  ```go tab="Go"
  package main

  import (
      "bytes"
      "crypto/rand"
      "encoding/json"
      "fmt"
      "log"
      "net/http"
      "os"
  )

  func requestID() string {
      value := make([]byte, 16)
      if _, err := rand.Read(value); err != nil { log.Fatal(err) }
      value[6], value[8] = (value[6]&0x0f)|0x40, (value[8]&0x3f)|0x80
      return fmt.Sprintf("%x-%x-%x-%x-%x", value[:4], value[4:6], value[6:8], value[8:10], value[10:])
  }

  func main() {
      body, _ := json.Marshal(map[string]any{
          "context": map[string]string{"currency": "USD"},
          "line_items": []map[string]any{
              {"item": map[string]string{"id": os.Getenv("VARIANT_ID")}, "quantity": 1},
          },
      })
      req, _ := http.NewRequest("POST", "https://api.godaddy.com/v1/shopping/checkout-sessions", bytes.NewReader(body))
      req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
      req.Header.Set("Request-Id", requestID())
      req.Header.Set("Idempotency-Key", os.Getenv("CREATE_IDEMPOTENCY_KEY"))
      req.Header.Set("Content-Type", "application/json")

      res, err := http.DefaultClient.Do(req)
      if err != nil { log.Fatal(err) }
      defer res.Body.Close()
      if res.StatusCode != http.StatusCreated { log.Fatalf("create checkout returned %s", res.Status) }

      var checkout map[string]any
      json.NewDecoder(res.Body).Decode(&checkout)
      fmt.Println("session:", checkout["id"], "status:", checkout["status"])
  }
  ```

The response includes an `id` and initial `status`. Save the writable line items and returned payment instruments for later requests.

Go to [Example responses](#example-responses) for trimmed response examples.

Always send a unique `Idempotency-Key` on `POST /checkout-sessions`.

If the request times out, retry the unchanged request with the same key. The server returns the existing session instead of creating a duplicate.

A status of `incomplete` means required fields are missing. A status of `ready_for_complete` means all required fields are present. Check `messages[]` in the response for specific missing-field details.

## Review the checkout

`GET /v1/shopping/checkout-sessions/{id}` returns current line items, totals, saved payment instruments, messages, links, and required agreements.

The following procedure refreshes the checkout before an update or completion.

* Retrieve the checkout and selected payment instrument:

  ```bash tab="curl"
  CHECKOUT_RESPONSE="$(curl --fail-with-body --silent --show-error \
    "https://api.godaddy.com/v1/shopping/checkout-sessions/$SESSION_ID" \
    -H "Authorization: Bearer $GODADDY_PAT" \
    -H "Request-Id: $(uuidgen)")"
  export PAYMENT_INSTRUMENT_ID="$(jq -er '.payment.instruments | (map(select(.selected == true))[0] // .[0]).id' <<<"$CHECKOUT_RESPONSE")"
  export PAYMENT_HANDLER_ID="$(jq -er '.payment.instruments | (map(select(.selected == true))[0] // .[0]).handler_id' <<<"$CHECKOUT_RESPONSE")"
  export PAYMENT_INSTRUMENT_TYPE="$(jq -er '.payment.instruments | (map(select(.selected == true))[0] // .[0]).type' <<<"$CHECKOUT_RESPONSE")"
  ```

  ```js tab="Node"
  const checkoutRes = await fetch(
    `https://api.godaddy.com/v1/shopping/checkout-sessions/${sessionId}`,
    { headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}`, "Request-Id": crypto.randomUUID() } },
  );
  const currentCheckout = await checkoutRes.json();
  if (!checkoutRes.ok || currentCheckout.ucp?.status === "error") {
    throw new Error(currentCheckout.messages?.[0]?.code ?? `HTTP ${checkoutRes.status}`);
  }
  const selectedInstrument = currentCheckout.payment?.instruments?.find((item) => item.selected)
    ?? currentCheckout.payment?.instruments?.[0];
  if (!selectedInstrument) throw new Error("No eligible saved payment instrument");
  ```

  ```python tab="Python"
  checkout_res = requests.get(
      f"https://api.godaddy.com/v1/shopping/checkout-sessions/{session_id}",
      headers={
          "Authorization": f"Bearer {os.environ['GODADDY_PAT']}",
          "Request-Id": str(uuid.uuid4()),
      },
  )
  current_checkout = checkout_res.json()
  if not checkout_res.ok or current_checkout.get("ucp", {}).get("status") == "error":
      raise RuntimeError(current_checkout.get("messages", [{}])[0].get("code", f"HTTP {checkout_res.status_code}"))
  instruments = current_checkout.get("payment", {}).get("instruments", [])
  payment_instrument = next((item for item in instruments if item.get("selected")), instruments[0] if instruments else None)
  if not payment_instrument:
      raise RuntimeError("No eligible saved payment instrument")
  ```

  ```go tab="Go"
  package main

  import (
      "crypto/rand"
      "encoding/json"
      "fmt"
      "log"
      "net/http"
      "os"
  )

  func requestID() string {
      value := make([]byte, 16)
      if _, err := rand.Read(value); err != nil { log.Fatal(err) }
      value[6], value[8] = (value[6]&0x0f)|0x40, (value[8]&0x3f)|0x80
      return fmt.Sprintf("%x-%x-%x-%x-%x", value[:4], value[4:6], value[6:8], value[8:10], value[10:])
  }

  func main() {
      req, _ := http.NewRequest("GET", "https://api.godaddy.com/v1/shopping/checkout-sessions/"+os.Getenv("SESSION_ID"), nil)
      req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
      req.Header.Set("Request-Id", requestID())
      res, err := http.DefaultClient.Do(req)
      if err != nil { log.Fatal(err) }
      defer res.Body.Close()
      if res.StatusCode != http.StatusOK { log.Fatalf("get checkout returned %s", res.Status) }
      var checkout map[string]any
      if err := json.NewDecoder(res.Body).Decode(&checkout); err != nil { log.Fatal(err) }
  }
  ```

Persist the checkout ID, writable line item, and selected instrument for later requests. Preserve the instrument's returned `id`, `handler_id`, and `type`.

The standalone Go examples read these values from `SESSION_ID`, `LINE_ITEM_ID`, `PAYMENT_INSTRUMENT_ID`, `PAYMENT_HANDLER_ID`, and `PAYMENT_INSTRUMENT_TYPE`.

## Update the checkout

`PUT /v1/shopping/checkout-sessions/{id}` replaces the writable checkout state. Use it to select a saved payment instrument and resend all line items.

`PUT` is a full replace, not a merge. Include the complete `line_items[]` array and every product `input` on each update. Omitting a writable field clears its previous value.

Payment instruments must already exist on the GoDaddy account. If none is eligible, add one at [GoDaddy payment methods](https://account.godaddy.com/payment-methods/add-payment) and retrieve the checkout again. Set `BUYER_FIRST_NAME`, `BUYER_LAST_NAME`, `BUYER_EMAIL`, and `BUYER_PHONE` to the buyer's contact values before running the examples.

The following procedure selects a payment instrument.

* Select a payment instrument and resend all line items:

  ```bash tab="curl"
  export UPDATE_IDEMPOTENCY_KEY="$(uuidgen)"
  UPDATE_RESPONSE="$(curl --fail-with-body --silent --show-error -X PUT "https://api.godaddy.com/v1/shopping/checkout-sessions/$SESSION_ID" \
    -H "Authorization: Bearer $GODADDY_PAT" \
    -H "Request-Id: $(uuidgen)" \
    -H "Idempotency-Key: $UPDATE_IDEMPOTENCY_KEY" \
    -H "Content-Type: application/json" \
    -d "$(jq -n \
      --arg line_id "$LINE_ITEM_ID" --arg variant_id "$VARIANT_ID" \
      --arg first "$BUYER_FIRST_NAME" --arg last "$BUYER_LAST_NAME" \
      --arg email "$BUYER_EMAIL" --arg phone "$BUYER_PHONE" \
      --arg instrument_id "$PAYMENT_INSTRUMENT_ID" --arg handler "$PAYMENT_HANDLER_ID" \
      --arg type "$PAYMENT_INSTRUMENT_TYPE" \
      '{context:{currency:"USD"},line_items:[{id:$line_id,item:{id:$variant_id},quantity:1}],buyer:{first_name:$first,last_name:$last,email:$email,phone_number:$phone},payment:{instruments:[{id:$instrument_id,handler_id:$handler,type:$type,selected:true}]}}')")"
  ```

  ```js tab="Node"
  const updateIdempotencyKey = crypto.randomUUID();
  const res = await fetch(
    `https://api.godaddy.com/v1/shopping/checkout-sessions/${sessionId}`,
    {
      method: "PUT",
      headers: {
        Authorization: `Bearer ${process.env.GODADDY_PAT}`,
        "Request-Id": crypto.randomUUID(),
        "Idempotency-Key": updateIdempotencyKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        context: { currency: "USD" },
        line_items: [writableLineItem],
        buyer: {
          first_name: process.env.BUYER_FIRST_NAME,
          last_name: process.env.BUYER_LAST_NAME,
          email: process.env.BUYER_EMAIL,
          phone_number: process.env.BUYER_PHONE,
        },
        payment: {
          instruments: [
            {
              ...selectedInstrument,
              selected: true,
            },
          ],
        },
      }),
    }
  );
  const updated = await res.json();
  if (!res.ok || updated.ucp?.status === "error") throw new Error(updated.messages?.[0]?.code ?? `HTTP ${res.status}`);
  ```

  ```python tab="Python"
  import os, uuid, requests

  idem_key = str(uuid.uuid4())
  res = requests.put(
      f"https://api.godaddy.com/v1/shopping/checkout-sessions/{session_id}",
      headers={
          "Authorization": f"Bearer {os.environ['GODADDY_PAT']}",
          "Request-Id": str(uuid.uuid4()),
          "Idempotency-Key": idem_key,
      },
      json={
          "context": {"currency": "USD"},
          "line_items": [writable_line_item],
          "buyer": {
              "first_name": os.environ["BUYER_FIRST_NAME"],
              "last_name": os.environ["BUYER_LAST_NAME"],
              "email": os.environ["BUYER_EMAIL"],
              "phone_number": os.environ["BUYER_PHONE"],
          },
          "payment": {
              "instruments": [
                  {**payment_instrument, "selected": True}
              ]
          },
      },
  )
  updated = res.json()
  if not res.ok or updated.get("ucp", {}).get("status") == "error":
      raise RuntimeError(updated.get("messages", [{}])[0].get("code", f"HTTP {res.status_code}"))
  ```

  ```go tab="Go"
  package main

  import (
      "bytes"
      "crypto/rand"
      "encoding/json"
      "fmt"
      "log"
      "net/http"
      "os"
  )

  func requestID() string {
      value := make([]byte, 16)
      if _, err := rand.Read(value); err != nil { log.Fatal(err) }
      value[6], value[8] = (value[6]&0x0f)|0x40, (value[8]&0x3f)|0x80
      return fmt.Sprintf("%x-%x-%x-%x-%x", value[:4], value[4:6], value[6:8], value[8:10], value[10:])
  }

  func main() {
      sessionID := os.Getenv("SESSION_ID")
      body, _ := json.Marshal(map[string]any{
          "context": map[string]string{"currency": "USD"},
          "line_items": []map[string]any{
              {"id": os.Getenv("LINE_ITEM_ID"), "item": map[string]string{"id": os.Getenv("VARIANT_ID")}, "quantity": 1},
          },
          "buyer": map[string]string{
              "first_name":   os.Getenv("BUYER_FIRST_NAME"),
              "last_name":    os.Getenv("BUYER_LAST_NAME"),
              "email":        os.Getenv("BUYER_EMAIL"),
              "phone_number": os.Getenv("BUYER_PHONE"),
          },
          "payment": map[string]any{
              "instruments": []map[string]any{
                  {
                      "id":         os.Getenv("PAYMENT_INSTRUMENT_ID"),
                      "handler_id": os.Getenv("PAYMENT_HANDLER_ID"),
                      "type":       os.Getenv("PAYMENT_INSTRUMENT_TYPE"),
                      "selected":   true,
                  },
              },
          },
      })
      req, _ := http.NewRequest("PUT",
          "https://api.godaddy.com/v1/shopping/checkout-sessions/"+sessionID,
          bytes.NewReader(body))
      req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
      req.Header.Set("Request-Id", requestID())
      req.Header.Set("Idempotency-Key", os.Getenv("UPDATE_IDEMPOTENCY_KEY"))
      req.Header.Set("Content-Type", "application/json")

      res, _ := http.DefaultClient.Do(req)
      defer res.Body.Close()
      if res.StatusCode != http.StatusOK { log.Fatalf("update checkout returned %s", res.Status) }

      var updated map[string]any
      json.NewDecoder(res.Body).Decode(&updated)
      fmt.Println("status:", updated["status"])
  }
  ```

After the update, check `status`:

* If `ready_for_complete`, proceed to complete the checkout.
* If `incomplete`, check `messages[]` for remaining required fields.
* If `required_agreements[]` is present, include `consent` when completing.

If an item can't be priced at update time, the server removes it from `line_items[]` and attaches a `messages[]` entry with `code: line_item_removed`. Check for missing items before proceeding.

## Configure products and fulfillment

Include one `line_items[]` entry for each selected variant. Set `quantity` for each entry and preserve its returned line-item `id` on updates.

Some variants expose an `input_schema`. Build `line_items[].input` from that schema and resend the input on every PUT request.

| Data                    | Request location                                                               |
| ----------------------- | ------------------------------------------------------------------------------ |
| Buyer contact           | `buyer.first_name`, `buyer.last_name`, `buyer.email`, and `buyer.phone_number` |
| Product configuration   | `line_items[].input`                                                           |
| Shipping or pickup      | `fulfillment.methods[]` and its selected destination                           |
| Billing address         | `payment.instruments[].billing_address`                                        |
| Saved payment selection | The returned instrument `id`, `handler_id`, `type`, and `selected: true`       |

Do not assume every saved instrument is a card. Preserve the handler and type returned by the checkout.

Go to [Shopping Checkout reference](https://developer.godaddy.com/docs/references/rest/shopping/create-checkout) for the full input, fulfillment, payment, and address schemas.

## Handle required agreements

Before completing, check the checkout response for `required_agreements[]`. Some purchases require the buyer to accept terms first.

Before completing:

* Include each agreement with `required: true` in `consent.agreement_types`.
* Record the buyer's acceptance time in `agreed_at`.

Missing or incomplete consent returns HTTP `400` with `validation_error`.

Example `required_agreements[]` in a checkout response:

```json
{
  "required_agreements": [
    {
      "key": "api_services_purchase_agreement",
      "title": "API Services Purchase Agreement",
      "url": "https://www.godaddy.com/legal/agreements/api-services-purchase-agreement",
      "required": true
    }
  ]
}
```

## Complete the checkout

`POST /v1/shopping/checkout-sessions/{id}/complete` completes a `ready_for_complete` session and creates its order. The request body must include `payment`. Include `consent` if the checkout has `required_agreements[]`.

The following procedure completes the checkout.

* Complete the session:

  ```bash tab="curl"
  export COMPLETE_IDEMPOTENCY_KEY="$(uuidgen)"
  export AGREED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  REQUIRED_AGREEMENTS="$(jq -c '[.required_agreements[]? | select(.required == true) | .key]' <<<"$UPDATE_RESPONSE")"
  COMPLETE_BODY="$(jq -n \
    --arg instrument_id "$PAYMENT_INSTRUMENT_ID" --arg handler "$PAYMENT_HANDLER_ID" \
    --arg type "$PAYMENT_INSTRUMENT_TYPE" --argjson agreements "$REQUIRED_AGREEMENTS" \
    --arg agreed_at "$AGREED_AT" \
    '{payment:{instruments:[{id:$instrument_id,handler_id:$handler,type:$type,selected:true}]}} +
     (if ($agreements | length) > 0 then {consent:{agreement_types:$agreements,agreed_at:$agreed_at}} else {} end)')"
  COMPLETE_RESPONSE="$(curl --fail-with-body --silent --show-error -X POST "https://api.godaddy.com/v1/shopping/checkout-sessions/$SESSION_ID/complete" \
    -H "Authorization: Bearer $GODADDY_PAT" \
    -H "Request-Id: $(uuidgen)" \
    -H "Idempotency-Key: $COMPLETE_IDEMPOTENCY_KEY" \
    -H "Content-Type: application/json" \
    -d "$COMPLETE_BODY")"
  export ORDER_ID="$(jq -er '.order.id' <<<"$COMPLETE_RESPONSE")"
  ```

  ```js tab="Node"
  const completionIdempotencyKey = crypto.randomUUID();
  const requiredAgreementKeys = (updated.required_agreements ?? [])
    .filter((agreement) => agreement.required)
    .map((agreement) => agreement.key);
  const res = await fetch(
    `https://api.godaddy.com/v1/shopping/checkout-sessions/${sessionId}/complete`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.GODADDY_PAT}`,
        "Request-Id": crypto.randomUUID(),
        "Idempotency-Key": completionIdempotencyKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        payment: { instruments: [{ ...selectedInstrument, selected: true }] },
        ...(requiredAgreementKeys.length > 0
          ? { consent: { agreement_types: requiredAgreementKeys, agreed_at: new Date().toISOString() } }
          : {}),
      }),
    }
  );
  const result = await res.json();
  if (!res.ok || result.ucp?.status === "error") throw new Error(result.messages?.[0]?.code ?? `HTTP ${res.status}`);
  const orderId = result.order?.id;
  const receiptUrl = result.order?.permalink_url;
  ```

  ```python tab="Python"
  import os, uuid, requests
  from datetime import datetime, timezone

  completion_idempotency_key = str(uuid.uuid4())
  required_agreement_keys = [
      agreement["key"]
      for agreement in updated.get("required_agreements", [])
      if agreement.get("required") is True
  ]
  complete_body = {
      "payment": {"instruments": [{**payment_instrument, "selected": True}]},
  }
  if required_agreement_keys:
      complete_body["consent"] = {
          "agreement_types": required_agreement_keys,
          "agreed_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
      }
  res = requests.post(
      f"https://api.godaddy.com/v1/shopping/checkout-sessions/{session_id}/complete",
      headers={
          "Authorization": f"Bearer {os.environ['GODADDY_PAT']}",
          "Request-Id": str(uuid.uuid4()),
          "Idempotency-Key": completion_idempotency_key,
      },
      json=complete_body,
  )
  result = res.json()
  if not res.ok or result.get("ucp", {}).get("status") == "error":
      raise RuntimeError(result.get("messages", [{}])[0].get("code", f"HTTP {res.status_code}"))
  order_id = result.get("order", {}).get("id")
  receipt_url = result.get("order", {}).get("permalink_url")
  ```

  ```go tab="Go"
  package main

  import (
      "bytes"
      "crypto/rand"
      "encoding/json"
      "fmt"
      "log"
      "net/http"
      "os"
      "time"
  )

  func requestID() string {
      value := make([]byte, 16)
      if _, err := rand.Read(value); err != nil { log.Fatal(err) }
      value[6], value[8] = (value[6]&0x0f)|0x40, (value[8]&0x3f)|0x80
      return fmt.Sprintf("%x-%x-%x-%x-%x", value[:4], value[4:6], value[6:8], value[8:10], value[10:])
  }

  func main() {
      sessionID := os.Getenv("SESSION_ID")
      var agreementTypes []string
      _ = json.Unmarshal([]byte(os.Getenv("REQUIRED_AGREEMENTS_JSON")), &agreementTypes)
      requestBody := map[string]any{
          "payment": map[string]any{"instruments": []map[string]any{{
              "id": os.Getenv("PAYMENT_INSTRUMENT_ID"), "handler_id": os.Getenv("PAYMENT_HANDLER_ID"),
              "type": os.Getenv("PAYMENT_INSTRUMENT_TYPE"), "selected": true,
          }}},
      }
      if len(agreementTypes) > 0 {
          requestBody["consent"] = map[string]any{"agreement_types": agreementTypes, "agreed_at": time.Now().UTC().Format(time.RFC3339)}
      }
      body, _ := json.Marshal(requestBody)
      req, _ := http.NewRequest("POST",
          "https://api.godaddy.com/v1/shopping/checkout-sessions/"+sessionID+"/complete",
          bytes.NewReader(body))
      req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
      req.Header.Set("Request-Id", requestID())
      req.Header.Set("Idempotency-Key", os.Getenv("COMPLETE_IDEMPOTENCY_KEY"))
      req.Header.Set("Content-Type", "application/json")

      res, _ := http.DefaultClient.Do(req)
      defer res.Body.Close()
      if res.StatusCode != http.StatusOK { log.Fatalf("complete checkout returned %s", res.Status) }

      var result map[string]any
      json.NewDecoder(res.Body).Decode(&result)
      if order, ok := result["order"].(map[string]any); ok {
          fmt.Println("order:", order["id"], "receipt:", order["permalink_url"])
      }
  }
  ```

The completion examples build `consent` from the checkout's required agreements. Never hardcode agreement keys across products.

The standalone Go completion example reads the required agreement-key array from `REQUIRED_AGREEMENTS_JSON` and the retry-stable key from `COMPLETE_IDEMPOTENCY_KEY`.

```json
{
  "payment": { "instruments": [{ "id": "$PAYMENT_INSTRUMENT_ID", "handler_id": "$PAYMENT_HANDLER_ID", "type": "$PAYMENT_INSTRUMENT_TYPE", "selected": true }] },
  "consent": {
    "agreement_types": ["api_services_purchase_agreement"],
    "agreed_at": "$AGREED_AT"
  }
}
```

When `status` is `completed`, `order.id` contains the order identifier and `order.permalink_url` links to the receipt.

## Retrieve the order

`GET /v1/shopping/orders/{id}` returns order totals, line statuses, fulfillment data, and a receipt link. A new order might return `404 order_not_found` while the read model updates.

The following procedure retrieves an order.

* Get order detail:

  ```bash tab="curl"
  ORDER_FILE="$(mktemp)"
  delay=1
  for attempt in 1 2 3 4 5 6; do
    status="$(curl --silent --show-error -o "$ORDER_FILE" -w '%{http_code}' \
      "https://api.godaddy.com/v1/shopping/orders/$ORDER_ID" \
      -H "Authorization: Bearer $GODADDY_PAT" \
      -H "Request-Id: $(uuidgen)")"
    if [ "$status" = "200" ]; then break; fi
    code="$(jq -r '.messages[0].code // empty' "$ORDER_FILE")"
    if [ "$status" != "404" ] || [ "$code" != "order_not_found" ]; then
      jq . "$ORDER_FILE" >&2
      exit 1
    fi
    sleep "$delay"
    delay=$((delay < 4 ? delay * 2 : 4))
  done
  test "$status" = "200"
  ORDER_RESPONSE="$(cat "$ORDER_FILE")"
  rm -f "$ORDER_FILE"
  ```

  ```js tab="Node"
  let delay = 1000;
  let order;
  for (let attempt = 1; attempt <= 6; attempt++) {
    const res = await fetch(`https://api.godaddy.com/v1/shopping/orders/${orderId}`, {
      headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}`, "Request-Id": crypto.randomUUID() },
    });
    const body = await res.json();
    if (res.ok) { order = body; break; }
    if (res.status !== 404 || body.messages?.[0]?.code !== "order_not_found" || attempt === 6) {
      throw new Error(body.messages?.[0]?.code ?? `HTTP ${res.status}`);
    }
    await new Promise((resolve) => setTimeout(resolve, delay));
    delay = Math.min(delay * 2, 4000);
  }
  ```

  ```python tab="Python"
  import os, time, uuid, requests

  delay = 1
  for attempt in range(1, 7):
      res = requests.get(
          f"https://api.godaddy.com/v1/shopping/orders/{order_id}",
          headers={
              "Authorization": f"Bearer {os.environ['GODADDY_PAT']}",
              "Request-Id": str(uuid.uuid4()),
          },
      )
      order = res.json()
      if res.ok:
          break
      code = order.get("messages", [{}])[0].get("code")
      if res.status_code != 404 or code != "order_not_found" or attempt == 6:
          raise RuntimeError(code or f"HTTP {res.status_code}")
      time.sleep(delay)
      delay = min(delay * 2, 4)
  ```

  ```go tab="Go"
  package main

  import (
      "crypto/rand"
      "encoding/json"
      "fmt"
      "log"
      "net/http"
      "os"
      "time"
  )

  func requestID() string {
      value := make([]byte, 16)
      if _, err := rand.Read(value); err != nil { log.Fatal(err) }
      value[6], value[8] = (value[6]&0x0f)|0x40, (value[8]&0x3f)|0x80
      return fmt.Sprintf("%x-%x-%x-%x-%x", value[:4], value[4:6], value[6:8], value[8:10], value[10:])
  }

  func main() {
      orderID := os.Getenv("ORDER_ID")
      delay := time.Second
      for attempt := 1; attempt <= 6; attempt++ {
          req, _ := http.NewRequest("GET", "https://api.godaddy.com/v1/shopping/orders/"+orderID, nil)
          req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
          req.Header.Set("Request-Id", requestID())
          res, err := http.DefaultClient.Do(req)
          if err != nil { log.Fatal(err) }
          var body map[string]any
          if err := json.NewDecoder(res.Body).Decode(&body); err != nil { log.Fatal(err) }
          res.Body.Close()
          if res.StatusCode == http.StatusOK { fmt.Println(body); return }
          code := ""
          if messages, ok := body["messages"].([]any); ok && len(messages) > 0 {
              if message, ok := messages[0].(map[string]any); ok { code, _ = message["code"].(string) }
          }
          if res.StatusCode != http.StatusNotFound || code != "order_not_found" || attempt == 6 {
              log.Fatalf("get order returned %s (%s)", res.Status, code)
          }
          time.Sleep(delay)
          if delay < 4*time.Second { delay *= 2 }
      }
  }
  ```

## Example responses

The following sections provide example responses for creating and completing checkout sessions.

Amounts use ISO 4217 minor units. For USD, an amount of `999` represents `$9.99`.

### Checkout session

`POST /checkout-sessions` returns `201` with a session object. An eligible saved instrument can make it immediately `ready_for_complete`:

```json
{
  "id": "d9cf79e3-b39b-4e58-8f30-ddfe1619e969",
  "status": "ready_for_complete",
  "currency": "USD",
  "line_items": [
    {
      "id": "1",
      "item": { "id": "hosting-economyapi:1mo" },
      "quantity": 1,
      "totals": [
        { "type": "subtotal", "amount": 999 },
        { "type": "total", "amount": 999 }
      ]
    }
  ],
  "totals": [
    { "type": "subtotal", "amount": 999 },
    { "type": "total", "amount": 999 }
  ],
  "payment": {
    "instruments": [
      {
        "id": "payment-instrument-id",
        "handler_id": "com.godaddy.payments",
        "type": "card",
        "selected": true
      }
    ]
  },
  "required_agreements": [
    {
      "key": "universal_terms_and_conditions",
      "title": "Universal Terms of Service Agreement",
      "required": true,
      "url": "https://www.godaddy.com/legal/agreements/universal-terms-of-service-agreement"
    }
  ],
  "ucp": { "version": "2026-04-08" }
}
```

### Completed checkout

`POST /checkout-sessions/{id}/complete` returns `status: completed` when payment succeeds. Use the numeric `order.id` to retrieve the order:

```json
{
  "id": "d9cf79e3-b39b-4e58-8f30-ddfe1619e969",
  "status": "completed",
  "order": {
    "id": "2211390786",
    "permalink_url": "https://account.godaddy.com/receipts/view/2211390786"
  },
  "ucp": { "version": "2026-04-08" }
}
```

## Common errors

| Response           | `messages[].code`                                                          | Cause                                                                             | Action                                                                                                  |
| ------------------ | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `200` with warning | `line_item_removed`                                                        | A line item could not be priced and was removed from the session.                 | Re-check `line_items[]` before completion.                                                              |
| `400`              | `payment_instrument_invalid`                                               | The selected payment profile is unavailable or ineligible.                        | Retrieve the checkout and select an eligible instrument.                                                |
| `400`              | `validation_error`                                                         | `POST /complete` was called with missing or incomplete `consent.agreement_types`. | Check the checkout's `required_agreements[]`, collect buyer consent, and retry with `consent` included. |
| `401`              | —                                                                          | The PAT is missing, invalid, or expired.                                          | Generate or export a valid PAT.                                                                         |
| `403`              | —                                                                          | The PAT lacks the required Shopping scope.                                        | Generate a PAT with `shopping.checkout:execute` and `shopping.order:read`.                              |
| `422`              | `item_not_found`, `invalid_domain`, `invalid_term`, `unsupported_currency` | The request is valid JSON but cannot be processed.                                | Correct the item or currency before retrying with a new idempotency key.                                |
| `404`              | `order_not_found`                                                          | A completed order is not visible in the read model yet.                           | Retry the unchanged GET with bounded backoff.                                                           |
| `429`              | —                                                                          | Rate limit exceeded.                                                              | Wait for the `Retry-After` header value, then retry.                                                    |

After completion, `GET /checkout-sessions/{id}` might return `404 checkout_not_found`. Keep the completion response and use its `order.id` for order retrieval. Go to [About the Shopping API](https://developer.godaddy.com/docs/api-users/shopping#error-responses) for error handling.

## Verify the purchase

Confirm that completion returned `status: completed` and a numeric `order.id`. After the order becomes visible, verify its currency, total, line-item statuses, and receipt permalink.

Fulfillment `events[]` and `expectations[]` are optional. Process them when present; do not require one expectation per line item.
