# Quickstart

URL: /docs/quickstart

Call the API with your store key, list products, create a cart, add a line and take a test payment, in about five minutes.

This walk-through makes the same calls a storefront makes: read the catalogue, start a cart, put something in it and check out. Every request runs from a terminal or a server, never from a browser, because it carries your server secret.

You need an Elara One store. If you do not have one yet, [get started](/signup) or [book a demo](/contact).

<Steps>
<Step>

## Copy your keys [#copy-your-keys]

In your store admin, open **Settings → Storefront API**. Copy the **store key** (it starts `pk_live_`, or `pk_test_` on a staging store) and reveal the **server secret**. Put both in your shell or your server's environment:

```bash title="Terminal"
export ELARAONE_TENANT_KEY="pk_live_…"
export ELARAONE_TENANT_SECRET="…"
```

<Callout type="warn" title="Keep the secret on the server">
  The server secret lets a caller act as your storefront server. Never put it in browser code or in a public environment variable such as `NEXT_PUBLIC_*`. See [Authentication](/docs/authentication).
</Callout>

</Step>
<Step>

## Check the connection [#check-the-connection]

`GET /v1/ping` answers for your store when the key and secret are right.

```bash tab="cURL"
curl https://api.elaraone.app/api/v1/ping \
  -H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
  -H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET"
```

```ts tab="TypeScript"
const API = 'https://api.elaraone.app/api/v1'

// Every call from your server carries both headers.
const headers = {
  'X-Tenant-Key': process.env.ELARAONE_TENANT_KEY!,
  'X-Tenant-Secret': process.env.ELARAONE_TENANT_SECRET!,
}

const ping = await fetch(`${API}/ping`, { headers })
console.log(ping.status, await ping.json())
```

A wrong or missing key answers with an [error](/docs/concepts/errors) such as `TENANT_KEY_MISSING` (400) or `TENANT_NOT_FOUND` (404).

</Step>
<Step>

## List products [#list-products]

Ask for variants and their prices with `include`. Prices are [money objects](/docs/concepts/money) in pence.

```bash tab="cURL"
curl -G https://api.elaraone.app/api/v1/products \
  -H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
  -H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET" \
  --data-urlencode "include=variants.prices,images" \
  --data-urlencode "perPage=3"
```

```ts tab="TypeScript"
const params = new URLSearchParams({ include: 'variants.prices,images', perPage: '3' })
const res = await fetch(`${API}/products?${params}`, { headers })
const { data: products } = await res.json()

const variant = products[0].variants[0]
console.log(products[0].name, variant.sku, variant.price.formatted)
```

```json title="200 OK (trimmed)"
{
  "data": [
    {
      "id": 101,
      "status": "published",
      "name": "Oak desk lamp",
      "slug": "oak-desk-lamp",
      "variants": [
        {
          "id": 1543,
          "sku": "LAMP-OAK-NAT",
          "stock": 12,
          "shippable": true,
          "price": { "amount": 8900, "currency": "GBP", "formatted": "£89.00" }
        }
      ],
      "images": [{ "id": 55, "url": "https://…/oak-desk-lamp.webp" }]
    }
  ],
  "links": { "first": "…", "last": "…", "prev": null, "next": "…" },
  "meta": { "current_page": 1, "per_page": 3, "total": 24 }
}
```

Note a variant's `id` or `sku`: you add variants, not products, to a cart. Filters, sorting and paging are in [Catalogue](/docs/concepts/catalogue).

</Step>
<Step>

## Create a cart [#create-a-cart]

`POST /v1/cart` returns a `cart_token`. Send it as `X-Cart-Token` on every cart and checkout call. Keep it on your server, for example in an httpOnly cookie.

```bash tab="cURL"
curl -X POST https://api.elaraone.app/api/v1/cart \
  -H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
  -H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET"
```

```ts tab="TypeScript"
const created = await fetch(`${API}/cart`, { method: 'POST', headers })
const { data } = await created.json()
const cartToken: string = data.cart_token
```

```json title="201 Created"
{
  "data": {
    "cart_token": "01JB9K3N7Q2W5X8Z4C6V1T0R3M",
    "cart": {
      "id": 812,
      "currency": "GBP",
      "lines": [],
      "total": { "amount": 0, "currency": "GBP", "formatted": "£0.00" }
    }
  }
}
```

</Step>
<Step>

## Add a line [#add-a-line]

Add a variant by `variant_id` or `sku`. The `Idempotency-Key` makes a retried request safe: the line is added once, however many times the request is sent.

```bash tab="cURL"
export CART_TOKEN="01JB9K3N7Q2W5X8Z4C6V1T0R3M"

curl -X POST https://api.elaraone.app/api/v1/cart/lines \
  -H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
  -H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET" \
  -H "X-Cart-Token: $CART_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "variant_id": 1543, "quantity": 1 }'
```

```ts tab="TypeScript"
const added = await fetch(`${API}/cart/lines`, {
  method: 'POST',
  headers: {
    ...headers,
    'X-Cart-Token': cartToken,
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ variant_id: variant.id, quantity: 1 }),
})
const { data: cart } = await added.json()
console.log(cart.total.formatted)
```

Every cart write returns the whole cart, recalculated: lines, promotions, tax and totals. See [Carts and the cart token](/docs/concepts/carts) for coupons, addresses and shipping.

</Step>
<Step>

## Take a test payment [#take-a-test-payment]

A checkout sets the addresses and a shipping option, then pays and places the order:

1. `PUT /v1/cart/addresses`, then `GET /v1/cart/shipping-options` and `PUT /v1/cart/shipping-option`.
2. `POST /v1/checkout/payment-intent` returns a Stripe `client_secret`. Your page mounts Stripe's Payment Element with it, and the shopper confirms the payment in the browser.
3. Your server calls `POST /v1/checkout/complete` with the PaymentIntent and an `Idempotency-Key`. It answers `201` with the placed order.

```bash title="Place the order"
curl -X POST https://api.elaraone.app/api/v1/checkout/complete \
  -H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
  -H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET" \
  -H "X-Cart-Token: $CART_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "payment": { "gateway": "stripe", "payment_intent": "pi_…" } }'
```

When `GET /v1/checkout/config` reports `"gateway_mode": "test"`, payments go to Stripe's test mode: pay with Stripe's test card `4242 4242 4242 4242`, any future expiry date and any CVC. A declined card answers `402 PAYMENT_FAILED` with a `decline_code` you can show the shopper.

The [Checkout with Stripe](/docs/guides/stripe-checkout) guide builds this end to end, including the browser code.

</Step>
</Steps>

## Next steps [#next-steps]

<Cards>
  <Card title="Choose a starter" href="/docs/starters" description="Start from a working storefront in Next.js, Nuxt or Astro + Alpine." />
  <Card title="Product listing with filters" href="/docs/guides/product-listing" description="Collections, price ranges, attributes, sorting and paging." />
  <Card title="Checkout with Stripe" href="/docs/guides/stripe-checkout" description="Addresses, shipping, the Payment Element and placing the order." />
  <Card title="Errors" href="/docs/concepts/errors" description="The error envelope and every code, with what to do about each." />
</Cards>
