Quickstart
Make a complete sandbox purchase, from the user's approval to a placed order, in eight requests.
You'll approve a test user, look up a product, build a checkout, place the order and read it back. Everything here runs in the sandbox: no real store is contacted and nothing is charged.
1. Get a sandbox key
Sign in to the dashboard, open API keys and create a sandbox key. It starts with hk_test_ and is shown once, so put it in your environment:
export HARBOR_KEY=hk_test_...Every request below sends it as Authorization: Bearer $HARBOR_KEY. Requests with a body send JSON.
2. Create an approval token
An approval token starts Harbor Approve for one of your users. client_user_id is your own id for them. The rules are the limits the user will see and can change; amounts are in cents.
curl https://www.useharbor.io/v1/approval_tokens \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "content-type: application/json" \
-d '{
"client_user_id": "user_123",
"rules": { "max_per_order": 15000, "confirm_above": 7500 }
}'const harbor = (path, body, userToken) =>
fetch(`https://www.useharbor.io/v1${path}`, {
method: body ? "POST" : "GET",
headers: {
authorization: `Bearer ${process.env.HARBOR_KEY}`,
"content-type": "application/json",
...(userToken ? { "harbor-user-token": userToken } : {}),
},
body: body ? JSON.stringify(body) : undefined,
}).then((r) => r.json());
const approval = await harbor("/approval_tokens", {
client_user_id: "user_123",
rules: { max_per_order: 15000, confirm_above: 7500 },
});import os, requests
def harbor(path, body=None, user_token=None):
headers = {"authorization": f"Bearer {os.environ['HARBOR_KEY']}"}
if user_token:
headers["harbor-user-token"] = user_token
url = f"https://www.useharbor.io/v1{path}"
if body is None:
return requests.get(url, headers=headers).json()
return requests.post(url, json=body, headers=headers).json()
approval = harbor("/approval_tokens", {
"client_user_id": "user_123",
"rules": {"max_per_order": 15000, "confirm_above": 7500},
}){
"approval_token": "approval-sandbox-9Zk…",
"expiration": "2026-09-24T18:30:00.000Z",
"hosted_url": "https://www.useharbor.io/approve?token=approval-sandbox-9Zk…"
}In production you open hosted_url for the user. In the sandbox you can skip the screen.
3. Approve without the screen (sandbox)
curl https://www.useharbor.io/v1/sandbox/public_token \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "content-type: application/json" \
-d '{ "approval_token": "approval-sandbox-9Zk…" }'const { public_token } = await harbor("/sandbox/public_token", { approval_token: approval.approval_token });public_token = harbor("/sandbox/public_token", {"approval_token": approval["approval_token"]})["public_token"]4. Exchange it for a user token
The public_token is short-lived. Exchange it on your server for a user_token, and store that against your user. It authorizes purchases for this user within their rules.
curl https://www.useharbor.io/v1/approval_tokens/exchange \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "content-type: application/json" \
-d '{ "public_token": "public-sandbox-f3Q…" }'const { user_token } = await harbor("/approval_tokens/exchange", { public_token });user_token = harbor("/approval_tokens/exchange", {"public_token": public_token})["user_token"]{ "user_token": "user-sandbox-Lq8…", "user_id": "usr_4n4363bk6m0x516172" }5. Look up an offer
Pass any product link. sbx_widget is a sandbox product that always succeeds.
curl https://www.useharbor.io/v1/offers \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "content-type: application/json" \
-d '{ "url": "https://woo.sandbox.harbor.local/products/sbx_widget" }'const offer = await harbor("/offers", { url: "https://woo.sandbox.harbor.local/products/sbx_widget" });offer = harbor("/offers", {"url": "https://woo.sandbox.harbor.local/products/sbx_widget"}){
"id": "ofr_071f4m634y1x453v055k",
"merchant_id": "sbx_woo_store",
"url": "https://woo.sandbox.harbor.local/products/sbx_widget",
"title": "Sandbox Widget",
"price": { "amount": 1999, "currency": "USD" },
"availability": "in_stock",
"item_ref": "sbx_widget",
"fetched_at": "2026-09-24T18:00:00.000Z",
"expires_at": "2026-09-24T18:15:00.000Z"
}Offers last 15 minutes. After that, look the product up again for a fresh price.
6. Create a checkout
Checkouts act for a user, so send their token in the Harbor-User-Token header.
curl https://www.useharbor.io/v1/checkouts \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: user-sandbox-Lq8…" \
-H "content-type: application/json" \
-d '{
"offer_id": "ofr_071f4m634y1x453v055k",
"quantity": 1,
"shipping_address": {
"name": "Sam Shopper", "line1": "1 Test St", "city": "Chicago",
"region": "IL", "postal_code": "60601", "country": "US"
}
}'const checkout = await harbor("/checkouts", {
offer_id: offer.id,
quantity: 1,
shipping_address: { name: "Sam Shopper", line1: "1 Test St", city: "Chicago", region: "IL", postal_code: "60601", country: "US" },
}, user_token);checkout = harbor("/checkouts", {
"offer_id": offer["id"],
"quantity": 1,
"shipping_address": {"name": "Sam Shopper", "line1": "1 Test St", "city": "Chicago",
"region": "IL", "postal_code": "60601", "country": "US"},
}, user_token){
"id": "chk_162g7258534z6g3e1o5k",
"status": "ready_to_confirm",
"route": "browser",
"line_items": [{ "item_ref": "sbx_widget", "title": "Sandbox Widget", "quantity": 1, "unit_price": { "amount": 1999, "currency": "USD" } }],
"totals": {
"subtotal": { "amount": 1999, "currency": "USD" },
"shipping": { "amount": 500, "currency": "USD" },
"tax": { "amount": 160, "currency": "USD" },
"total": { "amount": 2659, "currency": "USD" }
},
"requires_user_confirmation": false
}ready_to_confirm means the total is under the user's limits and they don't need to confirm it. When something else has to happen first, the status is requires_user_action and next_action says what. See Checkouts.
7. Place the order
curl -X POST https://www.useharbor.io/v1/checkouts/chk_162g7258534z6g3e1o5k/confirm \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: user-sandbox-Lq8…"const placed = await fetch(`https://www.useharbor.io/v1/checkouts/${checkout.id}/confirm`, {
method: "POST",
headers: { authorization: `Bearer ${process.env.HARBOR_KEY}`, "harbor-user-token": user_token },
}).then((r) => r.json());placed = requests.post(
f"https://www.useharbor.io/v1/checkouts/{checkout['id']}/confirm",
headers={"authorization": f"Bearer {os.environ['HARBOR_KEY']}", "harbor-user-token": user_token},
).json()The checkout comes back completed with an order_id.
8. Read the order
curl https://www.useharbor.io/v1/orders/ord_0n2k29185t5p1u04644a \
-H "Authorization: Bearer $HARBOR_KEY" \
-H "Harbor-User-Token: user-sandbox-Lq8…"const order = await harbor(`/orders/${placed.order_id}`, undefined, user_token);order = harbor(f"/orders/{placed['order_id']}", user_token=user_token){
"id": "ord_0n2k29185t5p1u04644a",
"checkout_id": "chk_162g7258534z6g3e1o5k",
"merchant_id": "sbx_woo_store",
"merchant_order_ref": "SBX-35918059",
"status": "placed",
"total": { "amount": 2659, "currency": "USD" }
}Open Checkouts in the dashboard to see a step-by-step replay of what you just did.
Next
- Approve users for real, with the hosted screen.
- Handle every next action a checkout can ask for.
- Get webhooks instead of polling.