Delivery configuration defines which shipment, pickup, and local-delivery choices Flint can offer before payment. It covers reusable profiles, zones, location sets, methods, pricing, quotes, previews, and selections. Fulfillment records remain under Fulfillment.
Quote and select delivery at checkout#
Fulfillment configuration answers three separate questions:
- A profile says whether a catalog item requires fulfillment and which fulfillment types may satisfy it.
- A method says how one buyer-visible choice is routed, checked for eligibility, priced, taxed, scheduled, and labeled.
- A quote evaluates the methods pinned to one checkout. A selection commits one option for every required choice group.
Profiles, zones, location sets, methods, and callback endpoints are versioned resources. A configuration change publishes an immutable revision for future checkouts. Existing checkout sessions continue using their pinned revisions. Use the resource's version as expected_version when a PATCH changes pinnable configuration.
Rate-table methods return their ordered rates inline in configuration.pricing.rate_table.rates. Manage an individual rate through /v1/delivery-methods/{delivery_method_id}/rates and its stable delivery_rate_id. Create, update, and delete operations require the method's current version as expected_version, publish a new method revision, and return the next delivery_method_version write fence.
The three selectable fulfillment types are shipment, pickup, and local_delivery. delivery is not an alias for local_delivery.
Every environment gets an active General profile when you create a physical catalog item without an explicit profile. Listing delivery profiles is read-only and does not create it. General requires a quote, allows shipment, pickup, and local delivery, uses the chosen method's origin, combines compatible obligations, and keeps each line item whole. Create another profile only when a catalog item needs different fulfillment rules.
Configure delivery#
Set up reusable resources before assigning methods to a payment link:
- Use the active General profile, or create and activate a profile with different rules. A new physical product variant that omits
delivery_profile_idreceives the General profile. - Create and activate any zones or location sets referenced by a method. The method's
origin.typedetermines whether Flint uses a location set for allocation or buyer pickup. - Create a method with one
typeand its typedconfiguration, then activate it. - Replace the payment link's complete method assignment with
PUT /v1/payment-links/{payment_link_id}/delivery-methods.
Each resource family has its own request schema. Method-only fields such as type, description, display_position, and recommendation_priority are rejected by profile, zone, location-set, and callback endpoints. Callback endpoints accept transport configuration but not metadata.
This creates a profile whose items require a quoted shipment or pickup:
curl -X POST \
https://api.withflintpay.com/v1/delivery-profiles \
-H "Authorization: Bearer flint_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-standard-profile" \
-d '{
"name": "Standard physical goods",
"configuration": {
"requirement": "required",
"resolution_mode": "quote",
"allowed_types": ["shipment", "pickup"],
"origin_policy": {
"type": "inventory_routing"
},
"combination_policy": "combine_when_compatible",
"splitting_policy": "whole_line_item"
}
}'
Creation publishes revision 1 and defaults to status: "inactive". Send status: "active" when every referenced dependency already exists and is active. Otherwise activate the method after its dependencies are ready.
A method request uses a closed, typed configuration. Predicate nodes contain exactly one operator. Leaf operators such as country, state, and postal_code are direct fields and are never wrapped in leaf:
{
"country": {
"subject": "destination_address",
"values": ["US", "CA"]
}
}
Eligibility can also match the current customer using customer_group, customer_verified, customer_has_email, and customer_has_phone_number. An anonymous buyer does not match a false-valued customer condition. Flint snapshots only these allowlisted facts. Customer metadata, names, notes, and other private fields are not part of fulfillment eligibility.
Zone expressions accept only geographic conditions: country, state, postal_code, and radius. Put customer and window_time conditions in the method's eligibility expression.
curl -X POST \
https://api.withflintpay.com/v1/delivery-methods \
-H "Authorization: Bearer flint_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-standard-shipping" \
-d '{
"name": "Standard shipping",
"type": "shipment",
"configuration": {
"origin": {
"type": "fixed_location",
"location_id": "loc_..."
},
"pricing": {
"type": "fixed",
"fixed": {
"currency_options": {
"USD": {"amount": 900}
}
}
},
"estimate": {"type": "none"}
}
}'
Omitting eligibility offers the method everywhere you serve. Omitting taxable and tax_category inherits the merchant's delivery tax defaults, then derives any unset category from the method type. Flint derives window selection from estimate.type and uses zero for the optional lifetime and selection guarantee settings. Inside currency_options, the map key supplies an omitted currency value. Calculated, callback, and caller-supplied pricing must include minimum_option_lifetime_seconds, which must be less than 900.
POST /v1/delivery-profiles/{delivery_profile_id}/activate
POST /v1/delivery-methods/{delivery_method_id}/activate
An activation or archive request that is blocked by another active resource returns DELIVERY_RESOURCE_HAS_DEPENDENCIES with the dependency type and blocking count. A stale expected_version returns DELIVERY_RESOURCE_VERSION_CONFLICT with the current version. Omitting expected_version while changing a field that publishes a revision returns DELIVERY_EXPECTED_VERSION_REQUIRED; its param identifies the field that triggered the write fence. A method's buyer-visible name, description, display position, and recommendation priority are pinnable and publish revisions. Presentation-only changes to other resource families do not.
Assign delivery_profile_id when a product variant or bundle component should not use its inherited or default profile. Reads return the assigned profile, its current revision, and delivery_configuration_status. Filter product-variant and bundle-component lists by delivery_profile_id or by delivery_configuration_status=configured|action_required to find assignments that need attention.
GET /v1/products/{product_id}/variants?delivery_profile_id=dprof_...
GET /v1/bundles/{bundle_id}/components?delivery_configuration_status=action_required
Clearing delivery_profile_id on an update leaves the catalog relationship in action_required until another active profile is assigned.
Buyer authentication#
A buyer-facing checkout request authenticates with both headers:
X-Checkout-Session-ID: cs_...
X-Checkout-Session-Secret: ...
The checkout session ID in the header must match the ID in the route. Merchant API keys and authorized partner tokens can use the same checkout delivery routes.
Customer-dependent rules use the checkout's snapshotted allowlisted facts. Creating a new quote evaluates the current checkout and customer state.
Read the current checkout state#
Read the checkout session and effective selection before creating or replacing a quote:
GET /v1/checkout-sessions/{checkout_session_id}
GET /v1/checkout-sessions/{checkout_session_id}/delivery-selections/current
The effective selection's source is provisional while checkout owns it and committed after payment commits it to the order. Terminal checkout sessions remain readable, including their effective selection and order, so a receipt or return visit does not need to start a new quote.
For Flint-owned pricing, an open checkout read can carry a derived current quote. A checkout that uses callback or caller-supplied pricing reports that it requires an explicit quote. Reads never call a merchant callback.
Create a quote#
Create a quote when the buyer supplies or corrects a destination, chooses a pickup location, or the checkout reports that an explicit quote is required:
curl -X POST \
https://api.withflintpay.com/v1/checkout-sessions/cs_123/delivery-quotes \
-H "X-Checkout-Session-ID: cs_123" \
-H "X-Checkout-Session-Secret: CHECKOUT_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: quote-cart-42-address-2" \
-d '{
"expected_delivery_selection_id": null,
"destination_address": {
"line1": "120 Kent Avenue",
"city": "Brooklyn",
"state": "NY",
"postal_code": "11249",
"country": "US"
}
}'
Launch destination validation accepts US and CA. Other country codes return a field-level validation error instead of producing a quote that downstream evaluators cannot support.
Pass the exact current selection ID when replacing an existing selection. If DELIVERY_SELECTION_CHANGED is returned, read the effective selection again and create the replacement quote from that new basis. Do not retry with a cached selection ID.
Each choice_group contains candidate outcomes and selectable options. An option owns its amount, expiry, arrival estimate, recipient requirements, and any offered windows. A quote is selectable only when every required choice group has a current available option.
window_start_at and window_end_at are absolute RFC 3339 instants. timezone names the IANA timezone to use when displaying offered windows. Do not display those instants as UTC unless timezone is UTC. When present, arrival_estimate contains buyer-facing local calendar dates and its resolved timezone. delivery_plan.type is single_delivery or multiple_deliveries; a multi-delivery plan also reports its planned count and overall first-arrival and completion bounds.
Use input_requirements to build quote and selection forms without parsing prose. Each requirement has a stable field_path, a purpose (quote, selection, or pickup_availability), and a typed constraint. Constraint types include string, address_field, email, phone, coordinate, time_window, and caller_supplied_rate. Length limits, formats, supported countries, allowed values, and offered windows are machine-readable fields when applicable. Selection requirements also identify the exact choice groups, methods, and options they apply to.
Find an available pickup location#
Query pickup availability before creating a pickup quote:
curl -X POST \
https://api.withflintpay.com/v1/checkout-sessions/cs_123/query-pickup-availability \
-H "X-Checkout-Session-ID: cs_123" \
-H "X-Checkout-Session-Secret: CHECKOUT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"expected_delivery_selection_id": null,
"buyer_location": {
"type": "address",
"address": {
"postal_code": "11249",
"country": "US"
}
}
}'
This query does not reserve inventory. Create a quote with the selected result's location_id as pickup_location_id. Quote creation checks the location and inventory again, so handle inventory changes by querying availability again.
Supply rates from your server#
A caller_supplied method uses a two-request exchange with merchant authentication:
- Create a quote without
method_results. The response is a non-selectable pending quote withpending_caller_rate_requests. - Price every requested method and choice-group pair, then create a second quote with the pending quote's ID as
basis_delivery_quote_idand the completemethod_resultsarray.
The second request must repeat the planning inputs from the pending request, including destination_address, buyer_location, pickup_location_id, and inventory_assignments when supplied. Use a new idempotency key because the priced quote has a different request body. Changed or stale inputs return DELIVERY_QUOTE_BASIS_INVALID.
{
"basis_delivery_quote_id": "dqt_...",
"destination_address": {
"postal_code": "11249",
"country": "US"
},
"method_results": [
{
"delivery_method_revision_id": "dmetr_...",
"delivery_choice_group_id": "dcgrp_...",
"outcome": {
"type": "available",
"amount_money": { "amount": 900, "currency": "USD" },
"expires_at": "2030-07-30T18:30:00Z",
"selection_guarantee_expires_at": "2030-07-30T19:00:00Z",
"service_level": "same_day"
}
}
]
}
The result array must contain exactly one outcome for every pending request. Extra, missing, or duplicate pairs are rejected. Buyer credentials cannot submit caller-supplied rates.
Select options and windows#
Create a selection with one choice per required group:
curl -X POST \
https://api.withflintpay.com/v1/checkout-sessions/cs_123/delivery-selections \
-H "X-Checkout-Session-ID: cs_123" \
-H "X-Checkout-Session-Secret: CHECKOUT_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: select-cart-42-3" \
-d '{
"delivery_quote_id": "dqt_...",
"expected_delivery_selection_id": null,
"choices": [
{
"delivery_choice_group_id": "dcgrp_...",
"delivery_option_id": "dopt_...",
"input": {
"shipment": {
"delivery_window_id": "fwin_..."
}
}
}
],
"destination_address": {
"line1": "120 Kent Avenue",
"city": "Brooklyn",
"state": "NY",
"postal_code": "11249",
"country": "US"
},
"recipient": {
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+14155552671"
}
}'
Send exactly one input object matching the option type. An empty matching object is valid when the choice has no instructions or offered window. Send delivery_window_id inside that typed input whenever the option has window_selection: "offered". The selected window can change the option price and can expire before the parent option. A lapsed window or quote returns a typed expiry error; create a current quote and ask the buyer to choose again.
Selection-purpose input_requirements is the complete source of truth for required recipient fields. Each option's recipient_requirements is the option-scoped convenience projection of those same name, email, and phone rules. Email and phone values are validated using the declared format. Unknown recipient fields are rejected.
Buyer credentials cannot set external_system or external_reference_id. Those fields are merchant-owned correlation values and require server authentication.
Clear a provisional selection with its exact current ID:
DELETE /v1/checkout-sessions/{checkout_session_id}/delivery-selections/current?expected_delivery_selection_id=dsel_...
Idempotency-Key: clear-cart-42-4
The compare-and-swap ID prevents one browser tab from clearing a newer selection written by another tab. A committed order selection cannot be cleared from checkout.
Payment and recovery#
Payment revalidates the selection, its calculation deadline, inventory authority, configuration revisions, and selected window before it starts a payment attempt. An expired promotion, coupon, option, or window cannot be extended by retrying payment.
Handle these errors by reading current state before retrying:
| Code | Recovery |
|---|---|
DELIVERY_SELECTION_CHANGED | Read the effective selection, then quote and select against its exact ID. |
DELIVERY_QUOTE_EXPIRED | Create a current quote and ask the buyer to select a current option. |
DELIVERY_SELECTION_EXPIRED | Create a current quote and selection. |
DELIVERY_CALCULATION_EXPIRED | Requote so discounts, tax, and delivery economics are recalculated together. |
PAYMENT_ATTEMPT_IN_PROGRESS | Finish or cancel the payment attempt that owns the selection. |
Pricing strategies#
A method uses exactly one pricing strategy:
fixed: one configured amount per currency.rate_table: the highest-priority matching rate wins.tiered: contiguous bands start at zero and the final band is unbounded.calculated: Flint combines configured base, distance, weight, and handling components.callback: Flint sends one signed, bounded request for the choice group's callback methods.caller_supplied: your server completes a pending quote with normalized method results.
Flint does not clamp callback or caller-supplied amounts. Values outside the method's configured currency bounds become cannot_calculate.
For a callback method, set configuration.quote_input_fields to the smallest closed set your rate service needs. Supported values are the address roots, line1, line2, city, state, postal_code, and country under destination_address or buyer_location, plus buyer_location.coordinate. Missing declared values become typed quote input requirements before Flint calls your server.
Hosted checkout rejects required caller_supplied methods because a buyer page cannot provide merchant-owned rates. In embedded checkout, create the quote from your server. A buyer-authenticated quote request on a checkout containing a caller-supplied method returns DELIVERY_CALLER_RATES_UNAVAILABLE_TO_BUYER.
Callback and caller-supplied outcomes can include a private merchant_reference of at most 500 characters. When that option is selected, Flint copies the reference into the encrypted selection and payment snapshot. Merchant-authenticated selection reads return it on the selected choice. Buyer-authenticated reads and public economic quote projections omit it.
Create a delivery rate callback directly. Flint owns its outbound transport and signing-key set, so the request does not accept a webhook endpoint ID:
curl -X POST \
https://api.withflintpay.com/v1/delivery-rate-callbacks \
-H "Authorization: Bearer flint_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-delivery-rate-callback" \
-d '{
"name": "Fulfillment rates",
"configuration": {
"url": "https://rates.example.com/flint/delivery-rates"
}
}'
The create response returns key_id and secret. Store the secret immediately because later reads omit it. Flint defaults request_timeout_seconds to 2, preview_enabled to false, both byte limits to 262144, and redirect_policy to reject. Each immutable delivery rate callback revision pins those values. Updating the URL publishes a new revision and does not rewrite an existing checkout's callback destination.
Routine signing-key rotation accepts both the outgoing and incoming key for one hour. Use delivery revocation to fence a compromised key immediately.
Implement a delivery rate callback#
Flint sends one JSON POST for all callback-priced candidate methods in a choice group. The event type is delivery.rate_evaluation. Test deliveries use delivery.rate_evaluation.test and set test: true in the body.
The request includes only address and coordinate fields declared by at least one method in the batch. It never includes customer eligibility facts, address-verification state, provider payloads, customer metadata, or private notes. Pricing bases, item quantity, weight, distances, and execution allocations remain available because they are required to calculate the rate.
The request uses the same signing scheme as Flint webhooks:
| Header | Value |
|---|---|
webhook-id | The delivery_rate_evaluation_id. It remains stable when Flint retries the same evaluation. |
webhook-timestamp | Unix timestamp used by Standard Webhooks verification. |
webhook-signature | Standard Webhooks signature. Verify it against the raw request body using the delivery rate callback secret. |
X-Flint-Signature | Legacy Flint signature over the raw body. |
X-Flint-Event-Type | delivery.rate_evaluation or delivery.rate_evaluation.test. |
X-Flint-Key-ID | The current signing key ID. |
X-Flint-Key-IDs | Every key ID whose signature may be present during the one-hour rotation overlap. |
Use the Webhooks guide for Standard Webhooks and legacy signature verification examples. Verify before parsing JSON, reject stale timestamps, and accept any listed signature during rotation.
A request has this shape:
{
"delivery_rate_evaluation_id": "dreval_...",
"key_id": "cbkey_...",
"checkout_session_id": "cs_...",
"order_id": "ord_...",
"delivery_quote_revision": 4,
"quote_creation_identity": "a stable quote-attempt identity",
"delivery_feasibility_plan_fingerprint": "sha256...",
"delivery_choice_group_id": "dcgrp_...",
"choice_group_fingerprint": "sha256...",
"delivery_rate_callback_revision_id": "dcbr_...",
"currency": "USD",
"destination": {
"postal_code": "11249",
"country": "US"
},
"candidates": [
{
"delivery_method_revision_id": "dmetr_...",
"execution_legs": [
{
"delivery_execution_leg_id": "dleg_...",
"fingerprint": "sha256...",
"origin_location_id": "loc_...",
"origin_geography_revision": 7,
"allocations": [
{
"demand_key": "line:oli_...",
"order_line_item_id": "oli_...",
"quantity": 1,
"merchandise_value_money": {
"amount": 2500,
"currency": "USD"
},
"allowed_types": ["shipment"]
}
]
}
],
"pricing": {
"currency": "USD",
"bases": {
"order_subtotal": 2500,
"choice_group_subtotal": 2500
},
"total_weight_grams": 900,
"total_item_quantity": 1,
"leg_distance_meters": [8200]
}
}
],
"evaluated_at": "2030-07-30T18:00:00Z"
}
Return one result for every candidate method revision and echo the evaluation ID:
{
"delivery_rate_evaluation_id": "dreval_...",
"ttl_seconds": 120,
"method_results": [
{
"delivery_method_revision_id": "dmetr_...",
"outcome": {
"type": "available",
"amount_money": {
"amount": 900,
"currency": "USD"
},
"expires_at": "2030-07-30T18:15:00Z",
"selection_guarantee_expires_at": "2030-07-30T18:30:00Z",
"service_level": "same_day",
"merchant_reference": "rate-request-8472"
}
}
]
}
An outcome is one of:
available: requiresamount_money. It may include one fixed window,offered_windows,window_selection,window_pricing, expiry fields,service_level, and a privatemerchant_reference.unavailable: include a stableunavailable_reason, such asdestination_not_served.cannot_calculate: include a stablefailure_categoryand whether the failure isretryable.
Return a 2xx response with one JSON object and no trailing data. Unknown response fields are rejected. The response must stay within the endpoint's maximum_response_bytes. A missing, duplicate, or malformed method result becomes cannot_calculate for that method without invalidating valid sibling results.
ttl_seconds controls response caching. It defaults to 120 seconds and cannot exceed 300 seconds. Responses containing cannot_calculate outcomes are cached for at most 30 seconds.
Flint applies the configured request timeout to each attempt. A transport failure or non-2xx response is retried once immediately with the same delivery_rate_evaluation_id. Malformed successful JSON is not retried. Your handler must therefore deduplicate by webhook-id or delivery_rate_evaluation_id before performing side effects. Redirects are never followed.
After 30 consecutive failures, the callback circuit opens and Flint drops its methods without calling the endpoint. Quote diagnostics report callback_circuit_open. The callback resource exposes circuit_state and circuit_failure_count. Flint automatically allows a half-open probe after the recovery delay and closes the circuit after a valid response.
Emergency revocation#
POST /v1/delivery-revocations permanently fences compromised or unsafe configuration. Prefer an exact revision, signing key, or Location geography version when you know it. A stable delivery_method or delivery_rate_callback target must include that resource's current version as expected_version. Flint resolves the stable target to that exact revision under a write lock, so a concurrent publication cannot revoke a revision you did not inspect.
Revocation immediately invalidates affected unowned quotes and provisional selections. Committed order and payment history remains readable for audit and fulfillment execution.
Previews and diagnostics#
POST /v1/delivery-previews evaluates an orderless cart without creating a checkout, selection, inventory hold, or retained address. Use it to test configuration and display likely options. A preview is not selection authority.
Merchant-authenticated quote reads and delivery diagnostics include resource-specific method revisions and safe failure categories. Buyer reads contain actionable outcomes and input requirements but omit warehouse identity, callback configuration, secrets, private correlation, raw provider values, and exact inventory.
