Authentication

Telenow accepts three credentials. Which one you use is decided by where the code runs, not by preference:

CredentialHeaderRuns onIdentifies
API keyX-API-Key: vai_live_…Your serverAn organization + a role
User JWTAuthorization: Bearer eyJ… (+ X-Org-Id)Dashboard, your own admin UIA signed‑in user
Client tokenAuthorization: Bearer …A browser or mobile appOne pre‑authorized call

Never put an API key in a browser, mobile app, or anything a user can view source on. A key is a bearer credential for your whole organization. For client‑side voice, mint a client token on your server and send that instead — it expires in minutes and can only start the one call you scoped it to.

Where each credential works

This table is the authoritative answer. If a code sample anywhere in these docs disagrees with it, the table is right — please tell us.

SurfaceAPI keyUser JWTNotes
/api/v1/* — Integration API✅❌Key only, by design. Flat JSON, slim projections.
/api/v1/integrations/* — Integration connectors✅❌Connect a store, CRM, database or WhatsApp number by key. Secrets are write‑only: accepted here, returned only as a mask.
/api/v1/sub-accounts/* — Sub-accounts✅❌owner/admin keys only. Creating one returns the sub-account's own key.
/api/agents/*✅✅Copilot + simulation sub‑routes are dashboard‑only (JWT).
/api/orgs/{orgId}/ — calls, recordings, call-audio, call-exports, webhooks, knowledge-bases, inbound-routing, campaigns, analytics, voices, dial, dnc, consent, analysis, memory, templates, sub-accounts✅✅A key is pinned to its own org; another org's {orgId} returns 403. sub-accounts needs an owner/admin key — see Sub-accounts. Over a key, a voice cannot be released as "self" — that attests it is a person's own voice and needs them signed in.
/api/orgs/{orgId}/ — whatsapp-hub, whatsapp-campaigns, whatsapp-cloud/channels/{channelId}/…✅✅Send, inbox, broadcasts, templates — see WhatsApp with your org API key. Pinned to its own org like the row above. Connecting a number stays dashboard‑only (next row).
/api/orgs/{orgId}/ — everything else (ambient-tracks, usage, billing, audit, apps, carriers, trunks, mcp, whatsapp (WhatsApp Web sessions), whatsapp-cloud onboarding, integrations, …)❌✅Dashboard‑only so far. Tell us if you need one of these programmatically. Connectors are the exception — manage them by key at /api/v1/integrations/* above. This dashboard route stays JWT‑only because it also carries the action bench, which fires real vendor requests.
/api/orgs/{orgId} itself — org profile, members❌✅
/api/sessions/ — initiate-call, init-web-call, {id}/transfer, {id}/play, {id}/say, DELETE {id}✅✅Start, transfer, play into, speak into and end calls server‑to‑server.
/api/sessions/{id}, {id}/messages, {id}/tool-invocations (reads)❌✅Read transcripts with GET /api/v1/calls/{id} instead.
/api/client-tokens✅✅Mints browser tokens. Never call this from a browser.
/api/catalog, /api/providers/*, /api/voice/numbers/*✅✅
/api/auth/*❌✅Login, password, MFA — a session concern, not a machine one.
/api/orgs/{orgId}/api-keys❌✅A key cannot mint or revoke keys. Prevents a leaked key minting itself a replacement that survives revocation.
/api/app-* — App Platform❌❌Separate scheme: app keys. See App scopes.

Read vs. write with a key

On the routes above that were historically dashboard‑only, a key's role decides whether it may mutate:

  • owner, admin, developer — full read + write.
  • member, viewer — read‑only. Any POST/PUT/PATCH/DELETE returns 403 with "This API key's role ('viewer') is read-only on this endpoint."

This does not apply to POST /api/v1/chat or POST /api/sessions/initiate-call, which any valid key may call — they're the normal way low‑privilege integrations do their job.

The same bar applies on /api/v1 itself, worded slightly differently: "This API key's role (viewer) is read-only; owner, admin or developer is required for write operations". On connectors that covers testing as well as writing — a verification spends a real request against the vendor using your stored credential, which a read‑only key is not entitled to spend.

An API key is scoped to one organization and carries a role. Create one in the dashboard, or via the API.

Create a key in the dashboard

  1. In the sidebar, go to Developers → API keys.
  2. Click New key (top right). You need the owner, admin, or developer role — viewers can see keys but can't mint them.
  3. Give the key a Name (e.g. Production backend) and click Create.
  4. The full secret is shown in a Save your API key dialog. Store it somewhere secure: it won't be shown on screen again. After that, an owner or admin can use the Copy button on the key's row, which puts it on the clipboard without showing it. A key made before that button existed has no copy to take, so if its secret is lost, create a new key.
  5. Click I've saved it. The key now appears in the table with its last‑four digits, creation date, last‑used time, and status.

To revoke a key, click the trash icon on its row (owner/admin only). Revocation is immediate — the next request with that key fails with 401.

Using a key

Send it in the X-API-Key header. Use the full secret (not a Bearer prefix). Because the key already identifies the organization, you do not send an X-Org-Id header:

curl https://api.telenow.ai/api/agents \
  -H "X-API-Key: vai_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

The same header works on both surfaces:

# Integration API (X-API-Key only, flat JSON)
curl https://api.telenow.ai/api/v1/me \
  -H "X-API-Key: vai_live_…"

Key facts:

  • Secrets are prefixed vai_live_ followed by a random token. The full secret is shown on screen once, at creation. Requests are checked against a SHA‑256 hash. Telenow also keeps an encrypted copy so an owner or admin can copy the key again; each copy is recorded in the audit log, and revoking a key deletes its copy.
  • Keys can be revoked at any time, immediately invalidating them.
  • A key's role (owner / admin / developer / viewer) governs what it can do (see Roles). New keys default to developer.
  • If the user who created a key is later deactivated or deleted, the key stops working (401 — "API key creator is no longer active").
  • The key's last‑used timestamp updates automatically on every successful request.

Keep keys server‑side. Never embed an API key in a browser, mobile app, or public widget. For browser experiences use the public widget or mint short‑lived sessions from your backend (see Programmatic web calls).

Key roles

A key inherits a role at creation; that role is checked on every request.

RoleTypical useCan create keys?Can revoke keys?Can manage webhooks/hooks?
ownerWorkspace ownerYesYesYes
adminAdministratorYesYesYes
developerBackend/CI service accountYesNoYes
viewerRead‑only / reportingNoNoNo (read‑only)

On the Integration API, hook management (POST/DELETE /api/v1/hooks) requires owner, admin, or developer; viewer keys are read‑only there. See Organizations, team & API keys for the full role model and Webhooks.

Managing keys via API

These endpoints are part of the Dashboard API and require a user JWT (an API key cannot mint or revoke keys for its own org through this route):

GET    /api/orgs/{orgId}/api-keys          # list
POST   /api/orgs/{orgId}/api-keys          # create -> returns the secret
POST   /api/orgs/{orgId}/api-keys/{keyId}/reveal  # copy -> returns the secret (owner / admin, audited)
DELETE /api/orgs/{orgId}/api-keys/{keyId}  # revoke

Create request:

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/api-keys \
  -H "Authorization: Bearer eyJ…" \
  -H "Content-Type: application/json" \
  -d '{ "name": "CI integration", "role": "developer" }'

role is optional and defaults to developer. Create response:

{
  "success": true,
  "data": {
    "key": {
      "id": "…", "org_id": "…", "name": "CI integration", "role": "developer",
      "last_four": "abcd", "created_by": "…", "last_used_at": null,
      "revoked_at": null, "created_at": "…"
    },
    "secret": "vai_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
}

The list endpoint returns { "success": true, "data": { "apiKeys": [ … ], "total": N } }; each row shows last_four rather than the secret, and copy_available says whether there is a copy to take. See Organizations, team & API keys.

User JWT (bearer token)

A JWT represents a signed‑in user. Obtain one by logging in:

curl -X POST https://api.telenow.ai/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "password": "••••••••" }'

Response:

{
  "success": true,
  "data": {
    "user": { "id": "…", "email": "[email protected]", "role": "owner" },
    "tokens": { "accessToken": "eyJ…", "refreshToken": "eyJ…" }
  }
}

Send the access token on subsequent requests, plus the organization id for org‑scoped routes:

curl https://api.telenow.ai/api/orgs/{orgId}/calls \
  -H "Authorization: Bearer eyJ…" \
  -H "X-Org-Id: {orgId}"
  • Access tokens expire; use POST /api/auth/refresh with the refresh token ({ "refreshToken": "eyJ…" }) to get a fresh pair.
  • An expired token returns 401 with code: "token_expired"; an otherwise invalid token returns code: "token_invalid". Use this to decide whether to refresh or re‑login.
  • POST /api/auth/logout revokes the current access token.

Two‑step sign‑in (/api/auth/lookup)

The web console signs in mobile‑first: it asks for the number, then decides what to ask for second. POST /api/auth/lookup is that decision, and it verifies no credential.

curl -X POST https://api.telenow.ai/api/auth/lookup \
  -H "Content-Type: application/json" \
  -d '{ "identifier": "+919876543210" }'

identifier also accepts an email, and the field may be sent as phone, mobile or email — they are aliases of the same field.

{
  "success": true,
  "data": {
    "status": "password",
    "identifier": "+919876543210",
    "channel": "phone",
    "selfSignupEnabled": true
  }
}
statusMeaning
passwordAn account exists — ask for the password, then POST /api/auth/login
signupNobody holds this identifier — start signup with it prefilled
unavailableHeld by an account that cannot sign in here (deactivated, or a partner‑plane staff identity)

Sign in and sign up with the identifier this returns, not the raw input: it is normalized server‑side (+<digits> for a mobile, lower‑cased for an email) and is the exact string the account is keyed on.

Throttled per address. This endpoint deliberately reveals whether an identifier is registered — that is what makes the two‑step page possible — but nothing beyond that, and password attempts remain rate‑limited per account independently.

Sign in with a one‑time code (/api/auth/login-otp)

For the returning user who doesn't remember their password. Unlike a reset, this issues a session rather than forcing a new password to be invented and stored.

curl -X POST https://api.telenow.ai/api/auth/login-otp \
  -H "Content-Type: application/json" \
  -d '{ "identifier": "+919876543210" }'
{
  "success": true,
  "data": {
    "requestId": "6d25aecb-…",
    "channel": "phone",
    "destinationHint": "+9198••••••10",
    "expiresInMinutes": 10
  }
}

The code goes to the identifier you submitted — never to the account's other channel. Redeem it:

curl -X POST https://api.telenow.ai/api/auth/login-otp/verify \
  -H "Content-Type: application/json" \
  -d '{ "requestId": "6d25aecb-…", "code": "482913" }'

Answers data.status: "login" with the same { user, tokens } envelope password login returns — or data.status: "mfa_required" for an MFA‑enabled account, in which case re‑post the same code with totpCode. A code emailed to an inbox is exactly the factor TOTP exists to backstop, so this path does not skip it; the challenge does not consume the code.

POST /api/auth/login-otp/resend mints a fresh code for the same requestId after a 60‑second cooldown. It does not reset the wrong‑guess counter, so resending cannot refill the guess budget.

Codes live 10 minutes, are single‑use, are stored only as a SHA‑256 hash, and die after 5 wrong guesses. Sends are capped at 5 per destination per hour. The endpoint answers identically — and in the same time — whether or not the identifier has an account.

The X-Org-Id header

Organization‑scoped endpoints live under /api/orgs/{orgId}/… (plus a few others like POST /api/sessions/initiate-call).

With a user JWT, send X-Org-Id: {orgId}. Telenow checks you're a member of that org before the handler runs, and the handler independently re‑checks your membership of the {orgId} in the path — so a mismatched header and path can't widen your access beyond orgs you already belong to. In practice: always set the header to the same org as the path.

With an API key, the org is implicit — it comes from the key, and X-Org-Id is ignored. The key is additionally pinned to its own org: if the {orgId} in the path isn't the key's org, the request fails with 403 — "This API key is scoped to a different organization." This holds even when the person who created the key belongs to several orgs, so a key can never be pointed at an org it wasn't minted for.

JWTs are never accepted on /api/v1/*.

Client tokens (for browsers and mobile apps)

A client token is an ephemeral, single‑call credential your backend mints with its API key and hands to an untrusted client:

your server  --X-API-Key-->  POST /api/client-tokens        -> { token, expiresAt }
   |
   +-- token -->  browser  --Bearer-->  POST /api/sessions/init-web-call  -> { sessionId, websocketUrl }

Everything that costs money or grants access — which agent, which context variables, who the caller is — is baked in at mint time and re‑read from the token at redemption. The browser cannot change any of it, even by editing the request. Tokens default to a 10‑minute lifetime and a single call.

Full reference: Client tokens. Ready‑made helpers: telenow.clientTokens.create() in the server SDKs.

All auth & account endpoints

MethodPathAuthPurpose
POST/api/public/signup/start—Start a sign‑up: sends a code to the email, and to WhatsApp if a mobile is given → signupId
POST/api/public/signup/verify—Redeem the code(s) → the new account's { user, tokens }
POST/api/auth/lookup—Does this mobile/email have an account? → password | signup | unavailable
POST/api/auth/login—Email or mobile + password login → { user, tokens }
POST/api/auth/login-otp—Send a one‑time sign‑in code to an email or mobile
POST/api/auth/login-otp/verify—Redeem the code → { user, tokens } (or mfa_required)
POST/api/auth/login-otp/resend—Re‑send the code (60s cooldown)
POST/api/auth/refresh—Exchange a refresh token for new tokens
POST/api/auth/forgot-password—Request a password‑reset email
POST/api/auth/reset-password—Complete reset with the emailed token
POST/api/auth/oidc/google/callback—Sign in with Google (OIDC)
POST/api/auth/mfa/challenge—Complete an MFA‑gated login (email + password + TOTP code)
GET/api/auth/meJWTCurrent user profile
POST/api/auth/change-passwordJWTChange password
POST/api/auth/logoutJWTRevoke the current access token
POST/api/auth/mfa/setupJWTBegin MFA enrolment (returns a QR/secret)
POST/api/auth/mfa/verifyJWTConfirm MFA enrolment with a TOTP code
POST/api/auth/mfa/disableJWTDisable MFA (requires current password)
POST/api/auth/deactivateJWTDeactivate your own account

MFA login flow

  1. POST /api/auth/login with email + password.
  2. If the account has MFA enabled and you didn't send a code, the response is 401 with error: "mfa_required" (no tokens are issued).
  3. Complete the login one of two ways — both return the same { user, tokens } payload:
    • Retry the same call: POST /api/auth/login with email, password, and totpCode (the 6‑digit code), or
    • Use the challenge endpoint: POST /api/auth/mfa/challenge with { "email": "…", "password": "…", "code": "123456" }.

Most integrations should use API keys and skip the user‑login flow entirely.

Which should I use?

Use caseAuth
Automation platform (Zapier/Make/n8n) — /api/v1API key (required)
Backend integration, CI, scriptsAPI key
Managing agents programmaticallyAPI key (owner/admin/developer role)
Acting as a specific signed‑in user / building a custom dashboardUser JWT
Voice call from your own web or mobile appClient token, minted server‑side
Anonymous public page, no backendNeither — use the public widget
Minting or revoking API keysUser JWT (keys can't manage keys)

Tips

  • The X-API-Key header is case‑insensitive (HTTP header names always are), but use the full secret value — don't truncate to the last four digits shown in the dashboard.
  • Rotate keys by creating a new one, deploying it, then revoking the old one — there's no in‑place rotation.
  • Give each integration its own key so you can revoke one without breaking the others, and so the audit log attributes actions to the right key.