Agent skills
Install Kentro agent skills in Claude Code, Codex and Cursor so agents follow Kentro's rules when they write integration code.
Agent skills are instruction files an AI agent loads when a task matches them. Kentro publishes three, each a SKILL.md file in the Agent Skills format: YAML front matter with a name and a description that says when to use it, then the procedure, the rules and the references. They carry the same rules as Instructions for AI agents, plus the steps and checks for one product.
| Skill | Use it for |
|---|---|
kentro-channel-api | Channel API integrations: marketplaces, carts, ERP, accounting, WMS, 3PL and retailer purchase orders |
kentro-store-api | Custom storefronts on the Store API |
kentro-pixel | Kentro Pixel in the browser and from a server |
Get the files
The three files are at the end of this page. Copy each one with its code block's copy button, or let your agent download them:
Fetch https://docs.kentro.io/docs/ai-agent-skills.md and save each skill file from that page at .claude/skills/<name>/SKILL.md in this repository.
Claude Code
Claude Code loads skills from two places:
| Location | Who gets the skill |
|---|---|
.claude/skills/<name>/SKILL.md in the repository | Everyone who works in the repository, once it is committed |
~/.claude/skills/<name>/SKILL.md | You, in every project |
Create one folder per skill, named after it, and save each file as SKILL.md in its folder:
mkdir -p .claude/skills/kentro-channel-api .claude/skills/kentro-store-api .claude/skills/kentro-pixel
Then start a new Claude Code session. Claude Code reads each skill's description and uses the skill when a request matches it, for example "Import our marketplace orders into Kentro".
Codex
Codex reads instructions from AGENTS.md files: ~/.codex/AGENTS.md for every project, AGENTS.md at the repository root, and AGENTS.md in a subfolder for that part of the tree. Add a Kentro section with the body of each skill the repository needs, meaning everything after its front matter:
## Kentro
When a task touches a Kentro API, follow https://docs.kentro.io/docs/ai-agent-instructions.md.
(Paste the body of the kentro-channel-api skill here.)
Paste only the skills the repository needs, so AGENTS.md stays short.
Cursor
Cursor reads project rules from .cursor/rules/. Save each skill as a rule file and keep its description, so Cursor's agent attaches the rule when a request matches it:
---
description: Build, change or debug an integration on the Kentro Channel API (copy the full description from the skill)
globs:
alwaysApply: false
---
(Paste the body of the kentro-channel-api skill here.)
Other agents
Any agent that accepts custom instructions can take the body of a skill, or the URL https://docs.kentro.io/docs/ai-agent-instructions.md.
Keep the skills current
The skills follow the APIs. When the Channel API changelog or the Store API changelog announces a change, download the files again and replace them.
Skill files
kentro-channel-api
Save as .claude/skills/kentro-channel-api/SKILL.md.
---
name: kentro-channel-api
description: Build, change or debug an integration on the Kentro Channel API (apis.kentro.live/channel/v1), which moves a sales channel's products, customers, orders, fulfillment orders, shipments, invoices, payments and purchase orders into and out of Kentro. Use for marketplace, shopping cart, ERP, accounting, WMS, 3PL and retailer purchase order integrations, and whenever code calls apis.kentro.live/channel or apis.kentro.dev/channel.
---
# Kentro Channel API
The Channel API is how a system puts a sales channel's documents into Kentro and reads them back. Kentro is the system of record; the integration is a system acting with an API key. One resource per endpoint, JSON in and out, every write idempotent.
## Before writing code
1. Look up every endpoint and field in the API Reference: the Markdown pages under References. Never guess a field name.
2. Ask the user for:
- A sandbox API key pair created in Kentro OS at https://kentro.cc under `Users` > `API keys`, with the narrowest scopes the job needs and bound to the sales channels it serves. The user stores it as `KENTRO_KEY` and `KENTRO_SECRET`. Never ask for it in the chat, and never ask for a Kentro password.
- The sales channel id (`GET /v1/channels` lists them).
- Which system owns each document type, and which way it flows.
## Request basics
| | Production | Sandbox |
|---|---|---|
| Base URL | `https://apis.kentro.live/channel/v1` | `https://apis.kentro.dev/channel/v1` |
| Keys created at | `https://kentro.app` | `https://kentro.cc` |
Send the key pair on every request:
```http
kentro-key: <key>
kentro-secret: <secret>
```
Or exchange it once for a bearer token: `POST https://apis.kentro.live/auth/login/api-key` with `{ "key": "...", "secret": "..." }` answers `accessToken` (15 minutes) and `refreshToken` (rotates on every use at `POST /auth/refresh`). Then send `Authorization: Bearer <accessToken>`.
Prove the setup with the first call:
```bash
curl -s https://apis.kentro.dev/channel/v1/channels \
-H "kentro-key: $KENTRO_KEY" \
-H "kentro-secret: $KENTRO_SECRET"
```
## Scopes
`channel:read` reads every resource. The write scopes are `customers:write`, `products:write`, `orders:write`, `fulfillment:write`, `shipments:write`, `invoices:write`, `payments:write` and `purchase-orders:write`; each also reads its resource. `channel:write` grants every write and read. Ask for the narrowest set and never `admin` for an integration. A missing scope answers `403 forbidden` with the scope named in the message. A key bound to channels writes only into them (`403`, detail code `not_bound`).
## Rules
- Send `Idempotency-Key` on every `POST`, `PUT`, `PATCH` and `DELETE`: 7 to 50 characters, derived from the source system's id for the change, identical on retries. The same key and body replays the first answer with `Idempotent-Replayed: true`. The same key with another body is `422 idempotency_key_reused`. A key still running is `409 in_progress` with `Retry-After`. Answers are kept 24 hours; failed requests are not stored.
- Send `externalId` on every create. Address documents by uuid `id`, by their 24-hex id, or as `ext:<channelId>:<externalId>`.
- `onDuplicate` decides what a create does when the document exists: `update` (default, so a create is an upsert), `skip`, or `error` (`409 duplicate`).
- Send `version` on a `PATCH` for optimistic concurrency (`409 version_conflict`).
- Money is a decimal string in currency units with `currency`: `"12.50"`. No cents and no floats in code. Totals must add up (`422 totals_mismatch`). Too many decimals is `400 validation_failed` with detail code `precision`.
- Timestamps are ISO-8601 UTC. Date-only fields take `YYYY-MM-DD`.
- Lists use cursors: `limit` 1 to 200 (default 50), follow `nextCursor` to `null`, newest first, no totals. Sync with `updatedSince` and walk every page before you advance the watermark.
- Order lines name a sellable variant. A parent group is `422 unresolved_reference` with detail code `group_container`.
- `X-Dry-Run: true` runs a write and rolls it back. `X-Import-Mode: historical` loads history without emails, packing slips, accounting exports or EDI. `X-Customer-Match: externalId` stops customer matching by email or phone.
- Bulk loads: `POST /v1/orders/import` and `POST /v1/products/import` answer `202` with an `operationId`; poll `GET /v1/operations/:id`.
- Unknown fields are refused with `400 validation_failed`. Never derive or send a tenant id: the key decides the tenant.
- Call only the endpoints the Channel API reference documents, at `https://apis.kentro.live/channel/v1`. There is no Kentro SDK or CLI.
## Common flows
| Need | Call |
|---|---|
| Catalog | `POST /v1/products` with variants and listings inline; stock with `PUT /v1/products/:id/inventory-levels`; the sellable view with `GET /v1/catalog?channelId=...` |
| Import an order | `POST /v1/orders` with the customer inline, items, totals and optional `invoicing` |
| The channel already split fulfillment | `POST /v1/orders/:id/fulfillment-orders/generate` |
| The channel ships itself | Send the order with `"fulfillment": { "skip": true }`, then `POST /v1/shipments` |
| A warehouse reports a shipment | `POST /v1/fulfillment-orders/:id/fulfill` with delta quantities and tracking |
| A shipment made elsewhere | `POST /v1/shipments` |
| Invoices and payments | `POST /v1/invoices`; `POST /v1/payments` with `application.mode` `auto` or `manual` |
| Cancel | `POST /v1/orders/:id/cancel` |
| Retailer purchase order | `POST /v1/purchase-orders`, then `POST /v1/purchase-orders/:id/accept` or `/reject` |
| Pick up changes made in Kentro | `GET /v1/<resource>?updatedSince=...` |
## Errors and retries
Refusals past the gateway: `{ "error": { "code", "message", "details": [{ "path", "code", "message" }], "requestId" } }`. Gateway refusals: `{ "error": "INVALID_CREDENTIALS" }`, `TOKEN_EXPIRED` (refresh the bearer token) or `TENANT_INACTIVE`, with a `message`.
| Answer | Action |
|---|---|
| `400 validation_failed` | Fix the field in `details[].path`. Do not retry unchanged. |
| `401`, `403 forbidden` | Credentials or scope. Tell the user which scope is missing. |
| `404 not_found`, `422 unresolved_reference` | Create or fix the referenced document first. |
| `409 duplicate`, `state_conflict`, `version_conflict` | Read the document, then decide. |
| `409 in_progress` | Wait for `Retry-After`, then retry with the same key. |
| `429 rate_limited` | Wait for `Retry-After`. Defaults: 1,200 writes and 6,000 reads per minute per tenant. |
| `500 internal`, `503 route_disabled`, `503 dependency_unavailable` | Exponential backoff with jitter, the same `Idempotency-Key`, at most five attempts. |
Log `requestId` (also the `X-Request-Id` header) with every failure. Kentro support is [email protected].
## Done means
- Every call ran against the sandbox, and the user saw the requests and responses with secrets redacted.
- One write sent twice with the same `Idempotency-Key` answered `Idempotent-Replayed: true`.
- No secret appears in code, logs or the repository.
## References
- https://docs.kentro.io/docs/ai-agent-instructions.md
- https://docs.kentro.io/reference/channel-api-getting-started.md
- https://docs.kentro.io/reference/channel-api-authentication.md
- https://docs.kentro.io/reference/channel-api-conventions.md
- https://docs.kentro.io/reference/channel-api-flows.md
- https://docs.kentro.io/llms.txt
kentro-store-api
Save as .claude/skills/kentro-store-api/SKILL.md.
---
name: kentro-store-api
description: Build, change or debug a custom storefront on the Kentro Store API (apis.kentro.live/store/v1): buyer sign-in, the catalog with buyer prices, cart, quotes, checkout, orders, invoices, account and subscriptions, called from the storefront's server. Use for a Next.js or any server-rendered storefront on Kentro, and whenever code calls apis.kentro.live/store or apis.kentro.dev/store.
---
# Kentro Store API
The Store API is Kentro's storefront infrastructure for a tenant who builds their own storefront UI. The storefront's server calls it for one buyer at a time. Kentro keeps the catalog, prices, stock, buyer accounts, carts, quotes, checkout, orders, invoices and subscriptions; the storefront owns pages, layout and its own session cookie.
## Before writing code
1. Look up every operation and field in the API Reference: the Markdown pages under References.
2. Ask the user for:
- The host of a published storefront in the sandbox (Kentro OS at https://kentro.cc, `Storefronts`). An unpublished storefront answers `404 NOT_FOUND`.
- A sandbox API key whose permission group holds `storefront:serve`, stored server-side as `KENTRO_API_KEY`. Never in the chat, never in client code.
- The framework. Default to Next.js with the App Router.
## Server only
- Call the Store API only from server code: Server Components, Route Handlers or Server Actions in Next.js, server routes elsewhere. It sends no CORS headers for browsers.
- Never put the API key, a buyer token or a guest token in a Client Component, a `NEXT_PUBLIC_` variable, local storage, a readable cookie or a URL. Map your own `httpOnly` session cookie to the tokens on the server.
- Never trust a tenant id, store id or buyer id from the browser. The key decides the tenant, the host decides the store, the token decides the buyer.
## Request basics
| | Production | Sandbox |
|---|---|---|
| Store API | `https://apis.kentro.live/store/v1` | `https://apis.kentro.dev/store/v1` |
| Buyer sign-in | `https://apis.kentro.live/auth` | `https://apis.kentro.dev/auth` |
Every operation is `POST /<family>.<operation>` with a JSON body, `{}` when it takes no fields.
| Header | Value |
|---|---|
| `X-API-Key` | The API key's secret. Every call. |
| `X-Storefront-Host` | The published storefront host from configuration: lower case, no scheme, no path. Every call. |
| `X-Storefront-Buyer` | The buyer's access token, for a signed-in buyer. |
| `X-Storefront-Guest` | The guest token from `session.guest`, for a guest. |
| `X-Storefront-Client-Ip` | The buyer's IP address. Recommended. |
| `X-Expected-Buyer` | The buyer id your session maps to (`guest:<guestId>` or `guest`). A mismatch is `409 CONFLICT`. |
| `Content-Type` | `application/json` |
`Authorization` is not read. A minimal server-only client for Next.js:
```ts
// lib/kentro-store.ts
import 'server-only';
const BASE = 'https://apis.kentro.dev/store/v1';
export async function storeCall<T>(
operation: string,
body: unknown = {},
who: { buyerToken?: string; guestToken?: string; clientIp?: string } = {},
): Promise<T> {
const res = await fetch(`${BASE}/${operation}`, {
method: 'POST',
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.KENTRO_API_KEY ?? '',
'X-Storefront-Host': process.env.KENTRO_STOREFRONT_HOST ?? '',
...(who.buyerToken ? { 'X-Storefront-Buyer': who.buyerToken } : {}),
...(who.guestToken ? { 'X-Storefront-Guest': who.guestToken } : {}),
...(who.clientIp ? { 'X-Storefront-Client-Ip': who.clientIp } : {}),
},
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) {
throw Object.assign(new Error(data.message ?? 'Store API refusal'), {
status: res.status,
code: data.code ?? data.error,
fieldErrors: data.fieldErrors ?? {},
});
}
return data as T;
}
```
The `server-only` package makes the build fail if a Client Component imports the file.
## Buyers and guests
- Sign in: `POST https://apis.kentro.dev/auth/login/customer` with `{ "email", "password", "host" }` (no API key) answers `accessToken` (15 minutes) and `refreshToken` (30 days, rotating). Refresh with `POST /auth/refresh { refreshToken }` before expiry or on the first `401`. Sign out with `POST /auth/logout { refreshToken }`.
- `session.get {}` tells you what the tokens amount to: `buyerId`, `authenticated`, `confirmed`.
- Sign-up is `session.signup`; confirmation is `account.confirm`. An unapproved buyer gets `403 APPROVAL_REQUIRED`; an unconfirmed one `403 CONFIRMATION_REQUIRED`.
- Guests: mint `session.guest {}` once per browser, only when `store.get` answers `guestCheckout: true`. One guest token is one visitor; never share it across browsers.
## Cart and checkout
- Read the store first: `store.get {}` (`access`, `publicPrices`, `guestPrices`, `guestCheckout`, `paymentProvider`, `fulfillment`, `currency`).
- Every cart mutation (`cart.add`, `cart.addMany`, `cart.set`, `cart.remove`, `cart.update`, `cart.contact`) sends `expectedRevision` and `ownershipVersion` from the last cart answer. A stale revision is `409 CONFLICT`: read `cart.get` and let the buyer act again. Render the cart the API answers; never recompute totals.
- After sign-in with a guest cart, send both tokens and resolve `cart.choices` and `cart.choose` before the next mutation (`409 CART_CHOICE_REQUIRED` otherwise).
- Quote: `cart.get { "quote": true }` answers `quoteReady`, `checkoutAllowed`, `shippingRates`, `charge` and `quoteFingerprint` (valid at most 10 minutes).
- Checkout: `checkout.start` with `expectedRevision`, `ownershipVersion`, `idempotencyKey` and `quoteFingerprint`. Follow `state`: `pending_payment` or `needs_action` send the buyer to `redirectUrl`, then `checkout.confirm { attemptId }`; `prepared`, `paid`, `bank_processing` and `unknown` poll `checkout.status`; `complete` has `orderId`; `declined` returns to the cart with `message`. Never start a second checkout while one is pending. Recover a lost answer with `checkout.status { idempotencyKey, quoteFingerprint }`.
- `idempotencyKey` (7 to 50 characters from `A-Z a-z 0-9 _ -`) also goes on `cart.addMany`, cart claims, invoice payments, saved payment methods, subscriptions and applications.
## Data rules
- Money is an integer in the currency's minor unit (`1250` is 12.50 USD); format it with the store's `currency`. Prices are `null` when the identity may not see them.
- Lists are page based: `page`, `hasNextPage`, `total`, `totalIsCapped`.
- Every answer is `Cache-Control: no-store`; cache nothing across requests.
- Call only the operations the Store API reference documents, at `https://apis.kentro.live/store/v1`. There is no Kentro SDK or CLI.
## Errors
Refusals are `{ "code", "message", "fieldErrors" }`, and `message` is written for buyers. `400 VALIDATION` fix the input; `401 UNAUTHORIZED` refresh or sign in; `403 FORBIDDEN` the key lacks `storefront:serve` or the host belongs to another tenant; `404 NOT_FOUND` or `PRODUCT_NOT_FOUND`; `409 CONFLICT` re-read and retry; `409 CHECKOUT_PENDING` or `PAYMENT_PENDING` poll `checkout.status`; `429 RATE_LIMITED` wait `Retry-After`; `503 UNAVAILABLE` retry with backoff (a write that answered `503` was not applied unless a later read shows it). A declined payment answers `200` with `state: "declined"`.
## Done means
- `store.get` answers `200` for the host, and a stale `expectedRevision` is handled by re-reading the cart.
- A quote and `checkout.start` succeed in the sandbox, and a repeated `checkout.start` with the same `idempotencyKey` creates no second order.
- The built client assets contain neither the API key's secret nor the string `X-API-Key`.
## References
- https://docs.kentro.io/docs/ai-agent-instructions.md
- https://docs.kentro.io/reference/store-api-getting-started.md
- https://docs.kentro.io/reference/store-api-authentication.md
- https://docs.kentro.io/reference/store-api-conventions.md
- https://docs.kentro.io/reference/store-api-flows.md
- https://docs.kentro.io/llms.txt
kentro-pixel
Save as .claude/skills/kentro-pixel/SKILL.md.
---
name: kentro-pixel
description: Install or change Kentro Pixel, Kentro's first-party, consent-aware event collector - the browser SDK on a website wired to a consent manager, signed server-side events such as commerce.order_paid from a backend, and the Pixel management API. Use whenever code loads pixel.kentro.live, calls kentroPixel(...), posts to /v1/server/events, or calls apis.kentro.live/pixel/v1.
---
# Kentro Pixel
Kentro Pixel receives events from websites (browser SDK), Kentro GO landing pages and servers (HMAC-signed server API). It validates every event against a shared taxonomy, applies each visitor's consent, and records the result for Kentro analytics, attribution, audiences and destinations.
## Hosts
| What | Production | Kentro staging | Credential |
|---|---|---|---|
| Collector and browser SDK | `https://pixel.kentro.live` | `https://pixel.kentro.dev` | Property public key (browser) or server key (server) |
| Management API | `https://apis.kentro.live/pixel/v1` | `https://apis.kentro.dev/pixel/v1` | Kentro bearer token or API key, plus `pixel:read` or `pixel:manage` |
Properties, keys, the snippet, the debugger and ingestion health are also in Kentro GO under `Pixel` (tabs `Properties`, `Destinations`, `Privacy`; property tabs `Install`, `Domains`, `Keys`, `Debugger`, `Validate`, `Health`, `Settings`).
## Browser install
1. Get the snippet from the property's `Install` tab, or `GET /v1/properties/:id/snippet`. Production properties have `pk_live_` keys; staging and development properties have `pk_test_` keys. A public key is meant to be visible in the page.
2. Put the queue stub before the SDK script in the head of every page:
```html
<script>window.kentroPixel=window.kentroPixel||function(){(window.kentroPixel.q=window.kentroPixel.q||[]).push(arguments)};</script>
<script async src="https://pixel.kentro.live/sdk/v1/kentro-pixel.js" data-key="pk_live_..."></script>
```
3. With `data-key` and no queued `init`, the SDK initializes itself, sends `marketing.session_started` and `marketing.page_viewed`, and tracks single-page app route changes. Without `data-key`, call `kentroPixel('init', { key, endpoint })`.
4. Report consent at init and on every change:
```js
kentroPixel('consentChanged', {
analytics: 'granted',
marketing: 'denied',
functional: 'granted',
policy_version: '2026-10',
source: 'cmp'
});
```
5. With a Content-Security-Policy, allow the collector host in `script-src` and `connect-src`, and the inline stub with a nonce or hash.
## Browser rules
- Never put a server key (`sk_...`) or its secret in browser code.
- Events without the required consent are recorded as suppressed; never work around consent or ad blockers, and never fingerprint.
- Never send identity claims (`subject_ref`) from the browser and never put emails, phone numbers or other personal data in properties. A signed-in visitor is identified only through an identity token the server signs.
- Use event names and properties from the taxonomy: `GET /v1/event-schemas`. Unknown names, reserved names, unlisted properties and floating-point money are rejected per event.
- `allowed_origins` on the property decides which hosts may send browser events (`403 ORIGIN_NOT_ALLOWED` otherwise).
## Server events
POST to `<collector>/v1/server/events` with a body that is one envelope or `{ "events": [...] }`, and three headers:
| Header | Value |
|---|---|
| `X-Kentro-Key-Id` | The server key id, `sk_...` |
| `X-Kentro-Timestamp` | Current Unix time in seconds |
| `X-Kentro-Signature` | `sha256=` + hex HMAC-SHA256 of `<timestamp>.<raw body>`, keyed with the server secret |
```js
import { createHmac } from 'node:crypto';
const body = JSON.stringify({ events });
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = 'sha256=' + createHmac('sha256', process.env.KENTRO_PIXEL_SECRET)
.update(`${timestamp}.${body}`)
.digest('hex');
const res = await fetch('https://pixel.kentro.dev/v1/server/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Kentro-Key-Id': process.env.KENTRO_PIXEL_KEY_ID,
'X-Kentro-Timestamp': timestamp,
'X-Kentro-Signature': signature,
},
body,
});
```
- Sign the exact bytes you send. A timestamp more than 300 seconds off is refused with `TIMESTAMP_STALE`; a wrong signature with `SIGNATURE_INVALID`; an unknown key with `KEY_INVALID`.
- Give each event a stable `event_id` derived from the business fact, so retries deduplicate (event ids are kept at least 14 days; a repeat answers `duplicate`).
- `event_name` is lowercase and dotted (`commerce.order_paid`). `occurred_at` is ISO-8601, at most 7 days old and 5 minutes ahead. Each event is at most 32 KB.
- Revenue facts are authoritative only from the server: send `commerce.order_paid` from the backend after the payment is committed. Money is integer minor units as the schema defines.
- Create server keys on the property's `Keys` tab or with `POST /v1/properties/:id/server-keys`; the secret is shown once. Keep it in a secrets store.
## Verify
- The collector answers `202` with `{ "receipt_id", "results": [{ "event_id", "status" }] }`, where `status` is `accepted`, `duplicate`, `suppressed`, `quarantined` or `rejected`.
- The `Debugger` tab (`GET /v1/events/debug?propertyId=...&limit=20`) lists the newest recorded events with `processing_status` (`pending`, `processed`, `suppressed_consent`, `quarantined`).
- The `Health` tab (`GET /v1/ingestion/health?propertyId=...`) lists rejections with reason codes.
- The `Validate` tab (`POST /v1/event-schemas/validate`) checks envelopes without storing them.
## Done means
- Page views arrive as `accepted` after consent and `suppressed` before it.
- Server events answer `accepted`, a repeat answers `duplicate`, and a bad signature is refused.
- No server key appears in browser code, logs or the repository.
## References
- https://docs.kentro.io/docs/ai-agent-instructions.md
- https://docs.kentro.io/docs/kentro-pixel.md
- https://docs.kentro.io/docs/pixel-quickstart.md
- https://docs.kentro.io/docs/pixel-browser-sdk.md
- https://docs.kentro.io/docs/pixel-events.md
- https://docs.kentro.io/docs/pixel-server-api.md
- https://docs.kentro.io/docs/pixel-consent-privacy.md
Related
Updated about 12 hours ago

