Skip to content
Developers

The commercial API

The Tephlo API lets your application talk to your workspace: start conversations answered by your assistant, look up orders with customer verification, capture support tickets, manage knowledge and guidance, and receive signed webhooks.

Availability

The API is rolling out gradually Rolling out — sandbox keys are available from your console’s Developers page once the platform enables the API; production access is granted per workspace.

Everything on this page works against sandbox keys and synthetic sandbox data, so you can build and test an integration before your workspace is approved for production traffic. What separates the two environments is covered at the end.

Authentication

Every request carries an API key in the Authorization header as a bearer token. Sandbox keys start with tphlo_sk_test_, production keys with tphlo_sk_live_:

Authorization: Bearer tphlo_sk_test_...

Keys are created in the tenant console on the Developers page. Each key carries scopes, and a request outside a key’s scopes is refused — so a key that only needs to read conversations should be created with only that, not with everything.

Secret keys belong on your server, never in a browser or a mobile app. Anything shipped to a client can be extracted from it. A key is shown exactly once, at the moment it is created — store it in your secret manager then, because the console cannot show it again. If a key leaks, or you simply want to be careful, rotate it from the console: a replacement is issued and the old key stops working.

Quickstart: five calls

The examples below use a placeholder base URL — https://api.example-tephlo.app — replace it with your backend origin, shown on the console’s Developers page. Export both values once and every example becomes copy-paste-run:

BASE="https://api.example-tephlo.app"   # replace with your backend origin
KEY="tphlo_sk_test_..."                 # from Console -> Developers

1. Check the key

GET /v1/me returns the workspace, environment and scopes the key resolves to — the fastest way to confirm authentication works before anything else.

curl "$BASE/v1/me" \
  -H "Authorization: Bearer $KEY"

2. Start a conversation

POST /v1/conversations opens a conversation for a customer you identify by your own reference. It is a consequential POST, so it takes an Idempotency-Key.

curl "$BASE/v1/conversations" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-conv-1" \
  -d '{"customer_ref":"demo-1"}'

The response includes the conversation’s id; the next two calls use it.

3. Send a customer message

curl "$BASE/v1/conversations/{id}/messages" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-msg-1" \
  -d '{"text":"What'\''s your return policy?"}'

The call returns as soon as the message is accepted — the assistant’s reply is generated asynchronously, exactly as it is for a customer writing on WhatsApp. Poll the message list (step 4) until the reply appears, or register a webhook and be told the moment it exists.

4. Read the conversation

curl "$BASE/v1/conversations/{id}/messages" \
  -H "Authorization: Bearer $KEY"

5. Capture a support ticket

POST /v1/tickets records a callback request, complaint or refund request against a customer — the same write action the assistant itself uses.

curl "$BASE/v1/tickets" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-ticket-1" \
  -d '{"kind":"callback","customer_ref":"demo-1","summary":"Please call me"}'

Idempotency

Networks fail mid-request, and retrying a POST that already succeeded must not create a second conversation or a second ticket. Every consequential POST therefore requires an Idempotency-Key header — any unique string you choose, one per logical operation.

  • Retrying with the same key and the same body replays the stored response — nothing happens twice.
  • Sending the same key with a different body is refused with 409: the key names an operation, and one key cannot name two.
  • Keys expire after 24 hours; after that the same key starts a fresh operation.

Errors

Every error is the same envelope, so one error handler covers the whole API:

{"error":{"code":"rate_limited","message":"Too many requests. Retry after the indicated delay."}}

Every response — success or failure — also carries an x-request-id header. Include it when you contact support: it lets the request be found in the logs without guessing.

Common error codes, their HTTP statuses and what to do about each
CodeStatusMeaning
unauthorized401Missing, malformed, revoked or expired key.
forbidden403The key is valid but lacks the scope for this endpoint.
capability_unavailable409The workspace cannot perform this action right now — for example a ticket kind the workspace has not enabled.
policy_unavailable503The platform could not load the policy needed to answer safely. Retry with backoff.
rate_limited429Too many requests. Honour the retry delay before trying again.
sandbox_quota_exhausted429The sandbox’s daily allowance is used up; it resets automatically.
idempotency_key_conflict409The Idempotency-Key was already used with a different body — see idempotency.

Webhooks

Webhooks are managed from the console’s Developers page — endpoints, signing secrets, a test ping and the delivery log for each endpoint — or through the API with a key that carries the webhooks:manage scope. Registering an endpoint and the events it should receive looks like this:

curl "$BASE/v1/webhook-endpoints" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-hook-1" \
  -d '{"url":"https://example.com/tephlo-webhook","events":["tephlo.v1.conversation.reply"]}'

Endpoints must be public HTTPS on port 443, and redirects are not followed — the URL you register is exactly where deliveries go.

Verifying a delivery

Every delivery is signed. The Tphlo-Signature header carries a timestamp and an HMAC-SHA256, computed with your endpoint’s signing secret over the string {t}.{body}:

Tphlo-Signature: t=<unix>,v1=<hex hmac-sha256 of "{t}.{body}">

Verify it before trusting the payload: recompute the HMAC over the raw request body, compare in constant time, and reject anything where |now − t| > 300 seconds — the timestamp is what stops a captured delivery being replayed later.

const crypto = require("node:crypto");

function verifyTphloSignature(header, rawBody, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(t + "." + rawBody)
    .digest("hex");
  const given = Buffer.from(parts.v1 || "", "hex");
  const want = Buffer.from(expected, "hex");
  return given.length === want.length && crypto.timingSafeEqual(given, want);
}
Deliveries are retried, so duplicates are normal. Each delivery carries a Tphlo-Event-Id header that stays the same across retries of the same event — record the ids you have processed and skip any you have seen before.

Sandbox vs production

The two environments are separated by the key, not by different URLs: a tphlo_sk_test_ key works in the sandbox, a tphlo_sk_live_ key in production. What a sandbox key can and cannot reach is precise:

SandboxConversations, tickets and webhook endpoints created with a sandbox key are their own set — a production key never sees them and they never see production’s. Sandbox conversations expire automatically after 14 days, so treat them as disposable and script their creation. A sandbox key can read the workspace’s real catalog and knowledge, so your integration sees real shapes, but it cannot change production data: writes to knowledge and guidance are refused with 403. Order-status lookups run against the workspace’s real order system and still require the customer’s verifier.
ProductionReal conversations, real tickets, real webhooks, and writes to knowledge and guidance. Production access is granted per workspace and requires operator approval — request it from the console’s Developers page (the page shows when the request was made), and build against the sandbox while you wait. A production key cannot be created until access is granted.