Authentication

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 APIKey only, by design. Flat JSON, slim projections.
/api/v1/integrations/*Integration connectorsConnect a store, CRM, database or WhatsApp number by key. Secrets are write‑only: accepted here, returned only as a mask.
/api/agents/*Copilot + simulation sub‑routes are dashboard‑only (JWT).
/api/orgs/{orgId}/calls, recordings, call-audio, webhooks, knowledge-bases, ambient-tracks onlyA key is pinned to its own org; another org's {orgId} returns 403.
/api/orgs/{orgId}/ — everything else (analytics, usage, billing, audit, dnc, campaigns, memory, apps, templates, carriers, trunks, mcp, whatsapp*, 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, DELETE {id}Start, transfer 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-tokensMints 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-keysA key cannot mint or revoke keys. Prevents a leaked key minting itself a replacement that survives revocation.
/api/app-*App PlatformSeparate 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, viewerread‑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 once in a Save your API key dialog. Copy it now and store it somewhere secure — Telenow keeps only a hash and will never show it again.
  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 once at creation — store it securely; Telenow keeps only a SHA‑256 hash.
  • 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 secret once
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 (secret returned exactly once):

{
  "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. 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/auth/registerCreate a user account
POST/api/auth/lookupDoes this mobile/email have an account? → password | signup | unavailable
POST/api/auth/loginEmail or mobile + password login → { user, tokens }
POST/api/auth/login-otpSend a one‑time sign‑in code to an email or mobile
POST/api/auth/login-otp/verifyRedeem the code → { user, tokens } (or mfa_required)
POST/api/auth/login-otp/resendRe‑send the code (60s cooldown)
POST/api/auth/refreshExchange a refresh token for new tokens
POST/api/auth/forgot-passwordRequest a password‑reset email
POST/api/auth/reset-passwordComplete reset with the emailed token
POST/api/auth/oidc/google/callbackSign in with Google (OIDC)
POST/api/auth/mfa/challengeComplete 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.