Add shipping to a checkout

This guide adds one flat-rate shipping choice to an existing order and checkout. You need an active Location with a published address, an order whose physical line items use the General delivery profile, and an API key with delivery and checkout write scopes.

A delivery selection is the only thing that gives an order a shipping destination. Billing details collected during payment do not become fulfillment data, so an order paid without a delivery selection has nothing to ship against. If you are building your own checkout, this step is not optional.

1. Create the shipping method#

Create the method as active when its Location is already ready. Omit estimate to use {"type":"none"}.

HTTP
POST /v1/delivery-methods
Authorization: Bearer flint_test_...
Idempotency-Key: create-standard-shipping
Content-Type: application/json

{
  "name": "Standard shipping",
  "type": "shipment",
  "status": "active",
  "configuration": {
    "origin": {
      "type": "fixed_location",
      "location_id": "loc_01K1P6G4M7H2N8Q9R3S5T6V7WX"
    },
    "pricing": {
      "type": "fixed",
      "fixed": {
        "currency_options": {
          "USD": {"amount": 900}
        }
      }
    }
  }
}
JSON
{
  "data": {
    "delivery_method_id": "dmet_01K1P6G4M7H2N8Q9R3S5T6V7WX",
    "type": "shipment",
    "name": "Standard shipping",
    "status": "active",
    "version": 1,
    "current_delivery_method_revision_id": "dmetr_01K1P6G4M7H2N8Q9R3S5T6V7WX",
    "configuration": {
      "origin": {"type": "fixed_location", "location_id": "loc_01K1P6G4M7H2N8Q9R3S5T6V7WX"},
      "pricing": {"type": "fixed", "fixed": {"currency_options": {"USD": {"amount": 900, "currency": "USD"}}}},
      "estimate": {"type": "none"},
      "tax_category": "shipping"
    }
  },
  "request_id": "req_01K1P6G4M7H2N8Q9R3S5T6V7WX"
}

2. Assign the method to the checkout#

Include the method when creating a checkout for the order. You can instead save it in settings.checkout.default_delivery_method_ids and omit delivery_method_ids on future checkout requests.

HTTP
POST /v1/checkout-sessions
Authorization: Bearer flint_test_...
Idempotency-Key: checkout-order-42
Content-Type: application/json

{
  "surface": "hosted",
  "order_id": "ord_01K1P6G4M7H2N8Q9R3S5T6V7WX",
  "delivery_method_ids": ["dmet_01K1P6G4M7H2N8Q9R3S5T6V7WX"]
}

The response carries the session under data.checkout_session and the credentials under data.checkout_access:

JSON
{
  "data": {
    "checkout_session": {
      "checkout_session_id": "cs_01K1P6G4M7H2N8Q9R3S5T6V7WX",
      "status": "open",
      "surface": "hosted",
      "order_id": "ord_01K1P6G4M7H2N8Q9R3S5T6V7WX",
      "delivery_method_ids": ["dmet_01K1P6G4M7H2N8Q9R3S5T6V7WX"]
    },
    "checkout_access": {
      "checkout_auth_token": "ckat_v1...",
      "hosted_url": "https://checkout.withflintpay.com/checkout/cs_01K1P6G4M7H2N8Q9R3S5T6V7WX#checkout_token=..."
    }
  },
  "request_id": "req_01K1P6G4M7H2N8Q9R3S5T6V7WX"
}

Redirect to data.checkout_access.hosted_url exactly as returned. The #checkout_token fragment is load-bearing, and a reconstructed URL drops it. For an embedded session, hosted_url is absent and you keep checkout_auth_token on your backend instead.

3. Quote the buyer's address#

Buyer-side requests use the checkout ID and secret headers. Send null to assert that no selection exists yet.

HTTP
POST /v1/checkout-sessions/cs_01K1P6G4M7H2N8Q9R3S5T6V7WX/delivery-quotes
X-Checkout-Session-ID: cs_01K1P6G4M7H2N8Q9R3S5T6V7WX
X-Checkout-Session-Secret: CHECKOUT_SECRET
Idempotency-Key: quote-order-42-address-1
Content-Type: application/json

{
  "expected_delivery_selection_id": null,
  "destination_address": {
    "line1": "120 Kent Avenue",
    "city": "Brooklyn",
    "state": "NY",
    "postal_code": "11249",
    "country": "US"
  }
}
JSON
{
  "data": {
    "audience": "buyer",
    "delivery_quote_id": "dquote_01K1P6G4M7H2N8Q9R3S5T6V7WX",
    "status": "open",
    "evaluation_status": "complete",
    "choice_groups": [{
      "delivery_choice_group_id": "dcgrp_01K1P6G4M7H2N8Q9R3S5T6V7WX",
      "availability_status": "available",
      "options": [{
        "delivery_option_id": "dopt_01K1P6G4M7H2N8Q9R3S5T6V7WX",
        "type": "shipment",
        "name": "Standard shipping",
        "amount_money": {"amount": 900, "currency": "USD"}
      }]
    }],
    "buyer_reasons": [],
    "expires_at": "2026-08-03T16:00:00Z"
  },
  "request_id": "req_01K1P6G4M7H2N8Q9R3S5T6V7WX"
}

Quotes expire. expires_at is on every quote, and selecting against a stale one fails rather than charging an outdated rate. When that happens the session reports it in problems[], and each entry carries a remediation naming what to do, which is normally to re-quote. Surface that instead of a generic error: the buyer's address is still valid, only the price is stale.

4. Select the option#

Use IDs from the quote. Do not reconstruct them from method data.

The selected option decides which recipient details are required. A shipment normally needs a recipient name, and it may require an email or phone for carrier notifications. Those requirements come back on the option, so read them rather than assuming; a selection missing a required field is rejected.

HTTP
POST /v1/checkout-sessions/cs_01K1P6G4M7H2N8Q9R3S5T6V7WX/delivery-selections
X-Checkout-Session-ID: cs_01K1P6G4M7H2N8Q9R3S5T6V7WX
X-Checkout-Session-Secret: CHECKOUT_SECRET
Idempotency-Key: select-order-42-shipping
Content-Type: application/json

{
  "delivery_quote_id": "dquote_01K1P6G4M7H2N8Q9R3S5T6V7WX",
  "expected_delivery_selection_id": null,
  "choices": [{
    "delivery_choice_group_id": "dcgrp_01K1P6G4M7H2N8Q9R3S5T6V7WX",
    "delivery_option_id": "dopt_01K1P6G4M7H2N8Q9R3S5T6V7WX"
  }]
}
JSON
{
  "data": {
    "audience": "buyer",
    "delivery_selection_id": "dsel_01K1P6G4M7H2N8Q9R3S5T6V7WX",
    "delivery_quote_id": "dquote_01K1P6G4M7H2N8Q9R3S5T6V7WX",
    "status": "selected",
    "choices": [{
      "delivery_choice_group_id": "dcgrp_01K1P6G4M7H2N8Q9R3S5T6V7WX",
      "delivery_option_id": "dopt_01K1P6G4M7H2N8Q9R3S5T6V7WX",
      "name": "Standard shipping",
      "total_money": {"amount": 900, "currency": "USD"}
    }]
  },
  "request_id": "req_01K1P6G4M7H2N8Q9R3S5T6V7WX"
}

Checkout can now collect payment. Flint commits the selection to the order atomically with successful payment processing.

5. Re-read the order#

A delivery selection changes what the buyer owes. The shipping charge is added, and because shipping is taxable in many jurisdictions, the tax total can move too.

Read the order after selecting and render the amounts it returns. Do not add the shipping price to a total you already had. If you are collecting payment yourself, the outstanding balance from this read is also what belongs in expected_outstanding_money on PayOrder, so a stale total is caught before the card is charged rather than after.

Rate this doc