API per sviluppatori
Un'API in sola lettura verso l'assistente del tuo negozio: risposte, suggerimenti mentre si digita, prodotti simili e catalogo, per il tuo server, la tua app o la tua pagina.
Versione dell'API 1.0.0
Panoramica#
L'API dà al tuo codice ciò che l'assistente del sito dà ai tuoi clienti: una risposta completa a una domanda in linguaggio naturale, suggerimenti di prodotti mentre si digita, elenchi di prodotti simili e lettura del catalogo. URL di base: https://assistant.onesite.agency/api/v1.
È in sola lettura e limitata a un negozio: una chiave apre il negozio che l'ha creata e nient'altro. Nomi, prezzi, disponibilità e link vengono sempre dal catalogo; l'IA scrive solo i testi brevi say e why, e la risposta li segnala (vedi «Testo generato dall'IA» più sotto).
Richieste e risposte sono JSON su HTTPS. La versione è nel percorso (/v1). L'API è disponibile nei piani che includono l'API per sviluppatori.
Avvio rapido#
Da zero alla prima risposta in tre passi.
1. Crea una chiave#
Nel cabinet apri la scheda Developers, dai un nome alla chiave e scegli i suoi scope, poi se vuoi un elenco di IP e origini del browser. La chiave viene mostrata una sola volta e ne conserviamo solo un hash: copiala direttamente nei segreti del tuo server, mai in un repository. Una chiave persa non si recupera: revocala e creane un'altra.
2. Verificala#
Chiama GET /me. Un 200 mostra quale negozio apre la chiave, i suoi scope e i suoi limiti. Sostituisci YOUR_API_KEY con la tua chiave.
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/me' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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. Fai una domanda#
Invia le parole del cliente a POST /answers (serve lo scope answer). Ricevi la migliore corrispondenza, alternative e un motivo in una riga per ciascuna.
Esempio di richiesta, 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"
}'Esempio di richiesta, 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);Esempio di richiesta, 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())Autenticazione#
Invia la chiave nell'intestazione Authorization come token bearer. Non metterla mai nell'URL: gli URL finiscono nei log e nella cronologia del browser.
Autenticazione
Authorization: Bearer YOUR_API_KEYUna chiave assente, malformata, sconosciuta o revocata dà sempre lo stesso 401 invalid_api_key, così la risposta non rivela mai a chi tira a indovinare che una chiave è quasi giusta. Le chiavi non valide ripetute da un indirizzo vengono rallentate con 429 too_many_failed_attempts.
Una chiave appartiene a un solo negozio e non può leggerne un altro: l'id di un prodotto di un altro negozio risponde 404, come uno inesistente.
Puoi restringere ancora la chiave con un elenco di IP consentiti (403 ip_not_allowed) e un elenco di origini del browser (403 origin_not_allowed). Gli scope si fissano alla creazione; per ampliarli, crea un'altra chiave.
Scope e limiti#
Ogni chiave porta gli scope che può usare. I limiti valgono per chiave e per scope.
| Scope | Endpoint | Chiamate ogni 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 ha inoltre un tetto giornaliero di 500 chiamate per chiave (giorno UTC), perché ogni risposta è una chiamata al modello. Una ripetizione con lo stesso Idempotency-Key non conta.
Ogni risposta include X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset; un 429 aggiunge Retry-After (secondi). Aspetta quel tempo invece di riprovare in ciclo. GET /me richiede una chiave valida ma nessuno scope in particolare.
Endpoint#
Ogni operazione qui sotto è costruita dal documento OpenAPI e ogni esempio viene eseguito dai nostri test contro l'API reale, quindi gli esempi non possono divergere dall'API. Le descrizioni di operazioni e campi sono in inglese, come nel documento.
GET /me#
Verifica quale negozio, scope e limiti ha una chiave.
Describes the key you are calling with: its scopes, shop, allow-lists and limits. Costs no quota; use it to check a key.
- Scope richiesto
- qualsiasi chiave valida
Nessun parametro.
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/me' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/me'Esempio di richiesta, 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);Esempio di richiesta, Python (requests)
import requests
response = requests.get(
"https://assistant.onesite.agency/api/v1/me",
)
print(response.status_code, response.json())Esempio di risposta · 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#
Fai una domanda con le parole del cliente e ottieni la migliore corrispondenza, alternative e un motivo per ciascuna.
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.
- Scope richiesto
answer- Limite
- 20 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
Idempotency-Key | header | string | null | no | lunghezza 1–64 | Makes a retry safe: the same key and body return the first answer without a second model call. |
Corpo della richiesta: AnswerRequest#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
query | string | sì | The shopper's question, in their own words. |
language | Language | null | no | Language of the reply (ru en el es de it fr). Defaults to the shop's language. |
session_id | string | null | no | 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 | no | Only products that can be bought now (excludes pre-orders). |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, 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"
}'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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)#
Esempio di richiesta, 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"
}'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
Suggerimenti rapidi di prodotti sotto un campo di ricerca mentre si digita. Nessun modello di IA coinvolto.
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.
- Scope richiesto
suggest- Limite
- 120 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
q | query | string | sì | lunghezza 1–500 | What has been typed so far. |
limit | query | integer | no | 1–20; predefinito 6 | |
in_stock_only | query | boolean | no | predefinito false | Only products that can be bought now. |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/suggestions?q=left+hand&limit=3' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
Leggi tutto il catalogo pagina per pagina.
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.
- Scope richiesto
catalogue:read- Limite
- 120 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
limit | query | integer | no | 1–100; predefinito 20 | |
cursor | query | string | null | no | lunghezza ≤ 64 | next_cursor of the previous page. |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products?limit=2' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products?limit=2&cursor=c1.MTAy' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products?limit=500' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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}#
Leggi un prodotto tramite il suo 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.
- Scope richiesto
catalogue:read- Limite
- 120 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
product_id | path | integer | sì | 1–9223372036854776000 | Product number (id in any product object). |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products/101' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products/101' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products/987654' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
Prodotti simili a uno dato. Nessun modello di IA coinvolto.
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.
- Scope richiesto
similar- Limite
- 60 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
product_id | path | integer | sì | 1–9223372036854776000 | Product number (id in any product object). |
limit | query | integer | no | 1–24; predefinito 8 |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/products/101/similar?limit=3' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
I totali del tuo negozio in un periodo — gli stessi numeri delle Statistiche del cabinet — con un confronto facoltativo.
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.
- Scope richiesto
analytics:read- Limite
- 30 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
compare | query | "prev" | "year" | "none" | no | predefinito "prev" | Compare with the window just before (prev), the same window a year back (year), or nothing. |
since | query | string (date-time) | null | no | | Start of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days. |
until | query | string (date-time) | null | no | | End of the window, exclusive (ISO 8601). Default: now. |
first_day | query | string (date) | null | no | | 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 | no | | Last day of the window (YYYY-MM-DD) in timezone, inclusive. |
timezone | query | string | null | no | lunghezza ≤ 64 | IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none. |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, 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'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
Domande, domande senza risultato e domande arrivate a un prodotto, per ogni giorno del periodo.
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.
- Scope richiesto
analytics:read- Limite
- 30 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
since | query | string (date-time) | null | no | | Start of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days. |
until | query | string (date-time) | null | no | | End of the window, exclusive (ISO 8601). Default: now. |
first_day | query | string (date) | null | no | | 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 | no | | Last day of the window (YYYY-MM-DD) in timezone, inclusive. |
timezone | query | string | null | no | lunghezza ≤ 64 | IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none. |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, 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'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
Le domande più frequenti e quelle che più spesso restano senza prodotto.
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.
- Scope richiesto
analytics:read- Limite
- 30 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
limit | query | integer | no | 1–100; predefinito 20 | Rows in each list. |
since | query | string (date-time) | null | no | | Start of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days. |
until | query | string (date-time) | null | no | | End of the window, exclusive (ISO 8601). Default: now. |
first_day | query | string (date) | null | no | | 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 | no | | Last day of the window (YYYY-MM-DD) in timezone, inclusive. |
timezone | query | string | null | no | lunghezza ≤ 64 | IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none. |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, 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'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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#
Ciò che l'assistente ha venduto, secondo i tuoi ordini — gli stessi numeri del riepilogo Acquisti del cabinet.
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.
- Scope richiesto
analytics:read- Limite
- 30 chiamate ogni 60 s
| Nome | Dove | Tipo | Obbligatorio | Vincoli | Descrizione |
|---|---|---|---|---|---|
days | query | integer | no | predefinito 30 | Period: today and the days before it. One of 7, 30, 90. |
timezone | query | string | null | no | lunghezza ≤ 64 | IANA time zone for days. Default: the shop's time zone, or UTC. |
| Stato | Schema della risposta | Descrizione |
|---|---|---|
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)#
Esempio di richiesta, curl
curl 'https://assistant.onesite.agency/api/v1/analytics/sales?days=7' \
-H 'Authorization: Bearer YOUR_API_KEY'Esempio di richiesta, 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);Esempio di richiesta, 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())Esempio di risposta · 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"
}
}
]
}Tipi di dati#
Gli oggetti usati in richieste e risposte. | null significa che il campo è sempre presente ma può essere null; un array si scrive Tipo[].
AiDisclosure#
Shown to your users? Then say so. The wording below is the minimum we ask integrations to pass on.
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
generated | boolean | no | Some text in this response was written by an AI model. |
fields | string[] | sì | JSON paths of the AI-written strings. |
notice | string | sì |
AnalyticsFunnelStep#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
stage | "visit" | "offered" | "opened" | "asked" | "reached" | "carted" | "bought" | sì | |
sessions | integer | sì | Sessions that reached this step. Sessions, not people: events on the site carry no visitor mark. |
share_of_previous | number | null | no | 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#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
since | string | sì | |
until | string | sì |
AnalyticsPoint#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
date | string | sì | The day (YYYY-MM-DD) in window.timezone. The first and last day may be partial: only the window is counted. |
queries | integer | sì | |
missed | integer | sì | |
reached | integer | sì | Questions of the day after which the shopper opened a product page from the answer. |
AnalyticsQueries#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "analytics.queries" | no | |
window | AnalyticsWindow | sì | |
top | AnalyticsQuery[] | sì | Most frequent questions, most frequent first. |
missed | AnalyticsQuery[] | sì | Questions most often left without a product, by number of misses. Its own list, not a filter of top. |
AnalyticsQuery#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
text | string | sì | The question as asked (case-insensitive group). E-mail addresses, phone and card numbers are masked. |
count | integer | sì | How many times it was asked. |
missed | integer | sì | How many of those had nothing to show. |
clicks | integer | sì | 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.
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "analytics.sales" | no | |
days | integer | sì | Today and the days before it, from midnight in timezone. |
since | string | sì | Midnight of the first day (ISO 8601). |
timezone | string | sì | |
currency | string | sì | Orders in another currency are not added up. |
assistant_orders | integer | sì | Orders with a product added to the cart by the assistant's button or bought after the assistant showed it. |
assistant_revenue | Money | sì | The value of those products (not the whole orders). |
button_revenue | Money | sì | Part of assistant_revenue added to the cart by the assistant's button. |
shown_revenue | Money | sì | Part of assistant_revenue bought after the assistant showed the product. |
carted | integer | sì | Products the assistant helped to put into a cart in the period (by its button or after showing them). |
talked_orders | integer | sì | Orders placed after a conversation with the assistant without any of the above. Not counted as its revenue. |
average_order | Money | null | no | Average total of assistant_orders; null when none has a total. |
days_data | AnalyticsSalesDay[] | sì | Every day of the period, empty days included. |
products | AnalyticsSoldProduct[] | sì | Top products sold through the assistant, by revenue. |
AnalyticsSalesDay#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
date | string | sì | The day (YYYY-MM-DD) in timezone. |
orders | integer | sì | |
revenue | Money | sì |
AnalyticsSeries#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "analytics.series" | no | |
window | AnalyticsWindow | sì | |
data | AnalyticsPoint[] | sì | Every day of the window, days without questions included as zeros. The sum of queries equals totals.queries of the summary. |
AnalyticsSoldProduct#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
product_id | integer | null | no | null when the product is no longer in the catalogue. |
title | string | null | no | |
quantity | integer | sì | |
revenue | Money | sì |
AnalyticsSummary#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "analytics.summary" | no | |
window | AnalyticsWindow | sì | |
totals | AnalyticsTotals | sì | |
compare | "prev" | "year" | "none" | sì | |
previous_window | AnalyticsPeriod | null | no | The window compared with. null when compare is none or the shop's history covers less than 90 % of it. |
previous | AnalyticsTotals | null | no | Totals of previous_window, counted the same way. null — no comparison. |
funnel | AnalyticsFunnelStep[] | sì | Sessions by step of the shopper's path, in order. |
funnel_since | string | null | no | 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.
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
queries | integer | sì | Questions people asked the assistant, in their own words. |
people | integer | sì | 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 | sì | Different sessions with at least one question. A session lasts about a day, so this is not people. |
answered | integer | sì | Questions answered with at least one product. |
missed | integer | sì | Questions with nothing to show. |
asked_again | integer | sì | Sessions that asked another question after an answer. |
impressions | integer | sì | Product cards shown in answers. |
clicks | integer | sì | Clicks from answer cards to product pages. |
reached | integer | sì | Questions after which the shopper opened a product page from the answer. Counted per question, not per click. |
browsed | integer | sì | Clicks to product pages from shelves and collections, with no question before them. |
AnalyticsWindow#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
since | string | sì | Start of the window, inclusive (ISO 8601, UTC). |
until | string | sì | End of the window, exclusive (ISO 8601, UTC). |
timezone | string | sì | IANA time zone in which days are cut (daily series, first_day/last_day). |
Answer#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
id | string | sì | Id of this answer in the shop's journal. Quote it when you report a problem. |
query | string | sì | |
intent | string | sì | 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 | no | A short spoken-style reply, written by an AI model. May be null. |
main | Recommendation | null | no | The single best match, when there is one. |
alternatives | Recommendation[] | no | |
shelves | Shelf[] | no | For “everything by …” questions: whole shelves instead of picks. |
miss | boolean | sì | true when the shop has nothing that fits. Do not invent a product then. |
miss_reason | "absent" | "unstocked" | "unmatched" | null | no | 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 | no | A clarifying question to ask the user, when the answer needs one. |
degraded | boolean | no | true when the answer was built without the model (it was unavailable). |
currency | string | sì | |
ai | AiDisclosure | sì |
AnswerRequest#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
query | string | sì | The shopper's question, in their own words. |
language | Language | null | no | Language of the reply (ru en el es de it fr). Defaults to the shop's language. |
session_id | string | null | no | 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 | no | Only products that can be bought now (excludes pre-orders). |
ErrorBody#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
error | ErrorDetail | sì |
ErrorDetail#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
type | "authentication_error" | "permission_error" | "invalid_request_error" | "not_found_error" | "conflict_error" | "rate_limit_error" | "billing_error" | "api_error" | sì | |
code | string | sì | Stable machine-readable reason. Branch on this, not on message. |
message | string | sì | For humans. The wording may change between releases. |
param | string | null | no | The offending field or parameter, for invalid_request. |
request_id | string | sì | Also in the X-Request-Id response header. |
KeyInfo#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
id | integer | sì | |
name | string | sì | |
hint | string | sì | Which key this is, e.g. osk_…x9Zq. The secret itself is never shown again. |
scopes | string[] | sì | |
shop | KeyShop | sì | |
origins | string[] | sì | Browser origins allowed to use this key. Empty = any (server-side use). |
networks | string[] | sì | IP networks the key is accepted from. Empty = any. |
created_at | string | sì | |
last_used_at | string | null | no | |
limits | map<string, ScopeLimit> | sì | Calls per minute, per scope. |
answers_per_day | integer | sì | Daily cap on POST /answers calls for this key (UTC day). |
KeyShop#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
domain | string | sì | |
currency | string | sì | |
language | string | sì |
Language#
Language code: ru, en, el, es, de, it, fr.
"ru" | "en" | "el" | "es" | "de" | "it" | "fr"
Money#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
amount | string | sì | Decimal amount as a string, e.g. "12.90". Never a float: no rounding surprises. |
currency | string | sì | 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.
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
id | integer | sì | Product number in this shop's catalogue. Stable until the product is removed from the shop. |
title | string | sì | |
maker | string | null | no | Whoever the shop credits as the maker of the product. |
line | string | null | no | Series, collection or product line, when the shop names one. |
url | string | null | no | Product page on the shop's own site. |
handle | string | null | no | The platform's product handle (Shopify handle, WooCommerce slug), when known. |
external_id | string | null | no | The shop platform's own product id, for mapping back to your system. |
price | Money | null | no | Current price; null when the shop does not publish one. |
availability | "in_stock" | "preorder" | "out_of_stock" | sì | |
ready_at | string | null | no | For preorder: the shop's promised ship date (ISO 8601). |
image_url | string | null | no | |
thumb_url | string | null | no | A smaller image, when the shop has one. |
ProductDetail#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
id | integer | sì | Product number in this shop's catalogue. Stable until the product is removed from the shop. |
title | string | sì | |
maker | string | null | no | Whoever the shop credits as the maker of the product. |
line | string | null | no | Series, collection or product line, when the shop names one. |
url | string | null | no | Product page on the shop's own site. |
handle | string | null | no | The platform's product handle (Shopify handle, WooCommerce slug), when known. |
external_id | string | null | no | The shop platform's own product id, for mapping back to your system. |
price | Money | null | no | Current price; null when the shop does not publish one. |
availability | "in_stock" | "preorder" | "out_of_stock" | sì | |
ready_at | string | null | no | For preorder: the shop's promised ship date (ISO 8601). |
image_url | string | null | no | |
thumb_url | string | null | no | A smaller image, when the shop has one. |
description | string | null | no | |
images | string[] | no | The shop's gallery, in the shop's order. |
ProductPage#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "list" | no | |
data | Product[] | sì | |
has_more | boolean | sì | |
next_cursor | string | null | no | Pass as cursor to get the next page. null on the last page. |
Recommendation#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
product | Product | sì | |
why | string | null | no | 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 | no | Who wrote why. null when there is no why. |
ScopeLimit#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
per_minute | integer | sì |
Shelf#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
title | string | sì | |
total | integer | sì | How many products the shelf holds in the shop — not how many are shown in items. |
items | Product[] | sì |
SimilarList#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "list" | no | |
data | Product[] | sì | |
product_id | integer | sì |
SuggestionList#
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
object | "list" | no | |
query | string | sì | |
data | Product[] | sì |
Errori#
Tutti gli errori hanno la stessa forma e un request_id (anche nell'intestazione X-Request-Id): citalo quando ci scrivi. Ramifica su code, mai su message, il cui testo può cambiare.
Errori · 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"
}
}| Codice | HTTP | Cosa significa e cosa fare | Riprova |
|---|---|---|---|
invalid_api_key | 401 | La chiave manca, è malformata, sconosciuta o revocata. I quattro casi hanno lo stesso aspetto di proposito. Invia Authorization: Bearer <chiave> o crea una nuova chiave. | correggi la richiesta |
ip_not_allowed | 403 | La chiave ha un elenco di IP consentiti e questo indirizzo non c'è. Chiama da una rete in elenco o modifica l'elenco nel cabinet. | correggi la richiesta |
origin_not_allowed | 403 | La chiamata arriva da una pagina del browser la cui origine non è nell'elenco delle origini della chiave. Aggiungi l'origine esatta (schema, host, porta) nel cabinet. | correggi la richiesta |
insufficient_scope | 403 | La chiave non ha lo scope richiesto da questo endpoint. Gli scope non si ampliano: crea una nuova chiave con quello scope. | correggi la richiesta |
api_disabled | 403 | L'API per sviluppatori non è attiva per questo negozio. Apri la scheda Developers nel cabinet o scrivici se non la vedi. | correggi la richiesta |
subscription_inactive | 402 | L'abbonamento del negozio non è attivo, quindi l'API è in pausa. Riprende da sola quando l'abbonamento torna attivo; le chiavi restano valide. | correggi la richiesta |
plan_not_entitled | 403 | Il piano del negozio non include l'API per sviluppatori. È disponibile nei piani che la includono. | correggi la richiesta |
rate_limited | 429 | Il limite al minuto per questa chiave e questo scope è esaurito. Attendi i secondi di Retry-After e riprova; distribuisci le chiamate o metti i risultati in cache. | più tardi |
daily_quota_exceeded | 429 | Il tetto giornaliero di risposte per questa chiave è raggiunto (giorno UTC). Riprova dopo i secondi di Retry-After, cioè al cambio di giorno. | più tardi |
too_many_failed_attempts | 429 | Da questo indirizzo sono arrivate troppe chiavi non valide. Attendi i secondi di Retry-After e controlla quale chiave invia il tuo codice. | più tardi |
invalid_request | 422 | Un parametro o un campo del corpo manca o è fuori intervallo. param indica il primo; correggi la richiesta, riprovare non serve. | correggi la richiesta |
invalid_cursor | 422 | Il cursore è stato modificato o non appartiene a questo elenco. Passa next_cursor esattamente come ricevuto, oppure ricomincia senza cursore. | correggi la richiesta |
product_not_found | 404 | Questo prodotto non esiste in questo negozio. L'id di un prodotto di un altro negozio risponde allo stesso modo: una chiave legge solo il proprio negozio. | correggi la richiesta |
idempotency_key_reused | 422 | Questo Idempotency-Key è già stato usato con un corpo diverso. Usa una nuova chiave per un'altra domanda. | correggi la richiesta |
idempotency_in_progress | 409 | Una richiesta con lo stesso Idempotency-Key è ancora in elaborazione. Attendi un attimo e invia di nuovo la stessa richiesta per ottenere il risultato. | più tardi |
request_too_large | 413 | Il corpo della richiesta supera il limite. Invia un corpo più piccolo. | correggi la richiesta |
answer_unavailable | 502 | Al momento non è stato possibile produrre la risposta. Riprova a breve con lo stesso Idempotency-Key. | più tardi |
internal_error | 500 | Qualcosa è andato storto da parte nostra. Riprova con una pausa; se persiste, inviaci il request_id. | più tardi |
not_found | 404 | Questo endpoint non esiste. Controlla il percorso nel riferimento; i percorsi iniziano con la versione, /api/v1. | correggi la richiesta |
method_not_allowed | 405 | Il percorso esiste ma non per questo metodo HTTP. L'intestazione Allow elenca quelli consentiti. | correggi la richiesta |
type raggruppa i codici: authentication_error, permission_error, invalid_request_error, not_found_error, conflict_error, rate_limit_error, billing_error, api_error. Nuovi codici possono comparire in v1; tratta un code sconosciuto in base al suo stato HTTP.
Paginazione#
Gli elenchi usano cursori. Passa next_cursor di una risposta come cursor nella richiesta successiva finché next_cursor è null. limit vale 20 di default e al massimo 100. Una pagina può contenere meno elementi di limit finché has_more è true: segui next_cursor invece di contare. Tratta il cursore come opaco: uno alterato dà 422 invalid_cursor.
Cache ed ETag#
Le risposte GET portano un ETag. Rimandalo come If-None-Match; se nulla è cambiato ricevi 304 Not Modified senza corpo, più veloce e leggero che riscaricare l'elenco. Metti in cache le letture del catalogo e riconvalida così.
Idempotenza e nuovi tentativi#
POST /answers accetta l'intestazione Idempotency-Key (da 1 a 64 caratteri: lettere, cifre e . _ : -). Ripetere lo stesso corpo entro 24 ore restituisce la prima risposta senza una seconda chiamata al modello e senza consumare quota; la ripetizione porta Idempotent-Replayed: true. La stessa chiave con un corpo diverso dà 422 idempotency_key_reused; finché la prima richiesta è in corso, 409 idempotency_in_progress.
Crea un UUID nuovo per ogni domanda dell'utente e riusalo solo per i nuovi tentativi di quella stessa domanda.
I corpi delle richieste sono limitati a 16384 byte (413 request_too_large).
Browser e CORS#
Una pagina web può chiamare l'API solo da un'origine elencata sulla chiave (schema, host e porta, per esempio https://shop.example); l'API invia intestazioni CORS solo alle origini elencate. Tutto ciò che sta nel codice di una pagina è pubblico, quindi una chiave usata lì può essere copiata da chiunque apra la pagina.
Quindi, per l'uso nel browser, dai alla chiave solo gli scope suggest e similar più un elenco di origini, e tieni le chiavi answer sul tuo server.
Analisi#
Il permesso analytics:read apre i numeri del tuo negozio: gli stessi totali, serie giornaliera, domande principali e vendite dell'assistente del cabinet, calcolati dallo stesso codice. Assegnalo solo a chiavi che restano sul tuo server.
Si contano solo persone: non i bot, non le nostre verifiche, non le chiamate fatte con chiavi API né le conversazioni rimosse dall'operatore. people conta visitatori distinti dal segno nel loro browser — chi torna dopo tre giorni è una persona, la stessa persona con smartphone e portatile ne conta due; le sessions durano circa un giorno; i passaggi dell'imbuto sono sessioni. Il fatturato totale del negozio non fa parte dell'API.
Un periodo è since/until (ISO 8601; senza offset, UTC) oppure first_day/last_day, entrambi i giorni inclusi, tagliati in timezone (predefinito: il fuso del negozio, o UTC). Un confronto (compare) viene restituito solo se lo storico del negozio copre il periodo di confronto; altrimenti previous è null.
Testo generato dall'IA#
In una risposta, say e ogni why sono scritti da un modello di IA, e la risposta lo dichiara nel suo oggetto ai: generated, i percorsi JSON in fields e un testo notice. Se mostri questi testi ai tuoi utenti, di' loro che sono generati dall'IA; notice è la formulazione minima che chiediamo alle integrazioni di riportare.
Nomi, prezzi, disponibilità e link non vengono mai dal modello. Quando miss è true, il negozio non ha nulla di adatto: mostralo e non inventare un prodotto.
Versioni#
La versione fa parte del percorso: /v1. All'interno di v1 facciamo solo modifiche retrocompatibili: nuovi endpoint, nuovi parametri opzionali, nuovi campi di risposta e nuovi valori di enumerazioni (per esempio nuovi valori di intent). Scrivi client che ignorano i campi sconosciuti e ripiegano in modo sensato sui valori di enumerazione sconosciuti. Una modifica incompatibile uscirebbe solo con un nuovo percorso di versione.
Per macchine e assistenti IA#
Lo stesso riferimento in forma leggibile dalle macchine. Importa il documento OpenAPI in Postman, Insomnia o in un generatore di codice.