Reseller API

Sell AuraStack products from your own shop, app or bot. Browse the catalog, get a quote, buy from your prepaid AuraStack balance, and receive the product credentials in the response.

Build it with your AI agent

Put your sandbox key in your project's environment as AURASTACK_API_KEY, then paste this prompt into Claude Code, Cursor, Codex or any agent that can read a web page. The agent is told never to use a live key unless you say so.

Prompt for your agent
Integrate the AuraStack Reseller API into this project.

1. First read https://aurastack.link/llms.txt and follow the links it gives: the guide (https://aurastack.link/docs/api.md), the OpenAPI contract (https://aurastack.link/api/v1/openapi.json) and the agent skill (https://aurastack.link/skills/aurastack-reseller/SKILL.md). Follow the skill's rules.
2. Use only the sandbox key in the AURASTACK_API_KEY environment variable (it starts with sk_sandbox_). Never use or ask for a live key, and never place a live order, unless I say so.
3. Build: product list, quote before checkout, order creation with one Idempotency-Key per purchase, delivery of the credentials to the buyer, and handling of every order status and error.
4. Add end-to-end tests against the sandbox: call POST /sandbox/reset first, then cover each AuraStack-Sandbox-Outcome value.
5. Never log or print delivery.items or the API key.

When you are done, tell me what you built, how to run the tests, and what I must do to go live.

The agent starts from https://aurastack.link/llms.txt, a short map written for agents. It links this guide as plain markdown (/docs/api.md), the OpenAPI contract (/api/v1/openapi.json) and the AuraStack skill. Building by hand? Start with the Quickstart, or try every endpoint in the API reference.

Claude Code: install the skill so the rules load in every session
mkdir -p ~/.claude/skills/aurastack-reseller && \
curl -fsSL https://aurastack.link/skills/aurastack-reseller/SKILL.md \
  -o ~/.claude/skills/aurastack-reseller/SKILL.md

Quickstart

  1. Sign in and open your dashboard. In the Sandbox panel, click Create sandbox key. The key is shown once: store it in a secret manager.
  2. List products, quote the one you want, then create the order with the quoted unit price. Sandbox orders are never charged.
  3. Read the credentials from the order response, or poll the order until it is delivered.
  4. When your integration works, create a live key under Live API Keys, top up your balance, and switch the key.

Request

export AURASTACK_API_KEY=sk_sandbox_...

curl https://aurastack.link/api/v1/balance \
  -H "Authorization: Bearer $AURASTACK_API_KEY"

Response 200

{
  "client_id": "...",
  "available_balance": "1000.00",
  "reserved_balance": "0.00",
  "currency": "EUR",
  "sandbox": true
}

Base URL: https://aurastack.link/api/v1. Requests and responses are JSON. Amounts are strings with two decimals, in the currency of the response: euros for everything since October 2026 (older orders show USD and vat: null).

Authentication

Send your key as a bearer token on every request: Authorization: Bearer <key>. A missing or unknown key returns 401; do not retry it.

Key
Sandbox key
Prefix
sk_sandbox_
What it does
Same API, never charged, sandbox balance. One per account; roll it from the dashboard.
Key
Live key
Prefix
sk_live_ (older keys sk_aura_)
What it does
Real orders, charged to your balance. Up to 10 per account.
Keep keys server-side. A live key spends your balance. Never put any key in browser code, a mobile app, a public repository, a URL or a log. Live keys cannot be deleted from the dashboard yet: if one leaks, contact us right away through the chat on aurastack.link and we will disable it.

Sandbox

A full copy of the live API that never costs money. Build your integration and run your end-to-end tests against it, then switch one key to go live.

URL and endpoints
Sandbox key
Same
Live key
Same
Products and quotes
Sandbox key
Catalog prices and stock (cached for about a minute: a live quote can differ by a few cents)
Live key
Live quote prices and stock
Validation, VAT, idempotency
Sandbox key
Same rules as live
Live key
Same rules
Balance
Sandbox key
Sandbox balance: starts at 1000.00 EUR, goes down with each order, refunds add back
Live key
Your real balance
Orders
Sandbox key
Stored and readable, statuses change over time like live. Never charged. Credentials look like SANDBOX-7F2A-...
Live key
Charged. Real credentials
Who sees the orders
Sandbox key
Your sandbox key only
Live key
Your live keys only
Balance, quote and order responses
Sandbox key
Same shape, with "sandbox": true
Live key
Same shape, with "sandbox": false

Sandbox writes (quotes, orders, resets) are limited to 120 per minute per account, and sandbox orders to 2000 per 24 hours (a reset does not lift it). A quantity outside the product's limits fails like live (500).

Force an outcome

A sandbox order is delivered at once, unless your sandbox balance is too low (then 402, like live). To test the other cases, send the AuraStack-Sandbox-Outcome header when you create the order. It is read on creation only (not on replays), and live keys ignore it.

Header value
delivered (default)
What you get
200, status "delivered" with sandbox credentials
What your code should do
Hand the credentials to your customer
Header value
processing
What you get
200, status "processing". About 30 seconds later the order is delivered
What your code should do
Poll GET /orders/{id} every 10 to 30 seconds
Header value
on_hold
What you get
502 with the order, status "failed", failure_code "NEEDS_REVIEW". Charged to the sandbox balance
What your code should do
Tell your customer it is being checked. Do not refund yet
Header value
refunded
What you get
502 with the order, status "refunded". The sandbox balance gets the amount back
What your code should do
Refund or re-order for your customer
Header value
insufficient_balance
What you get
402 payment_error, whatever the balance
What your code should do
Top up, then order with a new Idempotency-Key
Header value
price_changed
What you get
409 price_changed with the current unit_price
What your code should do
Quote again and confirm with your customer
Header value
out_of_stock
What you get
409 invalid_request_error
What your code should do
Offer another product

End-to-end tests

Put your sandbox key in your CI secrets and run your real code paths against the sandbox (it follows real stock: use a product your catalog shows in stock). Start each run with POST /sandbox/reset: it puts the sandbox balance back to 1000.00 EUR and clears your sandbox orders, so every run starts from the same state. Live keys get 403 on it.

e2e/aurastack.test.js (Node 20+, any test runner)

const API = 'https://aurastack.link/api/v1';
const H = { Authorization: `Bearer ${process.env.AURASTACK_SANDBOX_KEY}`, 'Content-Type': 'application/json' };
const call = (path, init = {}) => fetch(API + path, { ...init, headers: { ...H, ...init.headers } });
const quote = async () => (await call('/quotes', { method: 'POST',
  body: JSON.stringify({ product_id: 'prd_IQCRJMV8ww5aozXi', quantity: 1 }) })).json();
const order = (q, headers) => call('/orders', { method: 'POST', headers,
  body: JSON.stringify({ product_id: q.product_id, quantity: 1,
    expected_unit_price: q.unit_price, billing: { country: 'FR' } }) });

beforeAll(() => call('/sandbox/reset', { method: 'POST' }));

test('a paid order is delivered and charged', async () => {
  const res = await order(await quote(), { 'Idempotency-Key': 'e2e-1' });
  const placed = await res.json();
  expect(placed.status).toBe('delivered');
  const balance = await (await call('/balance')).json();
  expect(balance.available_balance).toBe((1000 - Number(placed.total_amount)).toFixed(2));
});

test('my shop handles an order on hold', async () => {
  const res = await order(await quote(), { 'Idempotency-Key': 'e2e-2', 'AuraStack-Sandbox-Outcome': 'on_hold' });
  expect(res.status).toBe(502);
  expect((await res.json()).failure_code).toBe('NEEDS_REVIEW');
});

Balance

GET/api/v1/balance

Your prepaid balance. available_balance is what orders can spend. reserved_balance is always "0.00": orders are charged immediately.

Products

GET/api/v1/products

The products you can buy, with your price. Query parameters: limit (1 to 100, default 100) and cursor. Pass next_cursor back as cursor to get the next page, until it is null. A page can hold fewer items than limit; keep following the cursor.

Response 200

{
  "items": [
    {
      "id": "prd_8Kq2mZt4Wb7xLn3R",
      "name": "Gemini Google One AI Pro 5TB, 18 months",
      "description": "...",
      "unit_price": "21.50",
      "currency": "EUR",
      "available_stock": 296,
      "minimum_quantity": 1,
      "maximum_quantity": 100,
      "updated_at": "2026-10-06T11:31:03.131Z"
    }
  ],
  "next_cursor": "..."
}

unit_price is per unit, in euros, before VAT. available_stock can be null when stock is not tracked. Ignore fields not listed here.

GET/api/v1/products/{id}

One product, same fields. 404 if the product is not sold here. An id that does not exist at all can return 500: check it against the list.

Quotes

POST/api/v1/quotes

The current price for a quantity. Quote right before ordering and send the quoted unit_price with the order.

Request

curl -X POST https://aurastack.link/api/v1/quotes \
  -H "Authorization: Bearer $AURASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "product_id": "prd_8Kq2mZt4Wb7xLn3R", "quantity": 2 }'

Response 200

{
  "product_id": "prd_8Kq2mZt4Wb7xLn3R",
  "quantity": 2,
  "unit_price": "21.50",
  "total_amount": "43.00",
  "currency": "EUR",
  "available_stock": 296,
  "can_purchase": true,
  "quoted_at": "2026-10-06T12:00:00.000Z"
}

Amounts are before VAT; VAT is added when you order (see VAT). If can_purchase is false, do not order. A quote does not reserve the price or the stock: the price is confirmed only when you order. Ignore fields not listed here.

Orders

POST/api/v1/orders

Buy a product with your balance. The price is always ours: expected_unit_price only confirms it. If it no longer matches, nothing is charged and you get 409 price_changed with the current unit_price. Send an Idempotency-Key with every order (see Idempotency and retries).

Request

curl -X POST https://aurastack.link/api/v1/orders \
  -H "Authorization: Bearer $AURASTACK_API_KEY" \
  -H "Idempotency-Key: my-order-1001" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "prd_8Kq2mZt4Wb7xLn3R",
    "quantity": 2,
    "expected_unit_price": "21.50",
    "billing": { "country": "FR" }
  }'

Buying as a company: add "is_company": true, "company_name": "...", "vat_number": "..." to billing, with your real tax number (it is checked).

Field
product_id
Required
yes
Description
From the catalog.
Field
quantity
Required
yes
Description
Integer from 1 to 100, within the product's minimum_quantity and maximum_quantity.
Field
expected_unit_price
Required
yes
Description
The quote's unit_price (euros, before VAT).
Field
billing.country
Required
yes
Description
Your billing country, 2-letter ISO 3166-1 code such as FR or US.
Field
billing.is_company
Required
no
Description
JSON boolean true if you buy as a company (the string "true" counts as false).
Field
billing.company_name
Required
if company
Description
Your company name.
Field
billing.vat_number
Required
if company
Description
Your tax number. EU VAT numbers start with the billing country's prefix (for example FR12345678901, EL for Greece) and are checked with the EU register (VIES); others are not checked.

The billing details are yours: you are our customer. total_amount is what your balance is charged, VAT included, and is the amount to rely on. total_amount minus vat.amount can differ from unit_price times quantity by a cent.

Response 200

{
  "id": "api_3f9c2a71d0b84e6a5c1d7e02",
  "product_id": "prd_8Kq2mZt4Wb7xLn3R",
  "quantity": 2,
  "status": "delivered",
  "unit_price": "21.50",
  "total_amount": "51.60",
  "vat": { "rate": 20, "amount": "8.60", "country": "FR" },
  "currency": "EUR",
  "delivery": { "items": ["XXXX-XXXX-XXXX-XXXX", "YYYY-YYYY-YYYY-YYYY"] },
  "failure_code": null,
  "failure_message": null,
  "created_at": "2026-10-06T12:00:01.000Z",
  "delivered_at": "2026-10-06T12:00:03.000Z"
}

Most orders are delivered in the response. If status is processing, poll the order (below) every 10 to 30 seconds until it is delivered. Deliver the items to your customer securely and never write them to logs.

GET/api/v1/orders/{id}

One of your orders, same shape as above. Reading a processing order also checks whether it has been delivered since.

Webhooks are not available yet. To follow an order, poll GET /api/v1/orders/{id}.

VAT

VAT is decided from your billing and, when your billing country is outside the EU, from the country your server calls us from. The order's vat shows what was applied.

You are
Billed outside the EU (Monaco counts as France), calling from a server outside the EU
VAT
None
You are
Billed outside the EU, calling from a server in the EU
VAT
20% (a tax ID from outside the EU cannot be verified; contact us with proof to reclaim it)
You are
A company in another EU country with a VAT number confirmed by VIES
VAT
None (reverse charge)
You are
A company in France
VAT
20%
You are
Anyone else billed in the EU
VAT
20% French VAT (vat.country is FR)

If the EU register (VIES) is unavailable when you order, the order goes through with VAT; contact us to reclaim it. A VAT number the register rejects returns 400.

Order statuses

status
pending_payment
Charged?
Not yet
What to do
Payment did not complete. Retry the same request with the same Idempotency-Key.
status
payment_captured
Charged?
Yes
What to do
Delivery is starting. Poll the order.
status
processing
Charged?
Yes
What to do
Delivery in progress. Poll the order.
status
delivered
Charged?
Yes
What to do
Final. Read delivery.items.
status
failed with failure_code: "INSUFFICIENT_BALANCE"
Charged?
No
What to do
Final. Top up, then order again with a new Idempotency-Key.
status
failed, any other code
Charged?
Yes
What to do
Our team resolves it: it ends delivered or refunded. Do not refund your customer yet.
status
refunded
Charged?
Refunded
What to do
Final. The amount is back on your balance.

Idempotency and retries

Send an Idempotency-Key header on every order: one unique key per customer purchase, for example your own order number. If the connection drops or you get a 503, retry the identical request with the same key. You are never charged twice: the retry returns the existing order. Without a key, a retry can create a second order.

Only product_id and quantity are compared: reusing a key with a different product or quantity returns 422 idempotency_error, and with any other change it returns the original order. A key whose order failed for insufficient balance keeps returning that result: use a new key after topping up.

Errors

Errors return { "error": { "message": "...", "type": "..." } }. Branch on the HTTP status and type.

HTTP
400
type
invalid_request_error
When
Missing or invalid field, or billing rejected (for example a VAT number VIES does not confirm).
What to do
Fix the request.
HTTP
401
type
authentication_error
When
Missing or invalid key.
What to do
Check the key. Do not retry.
HTTP
402
type
payment_error
When
Balance too low. Nothing was charged.
What to do
Top up, order again with a new Idempotency-Key.
HTTP
403
type
invalid_request_error
When
A live key called POST /sandbox/reset.
What to do
Use your sandbox key.
HTTP
404
type
invalid_request_error
When
Product not sold here (products, quotes), or an order that is not yours.
What to do
Check the id.
HTTP
409
type
price_changed
When
The price moved. error.unit_price has the new one. Nothing was charged.
What to do
Quote again and confirm with your customer.
HTTP
409
type
invalid_request_error
When
Out of stock or unavailable. Nothing was charged.
What to do
Offer another product. Do not retry right away.
HTTP
422
type
idempotency_error
When
Idempotency-Key reused for a different product or quantity.
What to do
Use a new key.
HTTP
429
type
rate_limit_error
When
Company orders (is_company with a vat_number), counted per attempt, are limited to 30 per 10 minutes per server IP. Sandbox: 120 writes per minute, 2000 orders per 24 hours. Nothing was charged.
What to do
Wait, then retry with the same Idempotency-Key.
HTTP
500
type
api_error or internal_error
When
Unexpected error, or a product id or quantity the product does not accept.
What to do
Retry a few times with backoff and the same Idempotency-Key. If it persists, check product_id against the catalog.
HTTP
502
type
the order
When
The order failed or was refunded. You may have been charged.
What to do
Read status and failure_code (see Order statuses).
HTTP
503
type
api_error
When
Payment outcome not confirmed.
What to do
Retry with the same Idempotency-Key. You will not be charged twice.