Zum Inhalt springen
Auf dieser Seite

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_KEY

Ein 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.

Scopes, ihre Endpunkte und Limits
ScopeEndpunkteAufrufe pro 60 s
answerPOST /answers20
suggestGET /suggestions120
catalogue:readGET /products, GET /products/{product_id}120
similarGET /products/{product_id}/similar60
analytics:readGET /analytics/summary, GET /analytics/series, GET /analytics/queries, GET /analytics/sales30

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.

Antworten
StatusAntwortschemaBeschreibung
200KeyInfoSuccessful Response
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
Idempotency-Keyheaderstring | nullneinLänge 1–64Makes a retry safe: the same key and body return the first answer without a second model call.

Request-Body: AnswerRequest#

Anfrageschema
FeldTypPflichtBeschreibung
querystringjaThe shopper's question, in their own words.
languageLanguage | nullneinLanguage of the reply (ru en el es de it fr). Defaults to the shop's language.
session_idstring | nullneinOpaque 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_onlybooleanneinOnly products that can be bought now (excludes pre-orders).
Antworten
StatusAntwortschemaBeschreibung
200AnswerSuccessful Response
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
409ErrorBodySame Idempotency-Key is still being processed.
422ErrorBodyInvalid request body, or Idempotency-Key reused with a different body.
429ErrorBodyRate limit or daily quota reached. See Retry-After.
502ErrorBodyThe 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
qquerystringjaLänge 1–500What has been typed so far.
limitqueryintegernein1–20; Standard 6
in_stock_onlyquerybooleanneinStandard falseOnly products that can be bought now.
Antworten
StatusAntwortschemaBeschreibung
200SuggestionListSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
limitqueryintegernein1–100; Standard 20
cursorquerystring | nullneinLänge ≤ 64next_cursor of the previous page.
Antworten
StatusAntwortschemaBeschreibung
200ProductPageSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
product_idpathintegerja1–9223372036854776000Product number (id in any product object).
Antworten
StatusAntwortschemaBeschreibung
200ProductDetailSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
404ErrorBodyNo such visible product in this shop.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
product_idpathintegerja1–9223372036854776000Product number (id in any product object).
limitqueryintegernein1–24; Standard 8
Antworten
StatusAntwortschemaBeschreibung
200SimilarListSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
404ErrorBodyNo such visible product in this shop.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
comparequery"prev" | "year" | "none"neinStandard "prev"Compare with the window just before (prev), the same window a year back (year), or nothing.
sincequerystring (date-time) | nullneinStart of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days.
untilquerystring (date-time) | nullneinEnd of the window, exclusive (ISO 8601). Default: now.
first_dayquerystring (date) | nullneinFirst day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until.
last_dayquerystring (date) | nullneinLast day of the window (YYYY-MM-DD) in timezone, inclusive.
timezonequerystring | nullneinLänge ≤ 64IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none.
Antworten
StatusAntwortschemaBeschreibung
200AnalyticsSummarySuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
sincequerystring (date-time) | nullneinStart of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days.
untilquerystring (date-time) | nullneinEnd of the window, exclusive (ISO 8601). Default: now.
first_dayquerystring (date) | nullneinFirst day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until.
last_dayquerystring (date) | nullneinLast day of the window (YYYY-MM-DD) in timezone, inclusive.
timezonequerystring | nullneinLänge ≤ 64IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none.
Antworten
StatusAntwortschemaBeschreibung
200AnalyticsSeriesSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
limitqueryintegernein1–100; Standard 20Rows in each list.
sincequerystring (date-time) | nullneinStart of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days.
untilquerystring (date-time) | nullneinEnd of the window, exclusive (ISO 8601). Default: now.
first_dayquerystring (date) | nullneinFirst day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until.
last_dayquerystring (date) | nullneinLast day of the window (YYYY-MM-DD) in timezone, inclusive.
timezonequerystring | nullneinLänge ≤ 64IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none.
Antworten
StatusAntwortschemaBeschreibung
200AnalyticsQueriesSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
422ErrorBodyInvalid request.
429ErrorBodyRate 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
Parameter
NameOrtTypPflichtEinschränkungenBeschreibung
daysqueryintegerneinStandard 30Period: today and the days before it. One of 7, 30, 90.
timezonequerystring | nullneinLänge ≤ 64IANA time zone for days. Default: the shop's time zone, or UTC.
Antworten
StatusAntwortschemaBeschreibung
200AnalyticsSalesSuccessful Response
304–Not modified: your If-None-Match matched the current ETag.
401ErrorBodyMissing, malformed, unknown or revoked API key (all look the same).
402ErrorBodyThe shop's subscription is not active.
403ErrorBodyKey not allowed here: missing scope, IP or origin not on the key's list, plan without API.
422ErrorBodyInvalid request.
429ErrorBodyRate 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.

AiDisclosure
FeldTypPflichtBeschreibung
generatedbooleanneinSome text in this response was written by an AI model.
fieldsstring[]jaJSON paths of the AI-written strings.
noticestringja

AnalyticsFunnelStep#

AnalyticsFunnelStep
FeldTypPflichtBeschreibung
stage"visit" | "offered" | "opened" | "asked" | "reached" | "carted" | "bought"ja
sessionsintegerjaSessions that reached this step. Sessions, not people: events on the site carry no visitor mark.
share_of_previousnumber | nullneinsessions 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#

AnalyticsPeriod
FeldTypPflichtBeschreibung
sincestringja
untilstringja

AnalyticsPoint#

AnalyticsPoint
FeldTypPflichtBeschreibung
datestringjaThe day (YYYY-MM-DD) in window.timezone. The first and last day may be partial: only the window is counted.
queriesintegerja
missedintegerja
reachedintegerjaQuestions of the day after which the shopper opened a product page from the answer.

AnalyticsQueries#

AnalyticsQueries
FeldTypPflichtBeschreibung
object"analytics.queries"nein
windowAnalyticsWindowja
topAnalyticsQuery[]jaMost frequent questions, most frequent first.
missedAnalyticsQuery[]jaQuestions most often left without a product, by number of misses. Its own list, not a filter of top.

AnalyticsQuery#

AnalyticsQuery
FeldTypPflichtBeschreibung
textstringjaThe question as asked (case-insensitive group). E-mail addresses, phone and card numbers are masked.
countintegerjaHow many times it was asked.
missedintegerjaHow many of those had nothing to show.
clicksintegerjaClicks 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.

AnalyticsSales
FeldTypPflichtBeschreibung
object"analytics.sales"nein
daysintegerjaToday and the days before it, from midnight in timezone.
sincestringjaMidnight of the first day (ISO 8601).
timezonestringja
currencystringjaOrders in another currency are not added up.
assistant_ordersintegerjaOrders with a product added to the cart by the assistant's button or bought after the assistant showed it.
assistant_revenueMoneyjaThe value of those products (not the whole orders).
button_revenueMoneyjaPart of assistant_revenue added to the cart by the assistant's button.
shown_revenueMoneyjaPart of assistant_revenue bought after the assistant showed the product.
cartedintegerjaProducts the assistant helped to put into a cart in the period (by its button or after showing them).
talked_ordersintegerjaOrders placed after a conversation with the assistant without any of the above. Not counted as its revenue.
average_orderMoney | nullneinAverage total of assistant_orders; null when none has a total.
days_dataAnalyticsSalesDay[]jaEvery day of the period, empty days included.
productsAnalyticsSoldProduct[]jaTop products sold through the assistant, by revenue.

AnalyticsSalesDay#

AnalyticsSalesDay
FeldTypPflichtBeschreibung
datestringjaThe day (YYYY-MM-DD) in timezone.
ordersintegerja
revenueMoneyja

AnalyticsSeries#

AnalyticsSeries
FeldTypPflichtBeschreibung
object"analytics.series"nein
windowAnalyticsWindowja
dataAnalyticsPoint[]jaEvery day of the window, days without questions included as zeros. The sum of queries equals totals.queries of the summary.

AnalyticsSoldProduct#

AnalyticsSoldProduct
FeldTypPflichtBeschreibung
product_idinteger | nullneinnull when the product is no longer in the catalogue.
titlestring | nullnein
quantityintegerja
revenueMoneyja

AnalyticsSummary#

AnalyticsSummary
FeldTypPflichtBeschreibung
object"analytics.summary"nein
windowAnalyticsWindowja
totalsAnalyticsTotalsja
compare"prev" | "year" | "none"ja
previous_windowAnalyticsPeriod | nullneinThe window compared with. null when compare is none or the shop's history covers less than 90 % of it.
previousAnalyticsTotals | nullneinTotals of previous_window, counted the same way. null — no comparison.
funnelAnalyticsFunnelStep[]jaSessions by step of the shopper's path, in order.
funnel_sincestring | nullneinSet 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.

AnalyticsTotals
FeldTypPflichtBeschreibung
queriesintegerjaQuestions people asked the assistant, in their own words.
peopleintegerjaDifferent 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.
sessionsintegerjaDifferent sessions with at least one question. A session lasts about a day, so this is not people.
answeredintegerjaQuestions answered with at least one product.
missedintegerjaQuestions with nothing to show.
asked_againintegerjaSessions that asked another question after an answer.
impressionsintegerjaProduct cards shown in answers.
clicksintegerjaClicks from answer cards to product pages.
reachedintegerjaQuestions after which the shopper opened a product page from the answer. Counted per question, not per click.
browsedintegerjaClicks to product pages from shelves and collections, with no question before them.

AnalyticsWindow#

AnalyticsWindow
FeldTypPflichtBeschreibung
sincestringjaStart of the window, inclusive (ISO 8601, UTC).
untilstringjaEnd of the window, exclusive (ISO 8601, UTC).
timezonestringjaIANA time zone in which days are cut (daily series, first_day/last_day).

Answer#

Answer
FeldTypPflichtBeschreibung
idstringjaId of this answer in the shop's journal. Quote it when you report a problem.
querystringja
intentstringjaWhat 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.
saystring | nullneinA short spoken-style reply, written by an AI model. May be null.
mainRecommendation | nullneinThe single best match, when there is one.
alternativesRecommendation[]nein
shelvesShelf[]neinFor “everything by …” questions: whole shelves instead of picks.
missbooleanjatrue when the shop has nothing that fits. Do not invent a product then.
miss_reason"absent" | "unstocked" | "unmatched" | nullneinWhy nothing is shown: not in the catalogue (absent), sold out (unstocked), or nothing matched the question's hard limits such as price (unmatched).
clarifystring | nullneinA clarifying question to ask the user, when the answer needs one.
degradedbooleanneintrue when the answer was built without the model (it was unavailable).
currencystringja
aiAiDisclosureja

AnswerRequest#

AnswerRequest
FeldTypPflichtBeschreibung
querystringjaThe shopper's question, in their own words.
languageLanguage | nullneinLanguage of the reply (ru en el es de it fr). Defaults to the shop's language.
session_idstring | nullneinOpaque 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_onlybooleanneinOnly products that can be bought now (excludes pre-orders).

ErrorBody#

ErrorBody
FeldTypPflichtBeschreibung
errorErrorDetailja

ErrorDetail#

ErrorDetail
FeldTypPflichtBeschreibung
type"authentication_error" | "permission_error" | "invalid_request_error" | "not_found_error" | "conflict_error" | "rate_limit_error" | "billing_error" | "api_error"ja
codestringjaStable machine-readable reason. Branch on this, not on message.
messagestringjaFor humans. The wording may change between releases.
paramstring | nullneinThe offending field or parameter, for invalid_request.
request_idstringjaAlso in the X-Request-Id response header.

KeyInfo#

KeyInfo
FeldTypPflichtBeschreibung
idintegerja
namestringja
hintstringjaWhich key this is, e.g. osk_…x9Zq. The secret itself is never shown again.
scopesstring[]ja
shopKeyShopja
originsstring[]jaBrowser origins allowed to use this key. Empty = any (server-side use).
networksstring[]jaIP networks the key is accepted from. Empty = any.
created_atstringja
last_used_atstring | nullnein
limitsmap<string, ScopeLimit>jaCalls per minute, per scope.
answers_per_dayintegerjaDaily cap on POST /answers calls for this key (UTC day).

KeyShop#

KeyShop
FeldTypPflichtBeschreibung
domainstringja
currencystringja
languagestringja

Language#

Language code: ru, en, el, es, de, it, fr.

"ru" | "en" | "el" | "es" | "de" | "it" | "fr"

Money#

Money
FeldTypPflichtBeschreibung
amountstringjaDecimal amount as a string, e.g. "12.90". Never a float: no rounding surprises.
currencystringjaISO 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.

Product
FeldTypPflichtBeschreibung
idintegerjaProduct number in this shop's catalogue. Stable until the product is removed from the shop.
titlestringja
makerstring | nullneinWhoever the shop credits as the maker of the product.
linestring | nullneinSeries, collection or product line, when the shop names one.
urlstring | nullneinProduct page on the shop's own site.
handlestring | nullneinThe platform's product handle (Shopify handle, WooCommerce slug), when known.
external_idstring | nullneinThe shop platform's own product id, for mapping back to your system.
priceMoney | nullneinCurrent price; null when the shop does not publish one.
availability"in_stock" | "preorder" | "out_of_stock"ja
ready_atstring | nullneinFor preorder: the shop's promised ship date (ISO 8601).
image_urlstring | nullnein
thumb_urlstring | nullneinA smaller image, when the shop has one.

ProductDetail#

ProductDetail
FeldTypPflichtBeschreibung
idintegerjaProduct number in this shop's catalogue. Stable until the product is removed from the shop.
titlestringja
makerstring | nullneinWhoever the shop credits as the maker of the product.
linestring | nullneinSeries, collection or product line, when the shop names one.
urlstring | nullneinProduct page on the shop's own site.
handlestring | nullneinThe platform's product handle (Shopify handle, WooCommerce slug), when known.
external_idstring | nullneinThe shop platform's own product id, for mapping back to your system.
priceMoney | nullneinCurrent price; null when the shop does not publish one.
availability"in_stock" | "preorder" | "out_of_stock"ja
ready_atstring | nullneinFor preorder: the shop's promised ship date (ISO 8601).
image_urlstring | nullnein
thumb_urlstring | nullneinA smaller image, when the shop has one.
descriptionstring | nullnein
imagesstring[]neinThe shop's gallery, in the shop's order.

ProductPage#

ProductPage
FeldTypPflichtBeschreibung
object"list"nein
dataProduct[]ja
has_morebooleanja
next_cursorstring | nullneinPass as cursor to get the next page. null on the last page.

Recommendation#

Recommendation
FeldTypPflichtBeschreibung
productProductja
whystring | nullneinOne 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" | nullneinWho wrote why. null when there is no why.

ScopeLimit#

ScopeLimit
FeldTypPflichtBeschreibung
per_minuteintegerja

Shelf#

Shelf
FeldTypPflichtBeschreibung
titlestringja
totalintegerjaHow many products the shelf holds in the shop — not how many are shown in items.
itemsProduct[]ja

SimilarList#

SimilarList
FeldTypPflichtBeschreibung
object"list"nein
dataProduct[]ja
product_idintegerja

SuggestionList#

SuggestionList
FeldTypPflichtBeschreibung
object"list"nein
querystringja
dataProduct[]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"
  }
}
Fehlercodes
CodeHTTPBedeutung und was zu tun istWiederholen
invalid_api_key401Der 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_allowed403Der 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_allowed403Der 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_scope403Dem 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_disabled403Die 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_inactive402Das 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_entitled403Der Tarif des Shops enthält die Entwickler-API nicht. Sie ist in Tarifen verfügbar, die sie enthalten.Anfrage korrigieren
rate_limited429Das 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_exceeded429Das 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_attempts429Von 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_request422Ein 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_cursor422Der 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_found404Dieses 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_reused422Dieser Idempotency-Key wurde schon mit einem anderen Body verwendet. Nehmen Sie für eine andere Frage einen neuen Schlüssel.Anfrage korrigieren
idempotency_in_progress409Eine 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_large413Der Request-Body ist größer als das Limit. Senden Sie einen kleineren Body.Anfrage korrigieren
answer_unavailable502Die Antwort konnte gerade nicht erzeugt werden. Versuchen Sie es in Kürze mit demselben Idempotency-Key erneut.später
internal_error500Auf unserer Seite ist etwas schiefgelaufen. Versuchen Sie es mit Pause erneut; bleibt es bestehen, senden Sie uns die request_id.später
not_found404Diesen Endpunkt gibt es nicht. Prüfen Sie den Pfad anhand der Referenz; Pfade beginnen mit der Version, /api/v1.Anfrage korrigieren
method_not_allowed405Der 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.