Passa al contenuto principale

Chat REST API

API HTTP per integrare la chat Tidiko senza widget e senza SDK. Due host:

HostBaseCosa fa
Laravelhttps://app.tidiko.aiScambio chiavi pk_ / sk_ → JWT di chat
Node Agenthttps://langgraphjs-prod.tidiko.aiThread, storico, messaggio JSON o SSE

Staging Node: https://langgraphjs-stage.tidiko.ai. In locale usa NODE_SERVER_URL / socket_url restituito dallo scambio token.

Widget, SDK o REST?
  • Widget Preact — UI Tidiko già pronta.
  • Chat SDK Headless — socket + stato in browser, UI tua.
  • Questa pagina — backend, CLI, mobile nativo, o un client HTTP tuo. Nessun Socket.io.

Le route interne Node (/create-thread/..., upload, queue) sono un altro contratto: HTTP API Node Agent.

Prova l’API

SuperficieURLChi la usa
Chat API docs (OpenAPI + UI)https://app.tidiko.ai/docs/apiTutti. Spec interattiva: token, thread, JSON e SSE.
Spec machine-readablehttps://app.tidiko.ai/docs/openapi.jsonClient codegen, Postman, CI.
Playground (dashboard)https://app.tidiko.ai/playgroundUtente loggato. Prova REST Chat e Chat SDK sull’agente scelto.

Nel Playground non incolli pk_ / sk_: la dashboard emette un JWT di sessione. I messaggi contano sul piano della company. Apri il tab REST con ?tab=rest, oppure un agente preciso con ?type=agent&uuid=…&tab=rest.

Nelle Chat API docs, “Try it out” sulle route Node richiede Authorization: Bearer con l’access JWT (non la chiave raw). Non incollare chiavi o JWT veri su uno schermo condiviso. I server nello spec seguono APP_URL e NODE_SERVER_URL dell’ambiente.

Cosa fa e cosa non fa

FaNon fa
JWT aud: tidiko-chat da pk_ / sk_Accettare pk_ / sk_ raw sul Node
Creare thread e inviare messaggiPrivacy cookie / welcome / form HITL del widget
Risposta JSON completa o stream SSEUI, markdown, product card
Autorizzare ogni threadId su company + agente del tokenLasciare che il client imposti laravelAppUrl o la collection

Flusso

  1. Crea una chiave API in dashboard (API Keys).
  2. Scambia la chiave su Laravel. Per REST da server usa sk_.
  3. Chiama il Node con Authorization: Bearer <access JWT>.
  4. Crea un thread, poi invia messaggi su quel threadId.
  5. Rinnova con refresh_token prima che scada l’access (TTL 3600s).

Esempio completo (server)

Da un backend (mai da una pagina pubblica). Sostituisci le variabili, poi esegui i passi in ordine.

LARAVEL="https://app.tidiko.ai"
SECRET="sk_live_xxx"
AGENT_ID="AGENT_UUID"

# 1. Scambia la chiave segreta (salva token e socket_url dalla risposta)
TOKEN_JSON=$(curl -sS -X POST "$LARAVEL/api/v1/service-token" \
-H "Content-Type: application/json" \
-d "{\"secret_key\":\"$SECRET\",\"agent_id\":\"$AGENT_ID\",\"type\":\"agent\"}")
ACCESS=$(printf '%s' "$TOKEN_JSON" | jq -r '.data.token')
NODE=$(printf '%s' "$TOKEN_JSON" | jq -r '.data.socket_url')
# Usa sempre data.socket_url (prod / stage / locale), non un host fisso.

# 2. Crea un thread
curl -sS -X POST "$NODE/v1/threads" \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-d '{}'
# Salva threadId (es. thread_<uuid>_<uuid>).

THREAD="thread_OWNER_UUID"

# 3. Invia un messaggio (JSON, attende la risposta intera)
curl -sS -X POST "$NODE/v1/threads/$THREAD/messages" \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-d '{"message":"Ciao, quali sono i vostri orari?"}'

# 4. Oppure stream SSE (stesso URL)
curl -N -X POST "$NODE/v1/threads/$THREAD/messages" \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"message":"Ciao, quali sono i vostri orari?"}'

# 5. Storico
curl -sS "$NODE/v1/threads/$THREAD/messages" \
-H "Authorization: Bearer $ACCESS"

Per assistant usa "type":"assistant" e l’UUID dell’assistente (con o senza prefisso assistant_).

Token (Laravel)

Prefisso route: /api/v1. Content-Type: application/json.

POST /api/v1/token — chiave pubblica (pk_)

Per frontend / SDK. Origin serve solo al CORS (allowed_origins sulla chiave). L’auth è pk_ + binding company/agente.

curl -X POST https://app.tidiko.ai/api/v1/token \
-H "Content-Type: application/json" \
-H "Origin: https://tuo-sito.example" \
-d '{"public_key":"pk_live_xxx","agent_id":"AGENT_UUID","type":"agent"}'

POST /api/v1/service-token — chiave segreta (sk_)

Solo backend. Nessun CORS. Mai in HTML, bundle frontend o playground pubblico.

curl -X POST https://app.tidiko.ai/api/v1/service-token \
-H "Content-Type: application/json" \
-d '{"secret_key":"sk_live_xxx","agent_id":"AGENT_UUID","type":"agent"}'

Body comune

CampoObbligatorioValori
public_key o secret_keypk_… / sk_…
agent_idUUID, con o senza prefisso agent_ / assistant_
typeagent o assistant

Se la chiave è vincolata a un agente, agent_id deve coincidere (i prefissi vengono normalizzati).

POST /api/v1/token/refresh

curl -X POST https://app.tidiko.ai/api/v1/token/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"REFRESH_JWT"}'

Il refresh è single-use (JTI). Non riusarlo. Diverso da POST /api/jwt/refresh del widget embed.

Risposta 200

{
"success": true,
"data": {
"token": "eyJ…",
"refresh_token": "eyJ…",
"expires_in": 3600,
"jwt": "eyJ…",
"jwtRefresh": "eyJ…",
"socket_url": "https://langgraphjs-prod.tidiko.ai"
}
}
CampoNote
token / jwtAccess JWT, aud: tidiko-chat, token_use: access, TTL 3600s
refresh_token / jwtRefreshRefresh, aud: tidiko-chat-refresh, TTL 86400s
socket_urlBase URL del Node Agent (anche per REST, non solo socket)

Scope di default: pk_chat:read, chat:write. sk_ → gli stessi più threads:manage.

Errori token

HTTPcodeQuando
404agent_not_foundAgente/assistente inesistente
403agent_not_allowedCompany o binding chiave non coincidono
401invalid_refresh_tokenRefresh invalido, scaduto o già speso
422(validazione Laravel)Campi mancanti / type non valido

Chat (Node Agent)

Base: valore di socket_url (senza slash finale). Header obbligatorio:

Authorization: Bearer <access JWT>

Accettati solo JWT firmati HS256, iss: neting-ai, aud: tidiko-chat, token_use: access, con companyId, agentId, jti.

Rifiutati (non usare come Bearer sul Node):

CredenzialeHTTPerror
Header assente / non Bearer401No token provided / Unauthorized
pk_… o sk_… raw403invalid_key
Refresh (token_use: refresh o aud refresh)401invalid_token
JWT widget (aud: tidiko-widget)403forbidden
JWT scaduto / firma errata401Invalid token

Il Node carica il runtime (collection, istruzioni, limiti) da Laravel in base all’identità del token. Campi extra nel body (laravelAppUrl, collection_name, …) non diventano contesto trusted.

POST /v1/threads

Crea un thread e registra la conversazione su Laravel. Serve scope chat:write.

curl -X POST https://langgraphjs-prod.tidiko.ai/v1/threads \
-H "Authorization: Bearer $ACCESS_JWT" \
-H "Content-Type: application/json" \
-d '{}'

201:

{ "threadId": "thread_<ownerUuid>_<uuid>" }

Body opzionale: { "agentId": "agent_…" }. Se presente e diverso dal token → 403 { "error": "agent_mismatch" }.

POST /v1/threads/:threadId/messages

Invia un messaggio. Serve chat:write. threadId path-safe, 1–191 caratteri, regex ^[A-Za-z0-9][A-Za-z0-9._:-]*$.

{ "message": "Ciao" }

message stringa obbligatoria, altrimenti 400 { "error": "message is required" }.

Risposta JSON (default)

curl -X POST "https://langgraphjs-prod.tidiko.ai/v1/threads/$THREAD_ID/messages" \
-H "Authorization: Bearer $ACCESS_JWT" \
-H "Content-Type: application/json" \
-d '{"message":"Ciao"}'

200: { "message": "testo completo dell'assistente" }.

Stream SSE

Stesso URL. Header Accept: text/event-stream. curl -N non bufferizza.

curl -N -X POST "https://langgraphjs-prod.tidiko.ai/v1/threads/$THREAD_ID/messages" \
-H "Authorization: Bearer $ACCESS_JWT" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"message":"Ciao"}'
EventodataQuando
start{}Inizio stream
chunk{ "text": "…" }Token / pezzo di testo
tool{ "feedback": "…" }Feedback tool in corso
done{ "text": "…" }Testo concatenato finale
error{ "code", "message" }Errore dopo che lo stream è già partito

Esempio parser:

const res = await fetch(`${nodeUrl}/v1/threads/${threadId}/messages`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessJwt}`,
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({ message: "Ciao" }),
});

// 400 / 401 / 403 / 429 / 500 arrive as JSON before any SSE body.
if (!res.ok) {
const err = await res.json().catch(() => ({}));
fail(err.code ?? String(res.status), err.message);
return;
}

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "";

while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const parts = buf.split("\n\n");
buf = parts.pop() ?? "";
for (const block of parts) {
let event = "message";
let payload = "{}";
for (const line of block.split("\n")) {
if (line.startsWith("event: ")) event = line.slice(7);
if (line.startsWith("data: ")) payload = line.slice(6);
}
const data = JSON.parse(payload);
if (event === "chunk") append(data.text);
if (event === "tool") showTool(data.feedback);
if (event === "done") finish(data.text);
if (event === "error") fail(data.code);
}
}

GET /v1/threads/:threadId/messages

Storico. Serve scope chat:read.

curl "https://langgraphjs-prod.tidiko.ai/v1/threads/$THREAD_ID/messages" \
-H "Authorization: Bearer $ACCESS_JWT"

200:

{
"messages": [
{ "type": "user", "content": "Ciao", "timestamp": 1710000000000 },
{ "type": "bot", "content": "Come posso aiutarti?", "timestamp": 1710000001000 }
]
}

type: user | bot | system. I turn tool-assisted sono fusi in un solo bot (stesso confine della bolla widget). Thread vuoto: { "messages": [] }.

Errori chat

HTTPerrorQuando
400Invalid thread idthreadId non valido
400message is requiredBody senza message stringa
401No token provided / Invalid token / UnauthorizedBearer mancante o JWT invalido
403invalid_keypk_ / sk_ usati come Bearer
403forbiddenAudience/runtime/scope non ok
403agent_mismatchagentId nel body ≠ token
403thread_forbiddenThread di un altro company/agente
429Message limit reachedLimite messaggi piano
404owner_not_foundRuntime Laravel assente
500internal_error / Server misconfigurationErrore interno o LARAVEL_APP_URL mancante sul Node

Dopo che lo SSE è partito, l’errore arriva come evento error, non come JSON HTTP.

Checklist

  1. Chiave sk_ in dashboard per integrazioni server; pk_ solo se il client è un browser con origini CORS.
  2. POST /api/v1/service-token (o /token) → salva token e refresh_token.
  3. POST {socket_url}/v1/threads → tieni threadId.
  4. POST …/messages con { "message": "…" }; per lo stream aggiungi Accept: text/event-stream.
  5. GET …/messages per lo storico.
  6. Refresh prima di expires_in; non riusare un refresh speso.
  7. Mai sk_ o refresh nel browser.

Sicurezza

  • Il JWT access non va loggato per intero.
  • Ogni threadId è autorizzato contro company + agente del token: non condividere thread tra tenant.
  • allowed_origins sulla pk_ è CORS, non auth. Una pk_ leakata si revoca da /apikeys.
  • Il client non può cambiare collection, istruzioni o URL Laravel dal body: il Node le prende dal runtime trusted.