Make your first request

A first Channel API call, a dry-run write and a first Store API call against the sandbox, in cURL and Node.

This page takes a sandbox API key to a first answer from each API. The examples use the sandbox hosts; in production, swap .dev for .live.

Before you start

  • Access to a tenant in the sandbox at https://kentro.cc. Questions about access go to [email protected].
  • For the Channel API: an API key whose permission group holds channel:read, plus customers:write for the dry run. See API keys and scopes.
  • For the Store API: an API key whose permission group holds storefront:serve, and the host of a published storefront.
  • For the Node examples: Node.js 18 or later (built-in fetch). Save each example as a .mjs file so top-level await works, and run it with node <file>.mjs.

Export the values once:

export KENTRO_KEY="<key from the API keys dialog>"
export KENTRO_SECRET="<secret from the API keys dialog>"
export STOREFRONT_HOST="shop.example.com"

1. Check reachability

curl -s https://apis.kentro.dev/channel/v1/ping

{ "ok": true, "service": "channel-api" } means the host answers. The ping takes no credentials.

2. A first Channel API call

List the tenant's sales channels. The ids are the channelId values every Channel API document carries.

curl -s https://apis.kentro.dev/channel/v1/channels \
  -H "kentro-key: $KENTRO_KEY" \
  -H "kentro-secret: $KENTRO_SECRET"
const res = await fetch('https://apis.kentro.dev/channel/v1/channels', {
  headers: {
    'kentro-key': process.env.KENTRO_KEY,
    'kentro-secret': process.env.KENTRO_SECRET,
  },
});
const body = await res.json();
if (!res.ok) {
  console.error(res.status, JSON.stringify(body), res.headers.get('x-request-id'));
  process.exit(1);
}
for (const channel of body.data) console.log(channel.id, channel.type, channel.name);

The answer:

{
  "data": [
    {
      "id": "64f0aabb-ccdd-1122-3344-000100000000",
      "name": "Web store",
      "type": "woocommerce",
      "isActive": true,
      "currency": "USD"
    }
  ],
  "nextCursor": null,
  "limit": 50
}

nextCursor: null means this is the last page. Lists are cursor based and newest first; see Channel API: Requests and responses.

3. A first write, as a dry run

X-Dry-Run: true runs a write in full, answers what it would have produced, and rolls everything back: nothing is stored and no event is recorded. Set CHANNEL_ID to an id from step 2.

curl -s -i -X POST https://apis.kentro.dev/channel/v1/customers \
  -H "kentro-key: $KENTRO_KEY" \
  -H "kentro-secret: $KENTRO_SECRET" \
  -H "Idempotency-Key: first-customer-0001" \
  -H "X-Dry-Run: true" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "'"$CHANNEL_ID"'",
    "externalId": "first-customer-0001",
    "firstName": "Ada",
    "lastName": "Lovelace",
    "email": "[email protected]",
    "onDuplicate": "update"
  }'
const res = await fetch('https://apis.kentro.dev/channel/v1/customers', {
  method: 'POST',
  headers: {
    'kentro-key': process.env.KENTRO_KEY,
    'kentro-secret': process.env.KENTRO_SECRET,
    'Idempotency-Key': 'first-customer-0001',
    'X-Dry-Run': 'true',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    channelId: process.env.CHANNEL_ID,
    externalId: 'first-customer-0001',
    firstName: 'Ada',
    lastName: 'Lovelace',
    email: '[email protected]',
    onDuplicate: 'update',
  }),
});
console.log(res.status, res.headers.get('x-dry-run'), JSON.stringify(await res.json(), null, 2));

A 201 with the response header X-Dry-Run: applied and "outcome": "created" in the body shows the request is valid. Without the X-Dry-Run header, the same request creates the customer; sent again, it answers 200 with outcome: updated or unchanged, because onDuplicate: update makes the create an upsert.

4. A first Store API call

store.get reads the published storefront behind a host. It needs the API key and the host, nothing else. X-API-Key carries the key's secret.

curl -s -X POST https://apis.kentro.dev/store/v1/store.get \
  -H "X-API-Key: $KENTRO_SECRET" \
  -H "X-Storefront-Host: $STOREFRONT_HOST" \
  -H "Content-Type: application/json" \
  -d '{}'
const res = await fetch('https://apis.kentro.dev/store/v1/store.get', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.KENTRO_SECRET,
    'X-Storefront-Host': process.env.STOREFRONT_HOST,
    'Content-Type': 'application/json',
  },
  body: '{}',
});
const store = await res.json();
if (!res.ok) {
  console.error(res.status, JSON.stringify(store));
  process.exit(1);
}
console.log(store.name, store.access, store.currency, store.guestCheckout);

An excerpt of the answer:

{
  "id": "0b6c2e51-5f1e-4a4f-8d0e-6a3d5c2f9a10",
  "name": "Example Wholesale",
  "access": "approval",
  "guestCheckout": false,
  "publicPrices": false,
  "paymentProvider": "stripe",
  "fulfillment": ["shipping", "pickup"],
  "currency": "USD"
}

access, publicPrices, guestCheckout and currency decide what a storefront renders before anyone signs in. Every amount the Store API answers is an integer in the minor unit of currency. See Store API: Getting started.

When the first call fails

AnswerCauseFix
401 { "error": { "code": "unauthenticated", ... } }Channel API: no credentials reached the API.Send kentro-key and kentro-secret.
401 { "error": "INVALID_REQUEST", ... }Store API: no X-API-Key.Send the key's secret as X-API-Key.
401 { "error": "INVALID_CREDENTIALS", ... }Wrong or disabled key, wrong secret, or a key from the other environment.Check the values and the host.
403 { "error": { "code": "forbidden", ... } }The key lacks the scope named in message.Add the scope to the key's permission group.
403 { "code": "FORBIDDEN", "message": "This API key cannot serve a storefront." }The key's groups hold neither storefront:serve nor admin.Add storefront:serve.
400 { "code": "VALIDATION", ... } on store.getX-Storefront-Host is missing.Send the published host.
404 { "code": "NOT_FOUND", "message": "No published storefront answers this host." }The storefront is not published, or the host belongs to the other environment.Publish it, or check the host.

Next: Errors, retries and rate limits, then the integration guide for what you are building, starting from the developer center.


Did this page help you?