> ## Documentation Index
> Fetch the complete documentation index at: https://docs-test.rye.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Single Step Checkout

> Create and confirm an order in a single API call using Rye's fire-and-forget Purchase endpoint.

export const OpenWithLLM = ({prompt, tools}) => {
  const handleClick = (tool, e) => {
    const btn = e.currentTarget;
    const labelSpan = btn.querySelector('[data-label]');
    const copiedLabel = btn.getAttribute('data-copied');
    if (tool === 'claude') {
      window.open(`https://claude.ai/new?q=${encodeURIComponent(prompt)}`, '_blank');
      return;
    }
    if (tool === 'lovable') {
      window.open(`https://lovable.dev/?autosubmit=true#prompt=${encodeURIComponent(prompt)}`, '_blank');
      return;
    }
    navigator.clipboard.writeText(prompt);
    if (labelSpan && copiedLabel) {
      const original = labelSpan.textContent;
      labelSpan.textContent = copiedLabel;
      setTimeout(() => {
        labelSpan.textContent = original;
      }, 2500);
    }
    if (tool === 'chatgpt') {
      window.open('https://chatgpt.com/', '_blank');
    }
  };
  const show = tool => !tools || tools.includes(tool);
  const buttonBase = "inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 border cursor-pointer";
  return <div className="my-6 rounded-xl border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 p-5">
      <div className="flex items-center gap-2 mb-3">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-gray-500 dark:text-gray-400">
          <path d="M12 2L2 7l10 5 10-5-10-5z" />
          <path d="M2 17l10 5 10-5" />
          <path d="M2 12l10 5 10-5" />
        </svg>
        <span className="text-sm font-semibold text-gray-700 dark:text-gray-200">Build this with AI</span>
      </div>
      <p className="text-xs text-gray-500 dark:text-gray-400 mb-4">
        Open a pre-built prompt in your preferred AI tool to generate a complete working integration.
      </p>
      <div className="flex flex-wrap gap-2">
        {show('claude') && <button onClick={e => handleClick('claude', e)} data-copied="Opening..." className={`${buttonBase}`}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path d="M4.709 15.955l4.72-2.756.08-.046 2.698-1.575c.092-.054.153-.063.2-.063.046 0 .107.01.199.063l2.698 1.575 4.8 2.802c.026.014.04.022.064.037l-7.552 4.41a.46.46 0 01-.232.063.46.46 0 01-.232-.063L4.709 15.955zm7.897-12.42l7.585 4.428a.471.471 0 01.233.408v8.747c0 .03-.003.057-.01.094l-4.812-2.81-2.698-1.574a.384.384 0 01-.199-.327V3.535zm-1.212 0v8.966a.384.384 0 01-.2.327L8.498 14.4l-4.812 2.81a.519.519 0 01-.01-.095V8.37c0-.17.09-.326.233-.408l7.485-4.428z" />
            </svg>
            <span data-label>Open in Claude</span>
          </button>}

        {show('lovable') && <button onClick={e => handleClick('lovable', e)} data-copied="Opening..." className={`${buttonBase}`}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
            </svg>
            <span data-label>Open in Lovable</span>
          </button>}

        {show('chatgpt') && <button onClick={e => handleClick('chatgpt', e)} data-copied="Copied! Paste in ChatGPT" className={`${buttonBase}`}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path d="M22.282 9.821a5.985 5.985 0 00-.516-4.91 6.046 6.046 0 00-6.51-2.9A6.065 6.065 0 0011.708.414a6.116 6.116 0 00-5.89 4.412 6.005 6.005 0 00-4.014 2.91 6.048 6.048 0 00.749 7.084 5.98 5.98 0 00.516 4.911 6.051 6.051 0 006.511 2.9 6.056 6.056 0 004.548 1.956 6.116 6.116 0 005.889-4.411 6.006 6.006 0 004.014-2.912 6.043 6.043 0 00-.749-7.084zM13.109 21.95a4.582 4.582 0 01-2.94-1.063c.037-.02.102-.056.144-.081l4.884-2.822a.794.794 0 00.4-.688v-6.892l2.064 1.192a.073.073 0 01.04.056v5.707a4.601 4.601 0 01-4.592 4.592zM3.6 17.938a4.556 4.556 0 01-.546-3.072c.036.021.099.06.144.084l4.884 2.822a.796.796 0 00.79 0l5.964-3.444v2.384a.074.074 0 01-.03.063L9.88 19.615a4.6 4.6 0 01-6.28-1.677zM2.338 7.875a4.56 4.56 0 012.394-2.007c0 .042-.003.114-.003.164v5.645a.788.788 0 00.4.688l5.964 3.443-2.064 1.192a.075.075 0 01-.071.006L4.032 14.16a4.6 4.6 0 01-1.694-6.284zM18.93 11.99l-5.965-3.443 2.065-1.192a.074.074 0 01.07-.006l4.927 2.844a4.588 4.588 0 01-.71 8.282v-5.797a.793.793 0 00-.387-.688zm2.056-3.08c-.037-.022-.1-.06-.144-.084L15.96 6.004a.795.795 0 00-.79 0L9.205 9.448V7.064a.073.073 0 01.03-.063l4.925-2.843a4.591 4.591 0 016.826 4.752zM8.114 12.544L6.05 11.352a.073.073 0 01-.04-.057V5.59a4.59 4.59 0 017.533-3.523c-.037.02-.102.055-.144.08L8.514 4.97a.794.794 0 00-.4.688v6.886zm1.12-2.418l2.657-1.534 2.656 1.534v3.068l-2.656 1.534-2.657-1.534V10.126z" />
            </svg>
            <span data-label>Open in ChatGPT</span>
          </button>}

        {show('cursor') && <button onClick={e => handleClick('cursor', e)} data-copied="Copied! Paste in Composer" className={`${buttonBase}`}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path d="M3.147 2.245a.6.6 0 01.652-.054l17.4 9.6a.6.6 0 01.014 1.046l-6.9 4.027-4.182 5.178a.6.6 0 01-1.075-.232L3.013 2.858a.6.6 0 01.134-.613zM10.17 15.2l3.563-2.08L5.34 4.44l4.83 10.76zm.894 1.09l-1.1 1.362 1.357 4.58 2.83-3.504-3.087-2.438z" />
            </svg>
            <span data-label>Copy for Cursor</span>
          </button>}

        {show('vscode') && <button onClick={e => handleClick('vscode', e)} data-copied="Copied! Paste in Copilot Chat" className={`${buttonBase}`}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
              <path d="M17.583 2.603l-5.402 4.914L6.968 3.26 4.2 4.86v14.28l2.768 1.6 5.213-4.257 5.402 4.914L21.6 19.2V4.8l-4.017-2.197zM17.4 17.063l-4.392-3.994 4.392-3.994v7.988zM6.6 15.674V8.326l3.695 3.674L6.6 15.674z" />
            </svg>
            <span data-label>Copy for Copilot</span>
          </button>}

        {show('claude-code') && <button onClick={e => handleClick('claude-code', e)} data-copied="Copied! Paste in Claude Code" className={`${buttonBase}`}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <polyline points="4 17 10 11 4 5" />
              <line x1="12" y1="19" x2="20" y2="19" />
            </svg>
            <span data-label>Copy for Claude Code</span>
          </button>}
      </div>
    </div>;
};

export const llmPrompt = `Build a complete, runnable Next.js application that integrates Rye's Universal Checkout API using the single-step Purchase endpoint — a fire-and-forget API call that creates and confirms an order in one request. Use the official Rye TypeScript SDK (checkout-intents) for all API calls.

## What Rye Does
Rye is a commerce API that lets you sell products from any merchant (Amazon, Shopify, etc.) in your app. The Purchase endpoint combines creating and confirming an order in a single call, with Rye handling offer retrieval, payment authorization, and order placement asynchronously.

## Architecture
IMPORTANT: All Rye SDK calls MUST happen server-side. Never import or use the checkout-intents SDK in client components — the API key must stay on the server.
- Frontend (client components): Form UI, status display. Calls internal Next.js API routes via fetch.
- Backend (Next.js Route Handlers in app/api/): Uses the checkout-intents SDK to talk to Rye. The API key never leaves the server.

## SDK Setup (server-side only)
Install: npm install checkout-intents
Environment variables (server-side, not prefixed with NEXT_PUBLIC_):
  CHECKOUT_INTENTS_API_KEY — the SDK reads this by default
  CHECKOUT_INTENTS_BASE_URL=https://staging.api.rye.com (for staging)

In a server-side file (e.g. lib/rye.ts or an API route):
import CheckoutIntents from 'checkout-intents';
const client = new CheckoutIntents(); // reads CHECKOUT_INTENTS_API_KEY from env

## API Route Structure
app/api/checkout/purchase/route.ts  — POST: receives buyer + productUrl + paymentMethod from frontend, calls SDK purchase, returns intent
app/api/checkout/get-intent/route.ts — GET: receives intent id, calls SDK, returns current state

## Single-Step Checkout Using SDK (server-side)

### Submit order (fire-and-forget, in API route)
const intent = await client.checkoutIntents.purchase({
  buyer: {
    firstName: 'John', lastName: 'Doe',
    email: 'john.doe@example.com', phone: '212-333-2121',
    address1: '123 Main St',
    city: 'New York', province: 'NY',
    postalCode: '10001', country: 'US',
  },
  paymentMethod: { stripeToken: 'tok_visa', type: 'stripe_token' },
  productUrl: 'https://flybyjing.com/collections/shop/products/sichuan-chili-crisp',
  quantity: 1,
  // Optional price constraints:
  // constraints: {
  //   maxShippingPrice: { amountSubunits: 1000, currencyCode: 'USD' },
  //   maxTotalPrice: { amountSubunits: 5000, currencyCode: 'USD' },
  // },
});

### Poll for final state (in API route)
const completed = await client.checkoutIntents.pollUntilCompleted(intent.id);
// completed.state === 'completed' means order placed successfully
// completed.state === 'failed' means something went wrong

### Error handling
import CheckoutIntents, { PollTimeoutError } from 'checkout-intents';
try { ... } catch (error) {
  if (error instanceof PollTimeoutError) { /* polling timed out */ }
  if (error instanceof CheckoutIntents.APIError) { /* API error with status, message */ }
}
If intent.state === 'failed', check intent.failureReason.code and intent.failureReason.message.

## Important Notes
- Test payment token: tok_visa (Stripe test token, works in staging)
- US shipping addresses only
- Polling helpers accept options: { pollIntervalMs: 5000, maxAttempts: 120 }

## App Requirements
- Next.js App Router with TypeScript and Tailwind CSS
- Use the checkout-intents SDK for ALL Rye API calls (do not use raw fetch)
- ALL SDK usage must be in server-side code (Route Handlers or Server Actions) — never in client components
- CHECKOUT_INTENTS_API_KEY must never be exposed to the browser
- Frontend client components call internal /api/ routes, which use the SDK server-side
- A form for: product URL, buyer details (name, email, phone, address), quantity
- Optional fields for max shipping price and max total price constraints
- Show real-time order status: submitting → processing → completed/failed
- Use SDK polling helpers (pollUntilCompleted) to check intent state after submission
- Handle error states and display failureReason if the order fails
- Environment variables (server-side): CHECKOUT_INTENTS_API_KEY, CHECKOUT_INTENTS_BASE_URL
- Include a README with setup instructions

Generate all files needed to run the app. Make sure the .gitignore includes .env and .env.* so secrets are never committed to version control. After generating the code, ask the user for their Rye API key so you can update the .env or .env.local file with the actual CHECKOUT_INTENTS_API_KEY value. Also confirm which environment they want to use (staging or production) so you can set CHECKOUT_INTENTS_BASE_URL accordingly.`;

<OpenWithLLM prompt={llmPrompt} />

This quickstart shows you how to submit an order in a single API call through our [Purchase endpoint](https://docs.rye.com/api-v2-experimental/api-reference/post-purchase--single-step-checkout).

It’s a fire-and-forget endpoint that combines creating and confirming an order in a single API call, letting our system handle offer retrieval, payment authorization, and order placement asynchronously. This simplifies your workflow and reduces integration complexity.

It’s ideal for automating routine purchases, processing high-volume asynchronous orders, or integrating into workflows that don’t require synchronous user approval.

## Step 1: Create a Rye Account

Create a [Rye staging account](https://staging.console.rye.com/register) to get an API key and submit a test order.

## Step 2: Get Your API Key

Go to the [Account tab in your Rye staging account](https://staging.console.rye.com/account), and copy the **Staging API Key**.

Set it as an environment variable:

```bash theme={null}
export RYE_API_KEY="YOUR_API_KEY"
```

## Step 3: Submit an Order

Send a `POST` request to `/api/v1/checkout-intents/purchase` with the product URL, buyer identity, and [payment method](https://docs.rye.com/api-v2/payment-providers).

<Info>This guide uses Stripe, but Rye supports other payment providers too. See [Payment Providers](/api-v2/payment-providers) for all options.</Info>

<CodeGroup>
  ```bash curl theme={null}
  curl https://staging.api.rye.com/api/v1/checkout-intents/purchase \
      -H 'Content-Type: application/json' \
      -H "Authorization: Bearer $CHECKOUT_INTENTS_API_KEY" \
      -d '{
            "buyer": {
              "address1": "123 Main St",
              "city": "New York",
              "country": "US",
              "email": "john.doe@example.com",
              "firstName": "John",
              "lastName": "Doe",
              "phone": "212-333-2121",
              "postalCode": "10001",
              "province": "NY"
            },
            "paymentMethod": {
              "stripeToken": "tok_visa",
              "type": "stripe_token"
            },
            "productUrl": "https://flybyjing.com/collections/shop/products/the-big-boi",
            "quantity": 1
          }'
  ```

  ```javascript TypeScript SDK theme={null}
  import CheckoutIntents from 'checkout-intents';

  const client = new CheckoutIntents({
    apiKey: process.env['CHECKOUT_INTENTS_API_KEY'],
  });

  const checkoutIntent = await client.checkoutIntents.purchase({
    buyer: {
      address1: '123 Main St',
      city: 'New York',
      country: 'US',
      email: 'john.doe@example.com',
      firstName: 'John',
      lastName: 'Doe',
      phone: '212-333-2121',
      postalCode: '10001',
      province: 'NY',
    },
    paymentMethod: { stripeToken: 'tok_visa', type: 'stripe_token' },
    productUrl: 'https://flybyjing.com/collections/shop/products/the-big-boi',
    quantity: 1,
  });

  console.log(checkoutIntent);
  ```

  ```python Python SDK theme={null}
  import os
  from checkout_intents import CheckoutIntents

  api_key = os.environ["RYE_API_KEY"]

  client = CheckoutIntents(api_key)

  checkout_intent = client.checkout_intents.create(
      buyer={
          "address1": "123 Main St",
          "city": "New York",
          "country": "US",
          "email": "john.doe@example.com",
          "first_name": "John",
          "last_name": "Doe",
          "phone": "212-333-2121",
          "postal_code": "10001",
          "province": "NY",
      },
      product_url="https://flybyjing.com/collections/shop/products/the-big-boi",
      quantity=1,
  )
  ```

  ```ruby Ruby SDK theme={null}
  require "checkout_intents"

  checkout_intents = CheckoutIntents::Client.new(
    api_key: ENV["CHECKOUT_INTENTS_API_KEY"],
    environment: "staging"
  )

  checkout_intent = checkout_intents.checkout_intents.purchase(
    buyer: {
      address1: "123 Main St",
      city: "New York",
      country: "US",
      email: "john.doe@example.com",
      firstName: "John",
      lastName: "Doe",
      phone: "212-333-2121",
      postalCode: "10001",
      province: "NY"
    },
    payment_method: {stripeToken: "tok_visa", type: "stripe_token"},
    product_url: "https://flybyjing.com/collections/shop/products/the-big-boi",
    quantity: 1
  )
  ```

  ```java Java SDK theme={null}
  import com.rye.client.CheckoutIntentsClient;
  import com.rye.client.okhttp.CheckoutIntentsOkHttpClient;
  import com.rye.models.checkoutintents.Buyer;
  import com.rye.models.checkoutintents.CheckoutIntent;
  import com.rye.models.checkoutintents.CheckoutIntentPurchaseParams;
  import com.rye.models.checkoutintents.PaymentMethod;

  CheckoutIntentsClient client = CheckoutIntentsOkHttpClient.fromEnv();

  CheckoutIntentPurchaseParams params = CheckoutIntentPurchaseParams.builder()
      .buyer(Buyer.builder()
          .address1("123 Main St")
          .city("New York")
          .country("US")
          .email("john.doe@example.com")
          .firstName("John")
          .lastName("Doe")
          .phone("212-333-2121")
          .postalCode("10001")
          .province("NY")
          .build())
      .paymentMethod(PaymentMethod.StripeTokenPaymentMethod.builder()
          .stripeToken("tok_visa")
          .type(PaymentMethod.StripeTokenPaymentMethod.Type.STRIPE_TOKEN)
          .build())
      .productUrl("https://flybyjing.com/collections/shop/products/the-big-boi")
      .quantity(1)
      .build();

  CheckoutIntent checkoutIntent = client.checkoutIntents().purchase(params);
  ```
</CodeGroup>

<Note>
  You can send orders to Amazon, Shopify, or other merchants by simply changing the product URL. Example test URLs:

  * [https://www.raakachocolate.com/products/blueberry-lemon?variant=41038993227863](https://www.raakachocolate.com/products/blueberry-lemon?variant=41038993227863)
  * [https://www.amazon.com/Apple-MX532LL-A-AirTag/dp/B0CWXNS552/](https://www.amazon.com/Apple-MX532LL-A-AirTag/dp/B0CWXNS552/)
</Note>

<Tip>
  The optional fields `constraints.maxShippingPrice` and `constraints.maxTotalPrice` let you set the maximum shipping cost and total amount the shopper is willing to pay. If either exceeds the specified values, the order won’t be processed. More details are [available here](https://docs.rye.com/api-v2-experimental/api-reference/post-purchase--single-step-checkout#body-constraints-max-shipping-price).
</Tip>

<Warning>
  Orders can only be shipped to **U.S. addresses**. International shipping is not yet supported. Using a non-US address will cause the checkout intent to fail.
</Warning>

## Step 4: Poll for Final State

Poll the `GET` endpoint until the checkout intent reaches a terminal state:

* `completed`: order placed successfully
* `failed`: something went wrong (e.g. out of stock, expired)

<CodeGroup>
  ```bash curl theme={null}
  curl --request GET \
    --url https://staging.api.rye.com/api/v1/checkout-intents/{id} \
    --header "Authorization: Basic $RYE_API_KEY"
  ```

  ```javascript TypeScript SDK theme={null}
  const checkoutIntent = await client.checkoutIntents.retrieve('id');
  ```

  ```python Python SDK theme={null}
  checkout_intent = client.checkout_intents.retrieve("id")
  ```

  ```java Java SDK theme={null}
  CheckoutIntent checkoutIntent = client.checkoutIntents().retrieve("id");
  ```

  ```ruby Ruby SDK theme={null}
  checkout_intent = checkout_intents.checkout_intents.retrieve("id")
  ```
</CodeGroup>

Example responses:

```json theme={null}
{ "state": "completed" }
```

```json theme={null}
{
  "state": "failed",
  "failureReason": {
    "code": "product_out_of_stock",
    "message": "The item is no longer available."
  }
}
```

<Note>
  Rye does not provide post-purchase tracking or webhooks. Tracking and order updates are sent directly to the buyer's email address.
</Note>

## Next Steps

Congrats — you just submitted your first Rye order!

If you want to check shipping options, taxes, total cost, and product availability before submitting an order, please [review this guide for the steps](https://docs.rye.com/api-v2/example-flows/simple-checkout).

Next, check out the [API Comparison](/api-v2/api-comparison) guide to learn about the differences between the Universal Checkout API and the Sync API.
