Vai al contenuto
In questa pagina

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_KEY

Una 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, relativi endpoint e limiti
ScopeEndpointChiamate ogni 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 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.

Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
Idempotency-Keyheaderstring | nullnolunghezza 1–64Makes a retry safe: the same key and body return the first answer without a second model call.

Corpo della richiesta: AnswerRequest#

Schema della richiesta
CampoTipoObbligatorioDescrizione
querystringsìThe shopper's question, in their own words.
languageLanguage | nullnoLanguage of the reply (ru en el es de it fr). Defaults to the shop's language.
session_idstring | nullnoOpaque 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_onlybooleannoOnly products that can be bought now (excludes pre-orders).
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
qquerystringsìlunghezza 1–500What has been typed so far.
limitqueryintegerno1–20; predefinito 6
in_stock_onlyquerybooleannopredefinito falseOnly products that can be bought now.
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
limitqueryintegerno1–100; predefinito 20
cursorquerystring | nullnolunghezza ≤ 64next_cursor of the previous page.
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
product_idpathintegersì1–9223372036854776000Product number (id in any product object).
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
product_idpathintegersì1–9223372036854776000Product number (id in any product object).
limitqueryintegerno1–24; predefinito 8
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
comparequery"prev" | "year" | "none"nopredefinito "prev"Compare with the window just before (prev), the same window a year back (year), or nothing.
sincequerystring (date-time) | nullnoStart of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days.
untilquerystring (date-time) | nullnoEnd of the window, exclusive (ISO 8601). Default: now.
first_dayquerystring (date) | nullnoFirst day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until.
last_dayquerystring (date) | nullnoLast day of the window (YYYY-MM-DD) in timezone, inclusive.
timezonequerystring | nullnolunghezza ≤ 64IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none.
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
sincequerystring (date-time) | nullnoStart of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days.
untilquerystring (date-time) | nullnoEnd of the window, exclusive (ISO 8601). Default: now.
first_dayquerystring (date) | nullnoFirst day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until.
last_dayquerystring (date) | nullnoLast day of the window (YYYY-MM-DD) in timezone, inclusive.
timezonequerystring | nullnolunghezza ≤ 64IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none.
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
limitqueryintegerno1–100; predefinito 20Rows in each list.
sincequerystring (date-time) | nullnoStart of the window (ISO 8601; without an offset — UTC). Default: until minus 30 days.
untilquerystring (date-time) | nullnoEnd of the window, exclusive (ISO 8601). Default: now.
first_dayquerystring (date) | nullnoFirst day of the window (YYYY-MM-DD) in timezone, inclusive. Use with last_day instead of since/until.
last_dayquerystring (date) | nullnoLast day of the window (YYYY-MM-DD) in timezone, inclusive.
timezonequerystring | nullnolunghezza ≤ 64IANA time zone for days, e.g. Europe/Berlin. Default: the shop's time zone, or UTC when the shop has none.
Risposte
StatoSchema della rispostaDescrizione
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)#

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
Parametri
NomeDoveTipoObbligatorioVincoliDescrizione
daysqueryintegernopredefinito 30Period: today and the days before it. One of 7, 30, 90.
timezonequerystring | nullnolunghezza ≤ 64IANA time zone for days. Default: the shop's time zone, or UTC.
Risposte
StatoSchema della rispostaDescrizione
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)#

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.

AiDisclosure
CampoTipoObbligatorioDescrizione
generatedbooleannoSome text in this response was written by an AI model.
fieldsstring[]sìJSON paths of the AI-written strings.
noticestringsì

AnalyticsFunnelStep#

AnalyticsFunnelStep
CampoTipoObbligatorioDescrizione
stage"visit" | "offered" | "opened" | "asked" | "reached" | "carted" | "bought"sì
sessionsintegersìSessions that reached this step. Sessions, not people: events on the site carry no visitor mark.
share_of_previousnumber | nullnosessions 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
CampoTipoObbligatorioDescrizione
sincestringsì
untilstringsì

AnalyticsPoint#

AnalyticsPoint
CampoTipoObbligatorioDescrizione
datestringsìThe day (YYYY-MM-DD) in window.timezone. The first and last day may be partial: only the window is counted.
queriesintegersì
missedintegersì
reachedintegersìQuestions of the day after which the shopper opened a product page from the answer.

AnalyticsQueries#

AnalyticsQueries
CampoTipoObbligatorioDescrizione
object"analytics.queries"no
windowAnalyticsWindowsì
topAnalyticsQuery[]sìMost frequent questions, most frequent first.
missedAnalyticsQuery[]sìQuestions most often left without a product, by number of misses. Its own list, not a filter of top.

AnalyticsQuery#

AnalyticsQuery
CampoTipoObbligatorioDescrizione
textstringsìThe question as asked (case-insensitive group). E-mail addresses, phone and card numbers are masked.
countintegersìHow many times it was asked.
missedintegersìHow many of those had nothing to show.
clicksintegersì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.

AnalyticsSales
CampoTipoObbligatorioDescrizione
object"analytics.sales"no
daysintegersìToday and the days before it, from midnight in timezone.
sincestringsìMidnight of the first day (ISO 8601).
timezonestringsì
currencystringsìOrders in another currency are not added up.
assistant_ordersintegersìOrders with a product added to the cart by the assistant's button or bought after the assistant showed it.
assistant_revenueMoneysìThe value of those products (not the whole orders).
button_revenueMoneysìPart of assistant_revenue added to the cart by the assistant's button.
shown_revenueMoneysìPart of assistant_revenue bought after the assistant showed the product.
cartedintegersìProducts the assistant helped to put into a cart in the period (by its button or after showing them).
talked_ordersintegersìOrders placed after a conversation with the assistant without any of the above. Not counted as its revenue.
average_orderMoney | nullnoAverage total of assistant_orders; null when none has a total.
days_dataAnalyticsSalesDay[]sìEvery day of the period, empty days included.
productsAnalyticsSoldProduct[]sìTop products sold through the assistant, by revenue.

AnalyticsSalesDay#

AnalyticsSalesDay
CampoTipoObbligatorioDescrizione
datestringsìThe day (YYYY-MM-DD) in timezone.
ordersintegersì
revenueMoneysì

AnalyticsSeries#

AnalyticsSeries
CampoTipoObbligatorioDescrizione
object"analytics.series"no
windowAnalyticsWindowsì
dataAnalyticsPoint[]sìEvery day of the window, days without questions included as zeros. The sum of queries equals totals.queries of the summary.

AnalyticsSoldProduct#

AnalyticsSoldProduct
CampoTipoObbligatorioDescrizione
product_idinteger | nullnonull when the product is no longer in the catalogue.
titlestring | nullno
quantityintegersì
revenueMoneysì

AnalyticsSummary#

AnalyticsSummary
CampoTipoObbligatorioDescrizione
object"analytics.summary"no
windowAnalyticsWindowsì
totalsAnalyticsTotalssì
compare"prev" | "year" | "none"sì
previous_windowAnalyticsPeriod | nullnoThe window compared with. null when compare is none or the shop's history covers less than 90 % of it.
previousAnalyticsTotals | nullnoTotals of previous_window, counted the same way. null — no comparison.
funnelAnalyticsFunnelStep[]sìSessions by step of the shopper's path, in order.
funnel_sincestring | nullnoSet 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
CampoTipoObbligatorioDescrizione
queriesintegersìQuestions people asked the assistant, in their own words.
peopleintegersì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.
sessionsintegersìDifferent sessions with at least one question. A session lasts about a day, so this is not people.
answeredintegersìQuestions answered with at least one product.
missedintegersìQuestions with nothing to show.
asked_againintegersìSessions that asked another question after an answer.
impressionsintegersìProduct cards shown in answers.
clicksintegersìClicks from answer cards to product pages.
reachedintegersìQuestions after which the shopper opened a product page from the answer. Counted per question, not per click.
browsedintegersìClicks to product pages from shelves and collections, with no question before them.

AnalyticsWindow#

AnalyticsWindow
CampoTipoObbligatorioDescrizione
sincestringsìStart of the window, inclusive (ISO 8601, UTC).
untilstringsìEnd of the window, exclusive (ISO 8601, UTC).
timezonestringsìIANA time zone in which days are cut (daily series, first_day/last_day).

Answer#

Answer
CampoTipoObbligatorioDescrizione
idstringsìId of this answer in the shop's journal. Quote it when you report a problem.
querystringsì
intentstringsì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.
saystring | nullnoA short spoken-style reply, written by an AI model. May be null.
mainRecommendation | nullnoThe single best match, when there is one.
alternativesRecommendation[]no
shelvesShelf[]noFor “everything by …” questions: whole shelves instead of picks.
missbooleansìtrue when the shop has nothing that fits. Do not invent a product then.
miss_reason"absent" | "unstocked" | "unmatched" | nullnoWhy nothing is shown: not in the catalogue (absent), sold out (unstocked), or nothing matched the question's hard limits such as price (unmatched).
clarifystring | nullnoA clarifying question to ask the user, when the answer needs one.
degradedbooleannotrue when the answer was built without the model (it was unavailable).
currencystringsì
aiAiDisclosuresì

AnswerRequest#

AnswerRequest
CampoTipoObbligatorioDescrizione
querystringsìThe shopper's question, in their own words.
languageLanguage | nullnoLanguage of the reply (ru en el es de it fr). Defaults to the shop's language.
session_idstring | nullnoOpaque 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_onlybooleannoOnly products that can be bought now (excludes pre-orders).

ErrorBody#

ErrorBody
CampoTipoObbligatorioDescrizione
errorErrorDetailsì

ErrorDetail#

ErrorDetail
CampoTipoObbligatorioDescrizione
type"authentication_error" | "permission_error" | "invalid_request_error" | "not_found_error" | "conflict_error" | "rate_limit_error" | "billing_error" | "api_error"sì
codestringsìStable machine-readable reason. Branch on this, not on message.
messagestringsìFor humans. The wording may change between releases.
paramstring | nullnoThe offending field or parameter, for invalid_request.
request_idstringsìAlso in the X-Request-Id response header.

KeyInfo#

KeyInfo
CampoTipoObbligatorioDescrizione
idintegersì
namestringsì
hintstringsìWhich key this is, e.g. osk_…x9Zq. The secret itself is never shown again.
scopesstring[]sì
shopKeyShopsì
originsstring[]sìBrowser origins allowed to use this key. Empty = any (server-side use).
networksstring[]sìIP networks the key is accepted from. Empty = any.
created_atstringsì
last_used_atstring | nullno
limitsmap<string, ScopeLimit>sìCalls per minute, per scope.
answers_per_dayintegersìDaily cap on POST /answers calls for this key (UTC day).

KeyShop#

KeyShop
CampoTipoObbligatorioDescrizione
domainstringsì
currencystringsì
languagestringsì

Language#

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

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

Money#

Money
CampoTipoObbligatorioDescrizione
amountstringsìDecimal amount as a string, e.g. "12.90". Never a float: no rounding surprises.
currencystringsì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.

Product
CampoTipoObbligatorioDescrizione
idintegersìProduct number in this shop's catalogue. Stable until the product is removed from the shop.
titlestringsì
makerstring | nullnoWhoever the shop credits as the maker of the product.
linestring | nullnoSeries, collection or product line, when the shop names one.
urlstring | nullnoProduct page on the shop's own site.
handlestring | nullnoThe platform's product handle (Shopify handle, WooCommerce slug), when known.
external_idstring | nullnoThe shop platform's own product id, for mapping back to your system.
priceMoney | nullnoCurrent price; null when the shop does not publish one.
availability"in_stock" | "preorder" | "out_of_stock"sì
ready_atstring | nullnoFor preorder: the shop's promised ship date (ISO 8601).
image_urlstring | nullno
thumb_urlstring | nullnoA smaller image, when the shop has one.

ProductDetail#

ProductDetail
CampoTipoObbligatorioDescrizione
idintegersìProduct number in this shop's catalogue. Stable until the product is removed from the shop.
titlestringsì
makerstring | nullnoWhoever the shop credits as the maker of the product.
linestring | nullnoSeries, collection or product line, when the shop names one.
urlstring | nullnoProduct page on the shop's own site.
handlestring | nullnoThe platform's product handle (Shopify handle, WooCommerce slug), when known.
external_idstring | nullnoThe shop platform's own product id, for mapping back to your system.
priceMoney | nullnoCurrent price; null when the shop does not publish one.
availability"in_stock" | "preorder" | "out_of_stock"sì
ready_atstring | nullnoFor preorder: the shop's promised ship date (ISO 8601).
image_urlstring | nullno
thumb_urlstring | nullnoA smaller image, when the shop has one.
descriptionstring | nullno
imagesstring[]noThe shop's gallery, in the shop's order.

ProductPage#

ProductPage
CampoTipoObbligatorioDescrizione
object"list"no
dataProduct[]sì
has_morebooleansì
next_cursorstring | nullnoPass as cursor to get the next page. null on the last page.

Recommendation#

Recommendation
CampoTipoObbligatorioDescrizione
productProductsì
whystring | nullnoOne 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" | nullnoWho wrote why. null when there is no why.

ScopeLimit#

ScopeLimit
CampoTipoObbligatorioDescrizione
per_minuteintegersì

Shelf#

Shelf
CampoTipoObbligatorioDescrizione
titlestringsì
totalintegersìHow many products the shelf holds in the shop — not how many are shown in items.
itemsProduct[]sì

SimilarList#

SimilarList
CampoTipoObbligatorioDescrizione
object"list"no
dataProduct[]sì
product_idintegersì

SuggestionList#

SuggestionList
CampoTipoObbligatorioDescrizione
object"list"no
querystringsì
dataProduct[]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"
  }
}
Codici di errore
CodiceHTTPCosa significa e cosa fareRiprova
invalid_api_key401La 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_allowed403La 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_allowed403La 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_scope403La 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_disabled403L'API per sviluppatori non è attiva per questo negozio. Apri la scheda Developers nel cabinet o scrivici se non la vedi.correggi la richiesta
subscription_inactive402L'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_entitled403Il piano del negozio non include l'API per sviluppatori. È disponibile nei piani che la includono.correggi la richiesta
rate_limited429Il 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_exceeded429Il 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_attempts429Da 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_request422Un parametro o un campo del corpo manca o è fuori intervallo. param indica il primo; correggi la richiesta, riprovare non serve.correggi la richiesta
invalid_cursor422Il cursore è stato modificato o non appartiene a questo elenco. Passa next_cursor esattamente come ricevuto, oppure ricomincia senza cursore.correggi la richiesta
product_not_found404Questo 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_reused422Questo Idempotency-Key è già stato usato con un corpo diverso. Usa una nuova chiave per un'altra domanda.correggi la richiesta
idempotency_in_progress409Una 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_large413Il corpo della richiesta supera il limite. Invia un corpo più piccolo.correggi la richiesta
answer_unavailable502Al momento non è stato possibile produrre la risposta. Riprova a breve con lo stesso Idempotency-Key.più tardi
internal_error500Qualcosa è andato storto da parte nostra. Riprova con una pausa; se persiste, inviaci il request_id.più tardi
not_found404Questo endpoint non esiste. Controlla il percorso nel riferimento; i percorsi iniziano con la versione, /api/v1.correggi la richiesta
method_not_allowed405Il 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.