Entwickler-API
Eine schreibgeschützte API zum Assistenten Ihres eigenen Shops: Antworten, Vorschläge beim Tippen, ähnliche Produkte und Katalog, für Ihren Server, Ihre App oder Ihre Seite.
API-Version 1.0.0
Überblick#
Die API gibt Ihrem Code, was der Assistent im Shop Ihren Kunden gibt: eine vollständige Antwort auf eine Frage in Alltagssprache, Produktvorschläge beim Tippen, Listen ähnlicher Produkte und Lesezugriff auf den Katalog. Basis-URL: https://assistant.onesite.agency/api/v1.
Sie ist schreibgeschützt und auf einen Shop begrenzt: Ein Schlüssel öffnet den Shop, der ihn erstellt hat, und nichts sonst. Namen, Preise, Bestand und Links stammen immer aus dem Katalog; die KI schreibt nur die kurzen Texte say und why, und die Antwort kennzeichnet sie (siehe „KI-generierte Texte“ unten).
Anfragen und Antworten sind JSON über HTTPS. Die Version steht im Pfad (/v1). Die API ist in Tarifen verfügbar, die die Entwickler-API enthalten.
Schnellstart#
In drei Schritten von null zur ersten Antwort.
1. Schlüssel erstellen#
Öffnen Sie im Kundenbereich den Tab Developers, benennen Sie den Schlüssel und wählen Sie seine Scopes, optional auch eine IP-Liste und Browser-Origins. Der Schlüssel wird nur einmal angezeigt, wir speichern nur einen Hash: Kopieren Sie ihn direkt in die Secrets Ihres Servers, nie in ein Repository. Ein verlorener Schlüssel ist nicht wiederherstellbar: widerrufen Sie ihn und erstellen Sie einen neuen.
2. Prüfen#
Rufen Sie GET /me auf. Ein 200 zeigt, welchen Shop der Schlüssel öffnet, seine Scopes und Limits. Ersetzen Sie YOUR_API_KEY durch Ihren Schlüssel.
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/me' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/me", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/me",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())3. Eine Frage stellen#
Senden Sie die Worte des Kunden an POST /answers (Scope answer nötig). Sie erhalten den besten Treffer, Alternativen und je eine Begründung in einer Zeile.
Beispielanfrage, curl
curl -X POST 'https://assistant.onesite.agency/api/v1/answers' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 3f9d1c52-7b0e-4a53-9a6f-0c2f8e1d77aa' \
-d '{
"query": "a cold, quiet science fiction novel for a long winter",
"language": "en",
"in_stock_only": true,
"session_id": "visitor-8841"
}'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/answers", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Idempotency-Key": "3f9d1c52-7b0e-4a53-9a6f-0c2f8e1d77aa",
},
body: JSON.stringify({
"query": "a cold, quiet science fiction novel for a long winter",
"language": "en",
"in_stock_only": true,
"session_id": "visitor-8841"
}),
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.post(
"https://assistant.onesite.agency/api/v1/answers",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Idempotency-Key": "3f9d1c52-7b0e-4a53-9a6f-0c2f8e1d77aa",
},
json={
"query": "a cold, quiet science fiction novel for a long winter",
"language": "en",
"in_stock_only": True,
"session_id": "visitor-8841",
},
)
print(response.status_code, response.json())Authentifizierung#
Senden Sie den Schlüssel im Header Authorization als Bearer-Token. Legen Sie ihn nie in die URL: URLs landen in Logs und im Browserverlauf.
Authentifizierung
Authorization: Bearer YOUR_API_KEYEin fehlender, fehlerhafter, unbekannter oder widerrufener Schlüssel ergibt immer dasselbe 401 invalid_api_key; so verrät die Antwort einem Ratenden nie, dass ein Schlüssel fast stimmt. Wiederholte ungültige Schlüssel von einer Adresse werden mit 429 too_many_failed_attempts gebremst.
Ein Schlüssel gehört genau einem Shop und kann keinen anderen lesen: Die ID eines Produkts aus einem anderen Shop antwortet 404, genau wie eine nicht vorhandene.
Sie können einen Schlüssel weiter einschränken: mit einer IP-Freigabeliste (403 ip_not_allowed) und einer Liste erlaubter Browser-Origins (403 origin_not_allowed). Scopes werden beim Erstellen festgelegt; um sie zu erweitern, erstellen Sie einen neuen Schlüssel.
Scopes und Limits#
Jeder Schlüssel trägt die Scopes, die er nutzen darf. Limits gelten pro Schlüssel und pro Scope.
| Scope | Endpunkte | Aufrufe pro 60 s |
|---|---|---|
answer | POST /answers | 20 |
suggest | GET /suggestions | 120 |
catalogue:read | GET /products, GET /products/{product_id} | 120 |
similar | GET /products/{product_id}/similar | 60 |
analytics:read | GET /analytics/summary, GET /analytics/series, GET /analytics/queries, GET /analytics/sales | 30 |
POST /answers hat zusätzlich ein Tageslimit von 500 Aufrufen pro Schlüssel (UTC-Tag), weil jede Antwort ein Modellaufruf ist. Eine Wiederholung mit demselben Idempotency-Key zählt nicht.
Jede Antwort enthält X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset; ein 429 fügt Retry-After (Sekunden) hinzu. Warten Sie so lange, statt in einer Schleife zu wiederholen. GET /me braucht einen gültigen Schlüssel, aber keinen bestimmten Scope.
Endpunkte#
Jede Operation unten wird aus dem OpenAPI-Dokument gebaut, und jedes Beispiel führen unsere Tests gegen die echte API aus; die Beispiele können also nicht von der API abweichen. Beschreibungen der Operationen und Felder sind englisch, wie im Dokument.
GET /me#
Prüfen Sie, welchen Shop, welche Scopes und Limits ein Schlüssel hat.
Describes the key you are calling with: its scopes, shop, allow-lists and limits. Costs no quota; use it to check a key.
- Erforderlicher Scope
- jeder gültige Schlüssel
Keine Parameter.
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | KeyInfo | Successful Response |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
Check a key (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/me' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/me", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/me",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"id": 3,
"name": "Storefront search",
"hint": "osk_…PLEE",
"scopes": [
"analytics:read",
"answer",
"catalogue:read",
"similar",
"suggest"
],
"shop": {
"domain": "alpha.example",
"currency": "EUR",
"language": "en"
},
"origins": [],
"networks": [],
"created_at": "2026-10-13T09:00:00+00:00",
"last_used_at": null,
"limits": {
"answer": {
"per_minute": 20
},
"suggest": {
"per_minute": 120
},
"similar": {
"per_minute": 60
},
"catalogue:read": {
"per_minute": 120
},
"analytics:read": {
"per_minute": 30
}
},
"answers_per_day": 500
}Missing or invalid key (HTTP 401)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/me'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/me", {
method: "GET",
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/me",
)
print(response.status_code, response.json())Beispielantwort · HTTP 401
{
"error": {
"type": "authentication_error",
"code": "invalid_api_key",
"message": "Missing or invalid API key. Send it as 'Authorization: Bearer osk_…'.",
"param": null,
"request_id": "req_9f2b000000000001"
}
}POST /answers#
Stellen Sie eine Frage in den Worten des Kunden und erhalten Sie den besten Treffer, Alternativen und je eine Begründung.
Answers a shopper's question the way the shop's own assistant does: the best match, alternatives and a one-line reason for each. Product facts (title, price, stock, link) always come from the shop's catalogue; only say and why are written by an AI model — see the ai object, and pass that on to your users.
Counted against the key's per-minute limit and its daily cap; a replay with the same Idempotency-Key is free.
- Erforderlicher Scope
answer- Limit
- 20 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
Idempotency-Key | header | string | null | nein | Länge 1–64 | Makes a retry safe: the same key and body return the first answer without a second model call. |
Request-Body: AnswerRequest#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
query | string | ja | The shopper's question, in their own words. |
language | Language | null | nein | Language of the reply (ru en el es de it fr). Defaults to the shop's language. |
session_id | string | null | nein | Opaque id of one conversation. Send the same id to let follow-ups (“cheaper”, “something lighter”) build on earlier turns. Sessions are private to your API key. |
in_stock_only | boolean | nein | Only products that can be bought now (excludes pre-orders). |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | Answer | Successful Response |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
409 | ErrorBody | Same Idempotency-Key is still being processed. |
422 | ErrorBody | Invalid request body, or Idempotency-Key reused with a different body. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
502 | ErrorBody | The answer could not be produced right now. Safe to retry. |
A shopper asks in their own words (HTTP 200)#
Beispielanfrage, curl
curl -X POST 'https://assistant.onesite.agency/api/v1/answers' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 3f9d1c52-7b0e-4a53-9a6f-0c2f8e1d77aa' \
-d '{
"query": "a cold, quiet science fiction novel for a long winter",
"language": "en",
"in_stock_only": true,
"session_id": "visitor-8841"
}'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/answers", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Idempotency-Key": "3f9d1c52-7b0e-4a53-9a6f-0c2f8e1d77aa",
},
body: JSON.stringify({
"query": "a cold, quiet science fiction novel for a long winter",
"language": "en",
"in_stock_only": true,
"session_id": "visitor-8841"
}),
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.post(
"https://assistant.onesite.agency/api/v1/answers",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
"Idempotency-Key": "3f9d1c52-7b0e-4a53-9a6f-0c2f8e1d77aa",
},
json={
"query": "a cold, quiet science fiction novel for a long winter",
"language": "en",
"in_stock_only": True,
"session_id": "visitor-8841",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"id": "6f1b4a52-3c0d-4d5e-9a7e-2b8c1d0f4e11",
"query": "a cold, quiet science fiction novel for a long winter",
"intent": "search",
"say": "Here is the best match for a cold, quiet read.",
"main": {
"product": {
"id": 101,
"title": "The Left Hand of Darkness",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/101",
"handle": "the-left-hand-of-darkness",
"external_id": "gid://shop/101",
"price": {
"amount": "12.90",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/101.jpg",
"thumb_url": "https://cdn.alpha.example/101-s.jpg"
},
"why": "A quiet, snowbound classic - the cold-world mood you described, in one of the genre's best-loved novels.",
"why_source": "model"
},
"alternatives": [
{
"product": {
"id": 102,
"title": "The Dispossessed",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/102",
"handle": "the-dispossessed",
"external_id": "gid://shop/102",
"price": {
"amount": "13.50",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/102.jpg",
"thumb_url": "https://cdn.alpha.example/102-s.jpg"
},
"why": "Set in the same universe: the politics of a desert moon instead of snow.",
"why_source": "model"
},
{
"product": {
"id": 104,
"title": "Solaris",
"maker": "Stanislaw Lem",
"line": null,
"url": "https://alpha.example/products/104",
"handle": "solaris",
"external_id": "gid://shop/104",
"price": {
"amount": "11.00",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/104.jpg",
"thumb_url": "https://cdn.alpha.example/104-s.jpg"
},
"why": "A different kind of stillness: an ocean planet that studies the people studying it.",
"why_source": "model"
}
],
"shelves": [],
"miss": false,
"miss_reason": null,
"clarify": null,
"degraded": false,
"currency": "EUR",
"ai": {
"generated": true,
"fields": [
"say",
"main.why",
"alternatives[].why"
],
"notice": "The `say` and `why` texts are generated by an AI model from this shop's catalogue and may contain mistakes. Product names, prices, stock and links come from the shop's catalogue, not from the model."
}
}Rate limit reached (HTTP 429)#
Beispielanfrage, curl
curl -X POST 'https://assistant.onesite.agency/api/v1/answers' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"query": "a cold, quiet science fiction novel for a long winter"
}'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/answers", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
"query": "a cold, quiet science fiction novel for a long winter"
}),
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.post(
"https://assistant.onesite.agency/api/v1/answers",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"query": "a cold, quiet science fiction novel for a long winter",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 429
{
"error": {
"type": "rate_limit_error",
"code": "rate_limited",
"message": "Rate limit reached: 20 calls per minute for this scope.",
"param": null,
"request_id": "req_9f2b000000000005"
}
}GET /suggestions#
Schnelle Produktvorschläge unter einem Suchfeld beim Tippen. Es ist kein KI-Modell beteiligt.
Instant products for a half-typed query. No model is called and nothing is journalled, so it is cheap — but it is not a substitute for POST /answers when the shopper asked a full question.
- Erforderlicher Scope
suggest- Limit
- 120 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
q | query | string | ja | Länge 1–500 | What has been typed so far. |
limit | query | integer | nein | 1–20; Standard 6 | |
in_stock_only | query | boolean | nein | Standard false | Only products that can be bought now. |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | SuggestionList | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
Suggestions while the shopper types (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/suggestions?q=left+hand&limit=3' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/suggestions?q=left+hand&limit=3", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/suggestions?q=left+hand&limit=3",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "list",
"query": "left hand",
"data": [
{
"id": 101,
"title": "The Left Hand of Darkness",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/101",
"handle": "the-left-hand-of-darkness",
"external_id": "gid://shop/101",
"price": {
"amount": "12.90",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/101.jpg",
"thumb_url": "https://cdn.alpha.example/101-s.jpg"
}
]
}GET /products#
Lesen Sie den gesamten Katalog seitenweise.
The shop's visible catalogue in a stable order (by product id), a page at a time. Products the owner has hidden or excluded are not listed. A page may hold fewer than limit items while has_more is still true; keep following next_cursor until it is null.
- Erforderlicher Scope
catalogue:read- Limit
- 120 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
limit | query | integer | nein | 1–100; Standard 20 | |
cursor | query | string | null | nein | Länge ≤ 64 | next_cursor of the previous page. |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | ProductPage | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
First page of the catalogue (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products?limit=2' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products?limit=2", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products?limit=2",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "list",
"data": [
{
"id": 101,
"title": "The Left Hand of Darkness",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/101",
"handle": "the-left-hand-of-darkness",
"external_id": "gid://shop/101",
"price": {
"amount": "12.90",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/101.jpg",
"thumb_url": "https://cdn.alpha.example/101-s.jpg"
},
{
"id": 102,
"title": "The Dispossessed",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/102",
"handle": "the-dispossessed",
"external_id": "gid://shop/102",
"price": {
"amount": "13.50",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/102.jpg",
"thumb_url": "https://cdn.alpha.example/102-s.jpg"
}
],
"has_more": true,
"next_cursor": "c1.MTAy"
}Following the cursor (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products?limit=2&cursor=c1.MTAy' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products?limit=2&cursor=c1.MTAy", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products?limit=2&cursor=c1.MTAy",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "list",
"data": [
{
"id": 103,
"title": "A Wizard of Earthsea",
"maker": "Ursula K. Le Guin",
"line": "Earthsea",
"url": "https://alpha.example/products/103",
"handle": "a-wizard-of-earthsea",
"external_id": "gid://shop/103",
"price": {
"amount": "9.99",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/103.jpg",
"thumb_url": "https://cdn.alpha.example/103-s.jpg"
},
{
"id": 104,
"title": "Solaris",
"maker": "Stanislaw Lem",
"line": null,
"url": "https://alpha.example/products/104",
"handle": "solaris",
"external_id": "gid://shop/104",
"price": {
"amount": "11.00",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/104.jpg",
"thumb_url": "https://cdn.alpha.example/104-s.jpg"
}
],
"has_more": true,
"next_cursor": "c1.MTA0"
}A parameter out of range (HTTP 422)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products?limit=500' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products?limit=500", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products?limit=500",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 422
{
"error": {
"type": "invalid_request_error",
"code": "invalid_request",
"message": "Invalid request. limit: Input should be less than or equal to 100",
"param": "limit",
"request_id": "req_9f2b000000000004"
}
}GET /products/{product_id}#
Lesen Sie ein Produkt anhand seiner ID.
One product of the shop with its description and gallery. A product that belongs to another shop, does not exist or is hidden by the owner is the same 404.
- Erforderlicher Scope
catalogue:read- Limit
- 120 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
product_id | path | integer | ja | 1–9223372036854776000 | Product number (id in any product object). |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | ProductDetail | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
404 | ErrorBody | No such visible product in this shop. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
One product with its description and gallery (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products/101' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products/101", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products/101",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"id": 101,
"title": "The Left Hand of Darkness",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/101",
"handle": "the-left-hand-of-darkness",
"external_id": "gid://shop/101",
"price": {
"amount": "12.90",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/101.jpg",
"thumb_url": "https://cdn.alpha.example/101-s.jpg",
"description": "The Left Hand of Darkness by Ursula K. Le Guin.",
"images": [
"https://cdn.alpha.example/101.jpg"
]
}A key without the needed scope (HTTP 403)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products/101' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products/101", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products/101",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 403
{
"error": {
"type": "permission_error",
"code": "insufficient_scope",
"message": "This key lacks the 'catalogue:read' scope. Create a key that has it.",
"param": "catalogue:read",
"request_id": "req_9f2b000000000002"
}
}A product that is not in this shop (HTTP 404)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products/987654' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products/987654", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products/987654",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 404
{
"error": {
"type": "not_found_error",
"code": "product_not_found",
"message": "No such product in this shop.",
"param": null,
"request_id": "req_9f2b000000000003"
}
}GET /products/{product_id}/similar#
Produkte, die einem Produkt ähneln. Es ist kein KI-Modell beteiligt.
Neighbours of the product's own embedding vector, ranked the way the shop's assistant ranks them. No model is called. Needs only the similar scope — a key without catalogue:read cannot fetch the product itself.
- Erforderlicher Scope
similar- Limit
- 60 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
product_id | path | integer | ja | 1–9223372036854776000 | Product number (id in any product object). |
limit | query | integer | nein | 1–24; Standard 8 |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | SimilarList | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
404 | ErrorBody | No such visible product in this shop. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
Products similar to one product (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/products/101/similar?limit=3' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/products/101/similar?limit=3", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/products/101/similar?limit=3",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "list",
"data": [
{
"id": 102,
"title": "The Dispossessed",
"maker": "Ursula K. Le Guin",
"line": "Hainish Cycle",
"url": "https://alpha.example/products/102",
"handle": "the-dispossessed",
"external_id": "gid://shop/102",
"price": {
"amount": "13.50",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/102.jpg",
"thumb_url": "https://cdn.alpha.example/102-s.jpg"
},
{
"id": 103,
"title": "A Wizard of Earthsea",
"maker": "Ursula K. Le Guin",
"line": "Earthsea",
"url": "https://alpha.example/products/103",
"handle": "a-wizard-of-earthsea",
"external_id": "gid://shop/103",
"price": {
"amount": "9.99",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/103.jpg",
"thumb_url": "https://cdn.alpha.example/103-s.jpg"
},
{
"id": 109,
"title": "Gift Cookbook",
"maker": "Chef Example",
"line": null,
"url": "https://alpha.example/products/109",
"handle": "gift-cookbook",
"external_id": "gid://shop/109",
"price": {
"amount": "24.00",
"currency": "EUR"
},
"availability": "in_stock",
"ready_at": null,
"image_url": "https://cdn.alpha.example/109.jpg",
"thumb_url": "https://cdn.alpha.example/109-s.jpg"
}
],
"product_id": 101
}GET /analytics/summary#
Die Summen Ihres Shops für einen Zeitraum – dieselben Zahlen wie in der Statistik des Kundenbereichs – mit optionalem Vergleich.
The cabinet's Statistics figures for the window: questions, people who asked, sessions, found and not found, clicks, and the shopper's path by step. Counted for people only. With compare, the same totals for the comparison window — only when the shop's history covers it; otherwise previous is null rather than a growth from nothing.
- Erforderlicher Scope
analytics:read- Limit
- 30 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
compare | query | "prev" | "year" | "none" | nein | Standard "prev" | Compare with the window just before (prev), the same window a year back (year), or nothing. |
since | query | string (date-time) | null | nein | | Start of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days. |
until | query | string (date-time) | null | nein | | End of the window, exclusive (ISO 8601). Default: now. |
first_day | query | string (date) | null | nein | | First day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until. |
last_day | query | string (date) | null | nein | | Last day of the window (YYYY-MM-DD) in timezone, inclusive. |
timezone | query | string | null | nein | Länge ≤ 64 | IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none. |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | AnalyticsSummary | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
Last week in figures, compared with the week before (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/analytics/summary?first_day=2026-10-06&last_day=2026-10-12' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/analytics/summary?first_day=2026-10-06&last_day=2026-10-12", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/analytics/summary?first_day=2026-10-06&last_day=2026-10-12",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "analytics.summary",
"window": {
"since": "2026-10-06T00:00:00+00:00",
"until": "2026-10-13T00:00:00+00:00",
"timezone": "UTC"
},
"totals": {
"queries": 280,
"people": 154,
"sessions": 174,
"answered": 238,
"missed": 42,
"asked_again": 31,
"impressions": 840,
"clicks": 82,
"reached": 63,
"browsed": 7
},
"compare": "prev",
"previous_window": {
"since": "2026-09-29T00:00:00+00:00",
"until": "2026-10-06T00:00:00+00:00"
},
"previous": {
"queries": 273,
"people": 150,
"sessions": 174,
"answered": 238,
"missed": 42,
"asked_again": 31,
"impressions": 840,
"clicks": 82,
"reached": 63,
"browsed": 7
},
"funnel": [
{
"stage": "visit",
"sessions": 900,
"share_of_previous": null
},
{
"stage": "offered",
"sessions": 860,
"share_of_previous": 0.9555555555555556
},
{
"stage": "opened",
"sessions": 140,
"share_of_previous": 0.16279069767441862
},
{
"stage": "asked",
"sessions": 120,
"share_of_previous": 0.8571428571428571
},
{
"stage": "reached",
"sessions": 30,
"share_of_previous": 0.25
},
{
"stage": "carted",
"sessions": 70,
"share_of_previous": null
},
{
"stage": "bought",
"sessions": 12,
"share_of_previous": 0.17142857142857143
}
],
"funnel_since": null
}GET /analytics/series#
Fragen, Fragen ohne Treffer und Fragen, die zu einem Produkt geführt haben, für jeden Tag des Zeitraums.
Questions, misses and questions that reached a product, for every day of the window in timezone. Days without questions are zeros. Only the window is counted, so the first and last day may be partial, and the days add up to the summary's totals.
- Erforderlicher Scope
analytics:read- Limit
- 30 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
since | query | string (date-time) | null | nein | | Start of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days. |
until | query | string (date-time) | null | nein | | End of the window, exclusive (ISO 8601). Default: now. |
first_day | query | string (date) | null | nein | | First day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until. |
last_day | query | string (date) | null | nein | | Last day of the window (YYYY-MM-DD) in timezone, inclusive. |
timezone | query | string | null | nein | Länge ≤ 64 | IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none. |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | AnalyticsSeries | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
Questions per day (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/analytics/series?first_day=2026-10-06&last_day=2026-10-12&timezone=Europe/Berlin' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/analytics/series?first_day=2026-10-06&last_day=2026-10-12&timezone=Europe/Berlin", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/analytics/series?first_day=2026-10-06&last_day=2026-10-12&timezone=Europe/Berlin",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "analytics.series",
"window": {
"since": "2026-10-05T22:00:00+00:00",
"until": "2026-10-12T22:00:00+00:00",
"timezone": "Europe/Berlin"
},
"data": [
{
"date": "2026-10-06",
"queries": 40,
"missed": 6,
"reached": 9
},
{
"date": "2026-10-07",
"queries": 40,
"missed": 6,
"reached": 9
},
{
"date": "2026-10-08",
"queries": 40,
"missed": 6,
"reached": 9
},
{
"date": "2026-10-09",
"queries": 40,
"missed": 6,
"reached": 9
},
{
"date": "2026-10-10",
"queries": 40,
"missed": 6,
"reached": 9
},
{
"date": "2026-10-11",
"queries": 40,
"missed": 6,
"reached": 9
},
{
"date": "2026-10-12",
"queries": 40,
"missed": 6,
"reached": 9
}
]
}GET /analytics/queries#
Die häufigsten Fragen und die Fragen, die am häufigsten ohne Produkt bleiben.
The questions people ask most, and the questions most often left without a product — what to stock and what to describe better. Questions are grouped without regard to case; e-mail addresses and phone or card numbers in them are masked.
- Erforderlicher Scope
analytics:read- Limit
- 30 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
limit | query | integer | nein | 1–100; Standard 20 | Rows in each list. |
since | query | string (date-time) | null | nein | | Start of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days. |
until | query | string (date-time) | null | nein | | End of the window, exclusive (ISO 8601). Default: now. |
first_day | query | string (date) | null | nein | | First day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until. |
last_day | query | string (date) | null | nein | | Last day of the window (YYYY-MM-DD) in timezone, inclusive. |
timezone | query | string | null | nein | Länge ≤ 64 | IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none. |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | AnalyticsQueries | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
What people ask most, and what they do not find (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/analytics/queries?first_day=2026-10-06&last_day=2026-10-12&limit=3' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/analytics/queries?first_day=2026-10-06&last_day=2026-10-12&limit=3", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/analytics/queries?first_day=2026-10-06&last_day=2026-10-12&limit=3",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "analytics.queries",
"window": {
"since": "2026-10-06T00:00:00+00:00",
"until": "2026-10-13T00:00:00+00:00",
"timezone": "UTC"
},
"top": [
{
"text": "science fiction for winter",
"count": 42,
"missed": 0,
"clicks": 28
},
{
"text": "gift for a ten-year-old",
"count": 28,
"missed": 7,
"clicks": 14
},
{
"text": "write to me at [email]",
"count": 1,
"missed": 1,
"clicks": 0
}
],
"missed": [
{
"text": "signed first editions",
"count": 21,
"missed": 21,
"clicks": 0
},
{
"text": "gift for a ten-year-old",
"count": 28,
"missed": 7,
"clicks": 14
}
]
}GET /analytics/sales#
Was der Assistent verkauft hat, laut Ihren eigenen Bestellungen – dieselben Zahlen wie die Übersicht „Käufe“ im Kundenbereich.
Orders and revenue the assistant helped with, by the shop's own orders: products its button put into the cart and products bought after it showed them. Orders after a conversation without either are counted apart and not as its revenue. The shop's total turnover is not part of this API.
- Erforderlicher Scope
analytics:read- Limit
- 30 Aufrufe pro 60 s
| Name | Ort | Typ | Pflicht | Einschränkungen | Beschreibung |
|---|---|---|---|---|---|
days | query | integer | nein | Standard 30 | Period: today and the days before it. One of 7, 30, 90. |
timezone | query | string | null | nein | Länge ≤ 64 | IANA time zone for days. Default: the shop's time zone, or UTC. |
| Status | Antwortschema | Beschreibung |
|---|---|---|
200 | AnalyticsSales | Successful Response |
304 | – | Not modified: your If-None-Match matched the current ETag. |
401 | ErrorBody | Missing, malformed, unknown or revoked API key (all look the same). |
402 | ErrorBody | The shop's subscription is not active. |
403 | ErrorBody | Key not allowed here: missing scope, IP or origin not on the key's list, plan without API. |
422 | ErrorBody | Invalid request. |
429 | ErrorBody | Rate limit or daily quota reached. See Retry-After. |
What the assistant sold in the last 7 days (HTTP 200)#
Beispielanfrage, curl
curl 'https://assistant.onesite.agency/api/v1/analytics/sales?days=7' \
-H 'Authorization: Bearer YOUR_API_KEY'Beispielanfrage, JavaScript (fetch)
const response = await fetch("https://assistant.onesite.agency/api/v1/analytics/sales?days=7", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
},
});
const data = await response.json();
console.log(response.status, data);Beispielanfrage, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/analytics/sales?days=7",
headers={
"Authorization": "Bearer YOUR_API_KEY",
},
)
print(response.status_code, response.json())Beispielantwort · HTTP 200
{
"object": "analytics.sales",
"days": 7,
"since": "2026-10-07T00:00:00+00:00",
"timezone": "UTC",
"currency": "EUR",
"assistant_orders": 2,
"assistant_revenue": {
"amount": "23.90",
"currency": "EUR"
},
"button_revenue": {
"amount": "12.90",
"currency": "EUR"
},
"shown_revenue": {
"amount": "11.00",
"currency": "EUR"
},
"carted": 3,
"talked_orders": 1,
"average_order": {
"amount": "18.70",
"currency": "EUR"
},
"days_data": [
{
"date": "2026-10-07",
"orders": 0,
"revenue": {
"amount": "0.00",
"currency": "EUR"
}
},
{
"date": "2026-10-08",
"orders": 0,
"revenue": {
"amount": "0.00",
"currency": "EUR"
}
},
{
"date": "2026-10-09",
"orders": 0,
"revenue": {
"amount": "0.00",
"currency": "EUR"
}
},
{
"date": "2026-10-10",
"orders": 0,
"revenue": {
"amount": "0.00",
"currency": "EUR"
}
},
{
"date": "2026-10-11",
"orders": 1,
"revenue": {
"amount": "11.00",
"currency": "EUR"
}
},
{
"date": "2026-10-12",
"orders": 0,
"revenue": {
"amount": "0.00",
"currency": "EUR"
}
},
{
"date": "2026-10-13",
"orders": 1,
"revenue": {
"amount": "12.90",
"currency": "EUR"
}
}
],
"products": [
{
"product_id": 101,
"title": "The Left Hand of Darkness",
"quantity": 1,
"revenue": {
"amount": "12.90",
"currency": "EUR"
}
},
{
"product_id": 104,
"title": "Solaris",
"quantity": 1,
"revenue": {
"amount": "11.00",
"currency": "EUR"
}
}
]
}Datentypen#
Die in Anfragen und Antworten verwendeten Objekte. | null heißt: Das Feld ist immer vorhanden, kann aber null sein; ein Array wird Typ[] geschrieben.
AiDisclosure#
Shown to your users? Then say so. The wording below is the minimum we ask integrations to pass on.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
generated | boolean | nein | Some text in this response was written by an AI model. |
fields | string[] | ja | JSON paths of the AI-written strings. |
notice | string | ja |
AnalyticsFunnelStep#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
stage | "visit" | "offered" | "opened" | "asked" | "reached" | "carted" | "bought" | ja | |
sessions | integer | ja | Sessions that reached this step. Sessions, not people: events on the site carry no visitor mark. |
share_of_previous | number | null | nein | sessions divided by the previous step. null on the first step and where a step is larger than the one before it (for example, carts added with the shop's own button without a click in an answer). |
AnalyticsPeriod#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
since | string | ja | |
until | string | ja |
AnalyticsPoint#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
date | string | ja | The day (YYYY-MM-DD) in window.timezone. The first and last day may be partial: only the window is counted. |
queries | integer | ja | |
missed | integer | ja | |
reached | integer | ja | Questions of the day after which the shopper opened a product page from the answer. |
AnalyticsQueries#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "analytics.queries" | nein | |
window | AnalyticsWindow | ja | |
top | AnalyticsQuery[] | ja | Most frequent questions, most frequent first. |
missed | AnalyticsQuery[] | ja | Questions most often left without a product, by number of misses. Its own list, not a filter of top. |
AnalyticsQuery#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
text | string | ja | The question as asked (case-insensitive group). E-mail addresses, phone and card numbers are masked. |
count | integer | ja | How many times it was asked. |
missed | integer | ja | How many of those had nothing to show. |
clicks | integer | ja | Clicks from its answers to product pages. |
AnalyticsSales#
What the assistant sold, by the shop's own orders — the same figures as the summary in the cabinet's Purchases section.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "analytics.sales" | nein | |
days | integer | ja | Today and the days before it, from midnight in timezone. |
since | string | ja | Midnight of the first day (ISO 8601). |
timezone | string | ja | |
currency | string | ja | Orders in another currency are not added up. |
assistant_orders | integer | ja | Orders with a product added to the cart by the assistant's button or bought after the assistant showed it. |
assistant_revenue | Money | ja | The value of those products (not the whole orders). |
button_revenue | Money | ja | Part of assistant_revenue added to the cart by the assistant's button. |
shown_revenue | Money | ja | Part of assistant_revenue bought after the assistant showed the product. |
carted | integer | ja | Products the assistant helped to put into a cart in the period (by its button or after showing them). |
talked_orders | integer | ja | Orders placed after a conversation with the assistant without any of the above. Not counted as its revenue. |
average_order | Money | null | nein | Average total of assistant_orders; null when none has a total. |
days_data | AnalyticsSalesDay[] | ja | Every day of the period, empty days included. |
products | AnalyticsSoldProduct[] | ja | Top products sold through the assistant, by revenue. |
AnalyticsSalesDay#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
date | string | ja | The day (YYYY-MM-DD) in timezone. |
orders | integer | ja | |
revenue | Money | ja |
AnalyticsSeries#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "analytics.series" | nein | |
window | AnalyticsWindow | ja | |
data | AnalyticsPoint[] | ja | Every day of the window, days without questions included as zeros. The sum of queries equals totals.queries of the summary. |
AnalyticsSoldProduct#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
product_id | integer | null | nein | null when the product is no longer in the catalogue. |
title | string | null | nein | |
quantity | integer | ja | |
revenue | Money | ja |
AnalyticsSummary#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "analytics.summary" | nein | |
window | AnalyticsWindow | ja | |
totals | AnalyticsTotals | ja | |
compare | "prev" | "year" | "none" | ja | |
previous_window | AnalyticsPeriod | null | nein | The window compared with. null when compare is none or the shop's history covers less than 90 % of it. |
previous | AnalyticsTotals | null | nein | Totals of previous_window, counted the same way. null — no comparison. |
funnel | AnalyticsFunnelStep[] | ja | Sessions by step of the shopper's path, in order. |
funnel_since | string | null | nein | Set when the funnel is counted from a later moment than window.since: before it one of the steps was not yet recorded. |
AnalyticsTotals#
Counted for people only: bots, our own checks, calls made with API keys and conversations removed by the operator are not counted.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
queries | integer | ja | Questions people asked the assistant, in their own words. |
people | integer | ja | Different people who asked at least once, by the visitor mark in their browser: someone back three days later is one person, the same person on phone and laptop is two. A question without the mark (no analytics consent) counts its session. |
sessions | integer | ja | Different sessions with at least one question. A session lasts about a day, so this is not people. |
answered | integer | ja | Questions answered with at least one product. |
missed | integer | ja | Questions with nothing to show. |
asked_again | integer | ja | Sessions that asked another question after an answer. |
impressions | integer | ja | Product cards shown in answers. |
clicks | integer | ja | Clicks from answer cards to product pages. |
reached | integer | ja | Questions after which the shopper opened a product page from the answer. Counted per question, not per click. |
browsed | integer | ja | Clicks to product pages from shelves and collections, with no question before them. |
AnalyticsWindow#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
since | string | ja | Start of the window, inclusive (ISO 8601, UTC). |
until | string | ja | End of the window, exclusive (ISO 8601, UTC). |
timezone | string | ja | IANA time zone in which days are cut (daily series, first_day/last_day). |
Answer#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | string | ja | Id of this answer in the shop's journal. Quote it when you report a problem. |
query | string | ja | |
intent | string | ja | What the question was understood to be: search, browse, basket, question, greeting, off_topic, code, about_item, order…. New values may appear; treat unknown ones as search. |
say | string | null | nein | A short spoken-style reply, written by an AI model. May be null. |
main | Recommendation | null | nein | The single best match, when there is one. |
alternatives | Recommendation[] | nein | |
shelves | Shelf[] | nein | For “everything by …” questions: whole shelves instead of picks. |
miss | boolean | ja | true when the shop has nothing that fits. Do not invent a product then. |
miss_reason | "absent" | "unstocked" | "unmatched" | null | nein | Why nothing is shown: not in the catalogue (absent), sold out (unstocked), or nothing matched the question's hard limits such as price (unmatched). |
clarify | string | null | nein | A clarifying question to ask the user, when the answer needs one. |
degraded | boolean | nein | true when the answer was built without the model (it was unavailable). |
currency | string | ja | |
ai | AiDisclosure | ja |
AnswerRequest#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
query | string | ja | The shopper's question, in their own words. |
language | Language | null | nein | Language of the reply (ru en el es de it fr). Defaults to the shop's language. |
session_id | string | null | nein | Opaque id of one conversation. Send the same id to let follow-ups (“cheaper”, “something lighter”) build on earlier turns. Sessions are private to your API key. |
in_stock_only | boolean | nein | Only products that can be bought now (excludes pre-orders). |
ErrorBody#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
error | ErrorDetail | ja |
ErrorDetail#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
type | "authentication_error" | "permission_error" | "invalid_request_error" | "not_found_error" | "conflict_error" | "rate_limit_error" | "billing_error" | "api_error" | ja | |
code | string | ja | Stable machine-readable reason. Branch on this, not on message. |
message | string | ja | For humans. The wording may change between releases. |
param | string | null | nein | The offending field or parameter, for invalid_request. |
request_id | string | ja | Also in the X-Request-Id response header. |
KeyInfo#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | integer | ja | |
name | string | ja | |
hint | string | ja | Which key this is, e.g. osk_…x9Zq. The secret itself is never shown again. |
scopes | string[] | ja | |
shop | KeyShop | ja | |
origins | string[] | ja | Browser origins allowed to use this key. Empty = any (server-side use). |
networks | string[] | ja | IP networks the key is accepted from. Empty = any. |
created_at | string | ja | |
last_used_at | string | null | nein | |
limits | map<string, ScopeLimit> | ja | Calls per minute, per scope. |
answers_per_day | integer | ja | Daily cap on POST /answers calls for this key (UTC day). |
KeyShop#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
domain | string | ja | |
currency | string | ja | |
language | string | ja |
Language#
Language code: ru, en, el, es, de, it, fr.
"ru" | "en" | "el" | "es" | "de" | "it" | "fr"
Money#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
amount | string | ja | Decimal amount as a string, e.g. "12.90". Never a float: no rounding surprises. |
currency | string | ja | ISO 4217 code of the shop currency, e.g. EUR. |
Product#
A catalogue card. Everything here comes from the shop's own catalogue, never from the model.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | integer | ja | Product number in this shop's catalogue. Stable until the product is removed from the shop. |
title | string | ja | |
maker | string | null | nein | Whoever the shop credits as the maker of the product. |
line | string | null | nein | Series, collection or product line, when the shop names one. |
url | string | null | nein | Product page on the shop's own site. |
handle | string | null | nein | The platform's product handle (Shopify handle, WooCommerce slug), when known. |
external_id | string | null | nein | The shop platform's own product id, for mapping back to your system. |
price | Money | null | nein | Current price; null when the shop does not publish one. |
availability | "in_stock" | "preorder" | "out_of_stock" | ja | |
ready_at | string | null | nein | For preorder: the shop's promised ship date (ISO 8601). |
image_url | string | null | nein | |
thumb_url | string | null | nein | A smaller image, when the shop has one. |
ProductDetail#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | integer | ja | Product number in this shop's catalogue. Stable until the product is removed from the shop. |
title | string | ja | |
maker | string | null | nein | Whoever the shop credits as the maker of the product. |
line | string | null | nein | Series, collection or product line, when the shop names one. |
url | string | null | nein | Product page on the shop's own site. |
handle | string | null | nein | The platform's product handle (Shopify handle, WooCommerce slug), when known. |
external_id | string | null | nein | The shop platform's own product id, for mapping back to your system. |
price | Money | null | nein | Current price; null when the shop does not publish one. |
availability | "in_stock" | "preorder" | "out_of_stock" | ja | |
ready_at | string | null | nein | For preorder: the shop's promised ship date (ISO 8601). |
image_url | string | null | nein | |
thumb_url | string | null | nein | A smaller image, when the shop has one. |
description | string | null | nein | |
images | string[] | nein | The shop's gallery, in the shop's order. |
ProductPage#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "list" | nein | |
data | Product[] | ja | |
has_more | boolean | ja | |
next_cursor | string | null | nein | Pass as cursor to get the next page. null on the last page. |
Recommendation#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
product | Product | ja | |
why | string | null | nein | One sentence on why this fits the question. Written by an AI model when why_source is model; a fixed catalogue template when it is catalogue. |
why_source | "model" | "catalogue" | null | nein | Who wrote why. null when there is no why. |
ScopeLimit#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
per_minute | integer | ja |
Shelf#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
title | string | ja | |
total | integer | ja | How many products the shelf holds in the shop — not how many are shown in items. |
items | Product[] | ja |
SimilarList#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "list" | nein | |
data | Product[] | ja | |
product_id | integer | ja |
SuggestionList#
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
object | "list" | nein | |
query | string | ja | |
data | Product[] | ja |
Fehler#
Alle Fehler haben dieselbe Form und eine request_id (auch im Header X-Request-Id): Nennen Sie sie, wenn Sie uns schreiben. Verzweigen Sie nach code, nie nach message, dessen Wortlaut sich ändern kann.
Fehler · HTTP 422
{
"error": {
"type": "invalid_request_error",
"code": "invalid_request",
"message": "Invalid request. limit: Input should be less than or equal to 100",
"param": "limit",
"request_id": "req_9f2b000000000004"
}
}| Code | HTTP | Bedeutung und was zu tun ist | Wiederholen |
|---|---|---|---|
invalid_api_key | 401 | Der Schlüssel fehlt, ist fehlerhaft, unbekannt oder widerrufen. Alle vier Fälle sehen absichtlich gleich aus. Senden Sie Authorization: Bearer <Schlüssel> oder erstellen Sie einen neuen Schlüssel. | Anfrage korrigieren |
ip_not_allowed | 403 | Der Schlüssel hat eine IP-Freigabeliste, und diese Adresse steht nicht darauf. Rufen Sie aus einem freigegebenen Netz auf oder ändern Sie die Liste im Kundenbereich. | Anfrage korrigieren |
origin_not_allowed | 403 | Der Aufruf kommt von einer Browserseite, deren Origin nicht auf der Origin-Liste des Schlüssels steht. Tragen Sie den genauen Origin (Schema, Host, Port) im Kundenbereich ein. | Anfrage korrigieren |
insufficient_scope | 403 | Dem Schlüssel fehlt der Scope, den dieser Endpunkt verlangt. Scopes lassen sich nicht erweitern: Erstellen Sie einen neuen Schlüssel mit diesem Scope. | Anfrage korrigieren |
api_disabled | 403 | Die Entwickler-API ist für diesen Shop nicht aktiviert. Öffnen Sie den Tab Developers im Kundenbereich oder schreiben Sie uns, falls er fehlt. | Anfrage korrigieren |
subscription_inactive | 402 | Das Abonnement des Shops ist nicht aktiv, die API pausiert daher. Sie läuft von selbst weiter, sobald das Abonnement wieder aktiv ist; die Schlüssel bleiben gültig. | Anfrage korrigieren |
plan_not_entitled | 403 | Der Tarif des Shops enthält die Entwickler-API nicht. Sie ist in Tarifen verfügbar, die sie enthalten. | Anfrage korrigieren |
rate_limited | 429 | Das Minutenlimit für diesen Schlüssel und Scope ist ausgeschöpft. Warten Sie die Sekunden aus Retry-After ab und versuchen Sie es erneut; verteilen Sie Aufrufe oder cachen Sie Ergebnisse. | später |
daily_quota_exceeded | 429 | Das Tageslimit für Antworten dieses Schlüssels ist erreicht (UTC-Tag). Versuchen Sie es nach den Sekunden aus Retry-After erneut, also nach dem Tageswechsel. | später |
too_many_failed_attempts | 429 | Von dieser Adresse kamen zu viele ungültige Schlüssel. Warten Sie die Sekunden aus Retry-After ab und prüfen Sie, welchen Schlüssel Ihr Code sendet. | später |
invalid_request | 422 | Ein Parameter oder Body-Feld fehlt oder liegt außerhalb des Bereichs. param nennt das erste fehlerhafte; korrigieren Sie die Anfrage, ein erneuter Versuch hilft nicht. | Anfrage korrigieren |
invalid_cursor | 422 | Der Cursor wurde verändert oder gehört nicht zu dieser Liste. Geben Sie next_cursor genau so zurück, wie Sie ihn erhalten haben, oder beginnen Sie ohne Cursor neu. | Anfrage korrigieren |
product_not_found | 404 | Dieses Produkt gibt es in diesem Shop nicht. Die ID eines Produkts aus einem anderen Shop antwortet genauso: Ein Schlüssel liest nur seinen eigenen Shop. | Anfrage korrigieren |
idempotency_key_reused | 422 | Dieser Idempotency-Key wurde schon mit einem anderen Body verwendet. Nehmen Sie für eine andere Frage einen neuen Schlüssel. | Anfrage korrigieren |
idempotency_in_progress | 409 | Eine Anfrage mit demselben Idempotency-Key wird noch verarbeitet. Warten Sie kurz und senden Sie dieselbe Anfrage erneut, um das Ergebnis zu erhalten. | später |
request_too_large | 413 | Der Request-Body ist größer als das Limit. Senden Sie einen kleineren Body. | Anfrage korrigieren |
answer_unavailable | 502 | Die Antwort konnte gerade nicht erzeugt werden. Versuchen Sie es in Kürze mit demselben Idempotency-Key erneut. | später |
internal_error | 500 | Auf unserer Seite ist etwas schiefgelaufen. Versuchen Sie es mit Pause erneut; bleibt es bestehen, senden Sie uns die request_id. | später |
not_found | 404 | Diesen Endpunkt gibt es nicht. Prüfen Sie den Pfad anhand der Referenz; Pfade beginnen mit der Version, /api/v1. | Anfrage korrigieren |
method_not_allowed | 405 | Der Pfad existiert, aber nicht für diese HTTP-Methode. Der Header Allow nennt die erlaubten. | Anfrage korrigieren |
type gruppiert die Codes: authentication_error, permission_error, invalid_request_error, not_found_error, conflict_error, rate_limit_error, billing_error, api_error. Innerhalb von v1 können neue Codes hinzukommen; behandeln Sie einen unbekannten code nach seinem HTTP-Status.
Paginierung#
Listen nutzen Cursor. Geben Sie next_cursor aus einer Antwort als cursor in der nächsten Anfrage mit, bis next_cursor null ist. limit ist standardmäßig 20 und höchstens 100. Eine Seite kann weniger Einträge als limit enthalten, solange has_more true ist; folgen Sie also next_cursor, statt zu zählen. Behandeln Sie den Cursor als undurchsichtig: Ein veränderter ergibt 422 invalid_cursor.
Caching und ETag#
GET-Antworten tragen ein ETag. Senden Sie es als If-None-Match zurück; hat sich nichts geändert, erhalten Sie 304 Not Modified ohne Body, schneller und leichter als die Liste erneut zu laden. Cachen Sie Katalog-Lesezugriffe bei sich und validieren Sie so neu.
Idempotenz und Wiederholungen#
POST /answers akzeptiert den Header Idempotency-Key (1 bis 64 Zeichen: Buchstaben, Ziffern und . _ : -). Dieselbe Anfrage innerhalb von 24 Stunden liefert die erste Antwort ohne zweiten Modellaufruf und ohne Kontingent zu verbrauchen; die Wiederholung trägt Idempotent-Replayed: true. Derselbe Schlüssel mit anderem Body ergibt 422 idempotency_key_reused; solange die erste Anfrage läuft, 409 idempotency_in_progress.
Erzeugen Sie für jede Nutzerfrage eine neue UUID und verwenden Sie sie nur für Wiederholungen genau dieser Frage.
Request-Bodys sind auf 16384 Bytes begrenzt (413 request_too_large).
Browser und CORS#
Eine Webseite darf die API nur von einem am Schlüssel eingetragenen Origin aufrufen (Schema, Host und Port, z. B. https://shop.example); die API sendet CORS-Header nur an eingetragene Origins. Alles im Code einer Seite ist öffentlich, ein dort verwendeter Schlüssel kann von jedem kopiert werden, der die Seite öffnet.
Geben Sie dem Schlüssel für die Browsernutzung deshalb nur die Scopes suggest und similar plus eine Origin-Liste, und lassen Sie answer-Schlüssel auf Ihrem Server.
Analysen#
Das Recht analytics:read öffnet die Zahlen Ihres eigenen Shops: dieselben Summen, Tagesreihen, häufigsten Fragen und Verkäufe des Assistenten wie im Kundenbereich, mit demselben Code berechnet. Geben Sie es nur Schlüsseln, die auf Ihrem Server bleiben.
Gezählt werden nur Menschen: keine Bots, keine eigenen Tests von uns, keine Aufrufe mit API-Schlüsseln und keine vom Operator entfernten Gespräche. people zählt verschiedene Besucher anhand der Markierung im Browser – wer nach drei Tagen wiederkommt, ist eine Person, dieselbe Person mit Smartphone und Laptop sind zwei; sessions dauern etwa einen Tag; die Trichterschritte sind Sitzungen. Der Gesamtumsatz des Shops ist nicht Teil der API.
Ein Zeitraum ist entweder since/until (ISO 8601; ohne Offset UTC) oder first_day/last_day, beide Tage eingeschlossen, geschnitten in timezone (Standard: Zeitzone des Shops oder UTC). Ein Vergleich (compare) wird nur geliefert, wenn die Historie des Shops den Vergleichszeitraum abdeckt; sonst ist previous null.
KI-generierte Texte#
In einer Antwort werden say und jedes why von einem KI-Modell geschrieben, und die Antwort sagt das in ihrem Objekt ai: generated, die JSON-Pfade in fields und einen Text notice. Zeigen Sie diese Texte Ihren Nutzern, sagen Sie ihnen, dass sie KI-generiert sind; notice ist der Mindestwortlaut, den wir Integrationen weiterzugeben bitten.
Namen, Preise, Bestand und Links stammen nie vom Modell. Ist miss true, hat der Shop nichts Passendes: Zeigen Sie das und erfinden Sie kein Produkt.
Versionierung#
Die Version ist Teil des Pfads: /v1. Innerhalb von v1 machen wir nur abwärtskompatible Änderungen: neue Endpunkte, neue optionale Parameter, neue Antwortfelder und neue Werte von Aufzählungen (zum Beispiel neue intent-Werte). Schreiben Sie Clients, die unbekannte Felder ignorieren und bei unbekannten Aufzählungswerten sinnvoll zurückfallen. Eine inkompatible Änderung würde nur unter einem neuen Versionspfad erscheinen.
Für Maschinen und KI-Assistenten#
Dieselbe Referenz in maschinenlesbarer Form. Importieren Sie das OpenAPI-Dokument in Postman, Insomnia oder einen Codegenerator.