Skip to main content

Commerce API

Build a server-side storefront on DYLI inventory. The Commerce API covers partner configuration, catalog discovery, customer synchronization, server-priced quotes, USDC and card payment, order status, Boxes, Collection, redemptions, and signed webhooks.

The API is designed for server-to-server use. Never put a DYLI API key in browser or mobile client code.

What your integration needs

You provide:

  • A backend that can make HTTPS requests and keep an API key secret
  • A stable customer ID and an Abstract wallet address for each customer receiving an item
  • A database or other durable store for your own customer, cart, idempotency, and order references

You do not need access to our database, a chain indexer, or DYLI infrastructure credentials. DYLI operates pricing, inventory claims, payment verification, order state, and fulfillment.

Choose the payment experience that fits your product:

Payment experienceWhat you operateWhat DYLI operates
Hosted Stripe CheckoutRedirect the customer to the returned URLStripe account, Checkout Session, payment webhook, verification, and order creation
Embedded Stripe CheckoutRender Stripe.js with the returned publishable key and client secretStripe account, PaymentIntent, payment webhook, verification, and order creation
Partner treasury USDCYour customer billing or ledger, treasury wallet, and wallet signerPayment instructions, onchain verification, replay protection, and fulfillment
Customer-paid USDCCustomer wallet connection and signaturePayment instructions, onchain verification, replay protection, and fulfillment

Hosted and embedded card checkout do not require your own Stripe account or Stripe webhook. If you collect customer funds yourself and settle from a treasury wallet, that separate customer-payment system remains yours to operate.

Partner webhooks are optional. A small integration can poll GET /orders/{order_id}; use signed webhooks when you want push-based updates.

Base URL and contract

https://www.dyli.io/api/commerce/v1
  • OpenAPI 3.1 document
  • Version: v1
  • Checkout shape: exactly one item with quantity 1
  • Purchase delivery: the customer's DYLI Collection on Abstract
  • Shipping: a separate redemption after purchase
  • Quote lifetime: normally 10 minutes; a Stripe session may extend it to the Checkout expiry

Every request needs a Commerce-enabled API key and a partner slug:

x-api-key: dyli_live_...
x-partner-slug: example-store

Authorization: Bearer dyli_live_... is also supported. Commerce credentials are accepted only in headers, never in query parameters or request bodies.

The slug creates an isolated configuration, customer, quote, order, event, and idempotency namespace for that app. Use a stable lowercase slug in production.

Start without onboarding

Create a key at dyli.io/requestapi. It includes Read and Commerce access immediately.

DYLI_PARTNER_SLUG can be any stable name up to 80 characters. DYLI converts it to lowercase kebab-case, so My Store, my_store, and my-store all become my-store.

The same DYLI account and slug share one namespace. Another account can use the same slug without sharing any data, so there is no global availability check. Call GET /api/commerce/v1 and read partner.slug to see the final value.

Each account can currently have three active self-serve keys and 25 partner slugs.

Check runtime readiness

Call the API root before enabling checkout:

curl "https://www.dyli.io/api/commerce/v1" \
-H "x-api-key: $DYLI_API_KEY" \
-H "x-partner-slug: $DYLI_PARTNER_SLUG"

Do not infer payment availability from documentation alone. Check:

{
"environment": "production",
"capabilities": {
"writes": { "enabled": true, "status": "available" },
"payments": ["usdc", "stripe_card"],
"purchasing_ready": true,
"fulfillment": { "ready": true, "live_ready": true, "mode": "executor" }
},
"payment": {
"stripe_card": {
"available": true,
"hosted_checkout": true,
"embedded_checkout": true,
"webhook_configured": true
}
}
}

Only show a payment method that appears in capabilities.payments. Only enable purchase submission when capabilities.purchasing_ready is true.

Minimal purchase flow

The shortest complete flow is:

  1. Read a catalog response and save its purchase.quote_item object.
  2. Synchronize the customer and their Abstract wallet.
  3. Create a quote with the untouched quote_item.
  4. Complete USDC or Stripe payment.
  5. Poll the order or receive signed webhook events until fulfillment is complete.
  6. For a Box, complete the returned customer-wallet commit/finalize flow.
  7. When the customer wants physical delivery, create a redemption.

Catalog, quote, and payment data must be treated as server-authoritative. Do not calculate the payable amount or copy token/recipient addresses into application configuration.

Catalog

Catalog results are filtered by the partner's saved configuration and do not expose DYLI-internal identity fields. Purchasable rows include an instruction like:

{
"purchase": {
"supported": true,
"action": "buy",
"reason": null,
"quote_item": {
"type": "listing",
"listing_id": "primary:12345",
"quantity": 1
}
}
}

Pass purchase.quote_item to POST /quotes without constructing a listing identifier yourself.

Available catalog surfaces include:

Method and pathPurpose
GET /catalog/explorePartner-filtered storefront inventory
GET /catalog/listingsPrimary and secondary buy-now listings
GET /catalog/secondary-listingsCurrent secondary listings for one product/token
GET /catalog/boxesBoxes that can be purchased and opened
GET /catalog/ebayApproved external marketplace inventory
GET /catalog/searchCross-catalog search
GET /catalog/collectionsActive partner collections
GET /catalog/fair-dropsFair Drop discovery
GET /catalog/digital-packsDigital Pack discovery
GET /catalog/redemptionsPublic redemption activity
GET /catalog/optionsAllowed brand/category filter values

Fair Drops, Digital Packs, collections, and redemption activity may be discovery-only. Check each row's purchase.supported, purchase.action, and purchase.reason.

Customer identity and delivery wallet

Your application owns login and the stable external customer ID. A purchase customer needs an Abstract EVM wallet because assets are delivered to that wallet's DYLI Collection.

curl -X PUT \
"https://www.dyli.io/api/commerce/v1/customers/customer_123" \
-H "x-api-key: $DYLI_API_KEY" \
-H "x-partner-slug: $DYLI_PARTNER_SLUG" \
-H "content-type: application/json" \
-d '{
"wallet_address": "0xCUSTOMER_ABSTRACT_WALLET",
"wallet_chain": "abstract",
"email": "buyer@example.com",
"name": "Example Buyer",
"partner_auth": {
"provider": "your-auth-system",
"subject": "auth-user-123"
}
}'

The same wallet or partner-auth identity cannot be linked to two external customers in one partner namespace. Treat external_customer_id as permanent.

The customer wallet and payment wallet are deliberately separate:

  • customer.wallet_address is the Abstract destination for the purchased asset.
  • payment.payer is the wallet that funded and authorized a USDC order.

This supports embedded wallets for customers and a partner-controlled treasury wallet for payment.

Create a quote

Every mutation needs an idempotency key. Reusing a key with the same request returns the prior result; reusing it with different input returns 409 idempotency_conflict.

curl -X POST "https://www.dyli.io/api/commerce/v1/quotes" \
-H "x-api-key: $DYLI_API_KEY" \
-H "x-partner-slug: $DYLI_PARTNER_SLUG" \
-H "Idempotency-Key: quote_customer_123_cart_456" \
-H "content-type: application/json" \
-d '{
"external_customer_id": "customer_123",
"items": [
{ "type": "listing", "listing_id": "primary:12345", "quantity": 1 }
]
}'

Use the purchase.quote_item returned by the chosen catalog row. Other supported quote-item types are box, secondary-listing, and ebay.

The response includes the server-priced quote and current payment instructions:

{
"quote": {
"id": "4b975557-7d53-4d52-b240-40ceac59eead",
"external_customer_id": "customer_123",
"currency": "USDC",
"items": [],
"price_breakdown": {
"subtotal_cents": 2500,
"partner_fee_cents": 125,
"total_cents": 2625,
"subtotal": 25,
"partner_fee": 1.25,
"total": 26.25,
"currency": "USDC",
"fee_label": "Service fee"
},
"status": "open",
"expires_at": "2026-08-14T18:10:00.000Z"
},
"payment": {
"amount": "26.25",
"currency": "USDC",
"recipients": {},
"tokens": {},
"supported_chains": []
}
}

Prices and inventory are checked again during payment finalization. A successful quote is not an inventory reservation.

USDC payment

USDC payment supports Abstract, Avalanche, Ethereum, Base, Arbitrum, Optimism, Polygon, HyperEVM, Monad, and Solana. The quote response provides the current recipient and token addresses for each chain.

The API requires both onchain payment evidence and a signature from the wallet that funded the payment. This binds the payment to the partner, quote, customer/fulfillment intent, chain, payer, transaction hash, and quote expiry.

Partner treasury wallet

A partner may collect money from customers in its own checkout or ledger and pay DYLI from a pooled treasury wallet. This works without moving the purchased asset through the treasury wallet:

  1. Your system charges or debits the customer using your own payment arrangement.
  2. Your treasury sends the exact quote total in USDC to the chain-specific recipient returned by DYLI.
  3. The treasury signs the DYLI authorization message.
  4. Your server sends the transaction hash, treasury address, and signature to DYLI.
  5. DYLI fulfills the asset to customer.wallet_address, not to the treasury.

This is a noncustodial treasury flow, not a prefunded balance held by DYLI. Your system remains responsible for customer charges, refunds, ledgering, and reconciliation. Never use one transaction for multiple DYLI orders; a payment transaction can be consumed once.

Authorize and create the order

After the transfer is confirmed, request the exact message the payer must sign:

curl -X POST \
"https://www.dyli.io/api/commerce/v1/quotes/$QUOTE_ID/payment-authorization" \
-H "x-api-key: $DYLI_API_KEY" \
-H "x-partner-slug: $DYLI_PARTNER_SLUG" \
-H "content-type: application/json" \
-d '{
"external_customer_id": "customer_123",
"customer": {
"external_customer_id": "customer_123",
"wallet_address": "0xCUSTOMER_ABSTRACT_WALLET"
},
"fulfillment": { "mode": "vault" },
"payment": {
"chain": "base",
"tx_hash": "0xPAYMENT_TRANSACTION_HASH",
"payer": "0xPARTNER_TREASURY_WALLET"
}
}'

Sign authorization.message exactly as returned:

  • EVM externally owned account: EIP-191 personal_sign
  • EVM smart wallet: EIP-1271 contract signature
  • Solana: Ed25519 signMessage, encoded as base58 or base64

Then create the order with the same customer, fulfillment, chain, transaction, and payer:

curl -X POST "https://www.dyli.io/api/commerce/v1/orders" \
-H "x-api-key: $DYLI_API_KEY" \
-H "x-partner-slug: $DYLI_PARTNER_SLUG" \
-H "Idempotency-Key: order_customer_123_checkout_456" \
-H "content-type: application/json" \
-d '{
"quote_id": "4b975557-7d53-4d52-b240-40ceac59eead",
"external_order_id": "your-order-456",
"external_customer_id": "customer_123",
"customer": {
"external_customer_id": "customer_123",
"wallet_address": "0xCUSTOMER_ABSTRACT_WALLET",
"email": "buyer@example.com"
},
"fulfillment": { "mode": "vault" },
"payment": {
"chain": "base",
"tx_hash": "0xPAYMENT_TRANSACTION_HASH",
"payer": "0xPARTNER_TREASURY_WALLET",
"payer_signature": "0xSIGNED_AUTHORIZATION_MESSAGE"
}
}'

For Solana, the transaction parser verifies a parsed SPL-token transfer from token accounts owned by the signed payer into the configured recipient. For EVM chains, DYLI verifies successful USDC Transfer logs, recipient, amount, sender, and confirmations.

Card payment

Stripe Checkout is created from an open quote. DYLI adds a 3% plus $0.33 card-processing fee to the quoted total.

Hosted Checkout:

curl -X POST \
"https://www.dyli.io/api/commerce/v1/quotes/$QUOTE_ID/stripe-checkout" \
-H "x-api-key: $DYLI_API_KEY" \
-H "x-partner-slug: $DYLI_PARTNER_SLUG" \
-H "Idempotency-Key: stripe_customer_123_checkout_456" \
-H "content-type: application/json" \
-d '{
"external_customer_id": "customer_123",
"external_order_id": "your-order-456",
"customer": {
"external_customer_id": "customer_123",
"wallet_address": "0xCUSTOMER_ABSTRACT_WALLET",
"email": "buyer@example.com"
},
"fulfillment": { "mode": "vault" },
"ui_mode": "hosted",
"success_url": "https://store.example/checkout/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://store.example/checkout/cancelled"
}'

Redirect the browser to checkout.checkout_url. For embedded Checkout, send ui_mode: "embedded" and an HTTPS return_url, then use the returned client_secret and publishable_key with Stripe.js.

DYLI finalizes paid sessions from the Stripe webhook. The success page should also call your server, which may call:

POST /stripe-checkouts/{payment_session_id}/confirm
GET /stripe-checkouts/{payment_session_id}

Both paths independently retrieve and verify the Stripe Checkout Session and PaymentIntent. A browser redirect by itself is never accepted as payment evidence.

Orders and asynchronous fulfillment

Paid order creation returns an order immediately. Fulfillment is asynchronous.

GET /orders/{order_id}
GET /orders?external_customer_id=customer_123&limit=25&offset=0

Important fields:

FieldMeaning
statusOverall state: normally paid, processing, completed, or failed
fulfillment_statusQueue/executor state
delivery.wallet_addressAbstract wallet receiving the item
payment.senderVerified funding wallet
dyli_order_idsDYLI fulfillment records after processing
errorStructured action/retry information when present

Do not mark an order fulfilled in your application only because payment succeeded. Wait for fulfillment_status: "completed" or an order.completed webhook.

How it appears in DYLI

Every checkout is saved as a Commerce order. DYLI also links the customer wallet to a DYLI user:

  • If the wallet already belongs to a DYLI user, the order uses that user.
  • Otherwise, DYLI creates a wallet-linked customer record. This does not automatically create a public username or login.

Before fulfillment can complete, DYLI verifies that the normal DYLI order exists, succeeded, and belongs to that customer. The purchase then appears in the customer's Collection and DYLI sales/activity like a purchase made on dyli.io. The normal order IDs are returned in dyli_order_ids.

Boxes

A Box order contains exactly one Box. After payment, the customer must send two zero-value Abstract transactions from the same customer wallet:

  1. POST /box-plays with the paid order_id returns the commit transaction.
  2. Send the exact transaction object through the customer wallet.
  3. POST /box-plays/{id}/commit with its transaction hash returns or prepares finalization.
  4. Send the returned finalize transaction.
  5. POST /box-plays/{id}/finalize verifies the transaction and completes the order.

Never rebuild calldata, replace the from wallet, or change the target/value. Treat the returned transaction as an opaque customer-wallet instruction.

Shipping and redemptions

Purchases always enter Collection first. Physical shipping is a separate customer-authorized Abstract redemption:

Method and pathPurpose
POST /redemptions/address-validationNormalize and validate the address
POST /redemptionsVerify Collection ownership and quote live shipping rates
GET /redemptions/{id}Read the redemption state
POST /redemptions/{id}/prepareReprice shipping, select rates, and return an Abstract transaction
POST /redemptions/{id}/confirmVerify the customer transaction and create fulfillment records

The redemption quote is idempotent and expires. Rate selection and any item-specific collection responses are submitted at prepare time. The customer sends the exact returned transaction from redemption.wallet_address.

Partner configuration and webhooks

Read or update partner configuration:

GET /config
PATCH /config

Configuration includes display_name, branding, catalog_rules, fee_rules, and webhook_url. A fee can be percentage, fixed, or fixed-plus-percentage with optional minimum/maximum limits.

Setting a webhook URL for the first time, or sending rotate_webhook_secret: true, returns webhook_secret once. Store it immediately.

Webhook requests include:

x-dyli-event-id: EVENT_UUID
x-dyli-event-type: order.completed
x-dyli-timestamp: 1786720800
x-dyli-signature: v1=HEX_HMAC_SHA256

Verify HMAC-SHA256(secret, timestamp + "." + raw_request_body) with a constant-time comparison. Reject stale timestamps and deduplicate by x-dyli-event-id before changing state. Respond with a 2xx quickly; process the event asynchronously.

Use GET /events for reconciliation. Event delivery is at-least-once, so consumers must be idempotent.

Retries, limits, and errors

  • Send Idempotency-Key for quote, order, Stripe Checkout, and redemption creation.
  • Persist the key and request body together until the operation reaches a terminal state.
  • Retry 409 payment_pending after the chain confirms.
  • Retry 429 after the Retry-After header.
  • Retry 502/503 with capped exponential backoff and jitter.
  • Do not automatically retry validation errors or idempotency conflicts with the same payload.
  • Log the x-request-id response header and request_id error field for support.

Errors use this shape:

{
"error": "payment_pending",
"message": "Payment needs more confirmations",
"details": { "confirmations": 0, "required_confirmations": 1 },
"request_id": "7473e546-60b3-4d30-8520-4accbf5a57df"
}

Commerce keys have an in-process API limit plus a durable per-key read/write limit. Rate-limit headers are included in responses. Contact support@dyli.io before a planned traffic increase.

Testing

Use the integration testing guide. An integrator only needs the lab base URL, API key, and partner slug supplied by DYLI. DYLI owns the lab database, Stripe test account, chain simulation, and fulfillment isolation. Test payments are rejected in production.