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

# First checkout

> Create and confirm a test USD hosted checkout.

## Prerequisites

Copy the API URL from the merchant dashboard and create a test key with
`checkout:create` and `checkout:read`. The read scope is required by the status
confirmation step below. A webhook-only integration that never calls a checkout
read endpoint can use only `checkout:create`. Store both values as server-side
environment variables:

```bash theme={null}
SKINLOOP_API_BASE_URL="YOUR_SKINLOOP_API_URL"
SKINLOOP_API_KEY="YOUR_SKINLOOP_API_KEY"
```

Never expose the API key in browser JavaScript.

## Create a checkout

<CodeGroup>
  ```bash cURL theme={null}
  : "${SKINLOOP_API_BASE_URL:?Set SKINLOOP_API_BASE_URL}"
  : "${SKINLOOP_API_KEY:?Set SKINLOOP_API_KEY}"
  ORDER_ID="order_123"
  ATTEMPT="1"
  IDEMPOTENCY_KEY="${ORDER_ID}_checkout_attempt_${ATTEMPT}"
  curl --fail-with-body "$SKINLOOP_API_BASE_URL/v1/merchant-api/checkouts" \
    -H "Authorization: Bearer $SKINLOOP_API_KEY" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    -H "Content-Type: application/json" \
     -d "{\"merchantOrderId\":\"$ORDER_ID\",\"amount\":{\"value\":4999,\"currency\":\"USD\"},\"allowedGames\":[\"cs2\",\"rust\"],\"successUrl\":\"https://YOUR_REGISTERED_ORIGIN.example/paid\",\"cancelUrl\":\"https://YOUR_REGISTERED_ORIGIN.example/canceled\"}"
  ```

  ```ts TypeScript theme={null}
  const baseUrl = process.env.SKINLOOP_API_BASE_URL;
  const apiKey = process.env.SKINLOOP_API_KEY;
  if (!baseUrl || !apiKey) throw new Error("Skinloop API configuration is missing");

  const orderId = "order_123";
  const attempt = 1;
  const idempotencyKey = `${orderId}_checkout_attempt_${attempt}`;
  const response = await fetch(`${baseUrl}/v1/merchant-api/checkouts`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Idempotency-Key": idempotencyKey,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      merchantOrderId: orderId,
      amount: { value: 4999, currency: "USD" },
      allowedGames: ["cs2", "rust"],
      successUrl: "https://YOUR_REGISTERED_ORIGIN.example/paid",
      cancelUrl: "https://YOUR_REGISTERED_ORIGIN.example/canceled"
    })
  });
  if (!response.ok) throw new Error(`Skinloop returned HTTP ${response.status}`);
  const checkout = await response.json();
  console.log(checkout.hostedUrl);
  ```

  ```python Python theme={null}
  import os, requests
  base_url = os.environ["SKINLOOP_API_BASE_URL"]
  order_id, attempt = "order_123", 1
  response = requests.post(
    f"{base_url}/v1/merchant-api/checkouts",
    headers={"Authorization": f"Bearer {os.environ['SKINLOOP_API_KEY']}",
             "Idempotency-Key": f"{order_id}_checkout_attempt_{attempt}"},
    json={"merchantOrderId":order_id,"amount":{"value":4999,"currency":"USD"},
          "allowedGames":["cs2","rust"],
          "successUrl":"https://YOUR_REGISTERED_ORIGIN.example/paid",
          "cancelUrl":"https://YOUR_REGISTERED_ORIGIN.example/canceled"})
  response.raise_for_status()
  checkout = response.json()
  print(checkout["hostedUrl"])
  ```

  ```php PHP theme={null}
  $baseUrl = $_ENV["SKINLOOP_API_BASE_URL"]
    ?? throw new RuntimeException("SKINLOOP_API_BASE_URL is missing");
  $apiKey = $_ENV["SKINLOOP_API_KEY"]
    ?? throw new RuntimeException("SKINLOOP_API_KEY is missing");
  $orderId = "order_123";
  $attempt = 1;
  $idempotencyKey = "{$orderId}_checkout_attempt_{$attempt}";
  $response = file_get_contents($baseUrl."/v1/merchant-api/checkouts", false,
    stream_context_create(["http" => ["method" => "POST", "header" =>
    "Authorization: Bearer ".$apiKey."\r\nIdempotency-Key: ".$idempotencyKey."\r\nContent-Type: application/json\r\n",
    "content" => json_encode(["merchantOrderId"=>$orderId,"amount"=>["value"=>4999,"currency"=>"USD"],
    "allowedGames"=>["cs2","rust"],"successUrl"=>"https://YOUR_REGISTERED_ORIGIN.example/paid",
    "cancelUrl"=>"https://YOUR_REGISTERED_ORIGIN.example/canceled"])] ]));
  if ($http_response_header[0] < 1 || !str_contains($http_response_header[0], " 2")) {
    throw new RuntimeException("Skinloop request failed");
  }
  $checkout = json_decode($response, true);
  echo $checkout["hostedUrl"];
  ```
</CodeGroup>

## Redirect the customer

Send the customer to `hostedUrl` from the response. The checkout also includes
its stable ID and expiration time.

## Confirm payment

Do not fulfill an order from the browser redirect alone. Confirm a `completed`
status through the checkout status endpoint or a signed webhook.

<Tip>
  Use a unique `Idempotency-Key` for each checkout attempt. Repeating the same
  request with the same key returns the original checkout instead of creating a
  duplicate.
</Tip>

The response includes `id`, `hostedUrl`, `expiresAt`, `status`, and `warnings`.
Store the checkout ID with your order. A new request using the same key but
different parameters returns an idempotency conflict; use a new key for a new
attempt.

**Expected result:** a customer can open the hosted URL and complete an
eligible CS2 or Rust deposit. **Next:** read [Checkout status](/checkout/status)
and [Webhooks](/webhooks/overview) before shipping.
