API Reference

Full reference for the Telegram sign-in API (the Passepartout API). All endpoints are served from https://api.vernesoft.com.

Examples are shown as raw curl requests and as calls through the official SDKs — see Installation to set one up. SDK snippets assume an initialized verne client with a passepartout key.


1. Authentication

Every request must include a Bearer token in the Authorization header. A key is vrn_ followed by random characters; it does not name an API. The Passepartout API needs a key with the passepartout scope (shown as Telegram sign-in in the dashboard), chosen when the key is created. A key without it is refused with 403 Forbidden and the code forbidden (This API key is not authorized for Passepartout.). Keys created before scopes existed and named vrn_passepartout_... still work and open the Passepartout API only. There is no sandbox mode: logins always run against the bot you configured. See API Keys.

Example header:

Authorization: Bearer vrn_9f8a7...

The key authenticates your application. The access token returned by a completed login (prefixed ppt_live_) represents an authenticated end-user — pass that back to your app's session, not the API key. Keys are scoped to a single tenant, and you create and revoke them on the Dashboard → API Keys page.

Bot setup. Before any login works, configure your Telegram bot token (from @BotFather) and enable it on the Dashboard → Telegram sign-in page. Tokens are encrypted at rest with AES-256-GCM.


2. Start Login

POST /v1/passepartout/login/start

Begins a Telegram login attempt. Returns a nonce and a deep_link; send the end-user to the deep_link, then poll Get Login Status with the nonce. Takes no request body.

Example Request

curl -X POST https://api.vernesoft.com/v1/passepartout/login/start \
  -H 'Authorization: Bearer vrn_9f8a7...'

Response

{
  "nonce": "n_9f2c8a1b",
  "deep_link": "https://t.me/your_bot?start=n_9f2c8a1b",
  "expires_at": "2026-07-25T12:05:00Z"
}
Status CodeMeaning
200 OKLogin started; nonce and deep_link returned.
401 UnauthorizedMissing or invalid Bearer token.
403 ForbiddenKey lacks the passepartout scope.
400 Bad RequestNo bot is configured (bot_not_configured) or the bot is switched off (bot_disabled) for this account. Set it up on the Passepartout page.
429 Too Many RequestsRate limit reached — retry after the Retry-After header.

Signing in is never blocked for billing reasons. A suspended account, or one with an unpaid renewal, can still start a login, poll its status and introspect tokens: your users are not locked out of your app because of a payment. The only 402 on the sign-in path is ACTIVE_USER_LIMIT_REACHED for a new person, described under Get Login Status.


3. Get Login Status

GET /v1/passepartout/login/status

Returns the current state of a login attempt. Poll this after sending the user to the deep link. While the user hasn't finished, status is pending; once done it becomes completed and carries the access_token and user.

If the person has been deactivated, the login does not complete: no token is issued, the person is told in Telegram that the account is disabled, and this call answers 403 with the code account_disabled.

Polling is not metered, and it is never refused for billing, so a login that has begun can finish. Poll each login about every 2 seconds; the limit is 3,000 polls a minute per account, and beyond it you get 429 Too Many Requests with a Retry-After header.

Each person who completes a login is an active user for that calendar month, however many times they sign in; that is what your plan limits (see Plans, Limits and Refusals). If your account is over its active-user limit and the month to move to a higher plan has ended, a new person's first login is refused and this call answers 402 with the code ACTIVE_USER_LIMIT_REACHED. People who already have an account sign in as usual.

Query Parameters

ParameterTypeRequiredDescription
noncestringYesThe nonce returned by Start Login.

Example Request

curl 'https://api.vernesoft.com/v1/passepartout/login/status?nonce=n_9f2c8a1b' \
  -H 'Authorization: Bearer vrn_9f8a7...'

Response

{
  "status": "pending"
}
Status CodeMeaning
200 OKStatus returned (pending or completed).
400 Bad RequestThe nonce query parameter is missing (invalid_payload).
401 UnauthorizedMissing or invalid Bearer token.
403 ForbiddenThe person who tried to sign in is deactivated (account_disabled). No token is issued.
402 Payment RequiredACTIVE_USER_LIMIT_REACHED: a new person tried to sign in and your account is past its active-user limit and the month to move up. Nothing was created. Moving to a higher plan lifts it; retrying does not.
404 Not FoundUnknown or expired nonce (unknown_nonce). The answer is the same for a nonce that never existed, one that expired, and one that belongs to another account.
429 Too Many RequestsPolling faster than the account limit — retry after the Retry-After header.

4. Introspect Token

POST /v1/passepartout/tokens/introspect

Validates a Passepartout access token and returns its attributes. A token is reported as { "active": false } when it is unknown or expired, is owned by another tenant, or belongs to a user you have deactivated or deleted.

An active token also lists the user's organizations and roles in orgs: the organizations of the project the API key belongs to, with the role the user holds in each and its permissions, read at the moment you ask (the first 100, in the order the user joined; "orgs_truncated": true appears, and only then, when there are more). A user who belongs to none gets "orgs": [].

Request Body

FieldTypeRequiredDescription
access_tokenstringYesThe ppt_live_ token returned by a completed login.

Example Request

curl -X POST https://api.vernesoft.com/v1/passepartout/tokens/introspect \
  -H 'Authorization: Bearer vrn_9f8a7...' \
  -H 'Content-Type: application/json' \
  -d '{ "access_token": "ppt_live_7bK2mQ9pXr…" }'

Response

{
  "active": true,
  "subject": "9d3f1c22-4a8e-4b17-9c5e-2f0a1b6d8e44",
  "tenant_id": "b1a7…",
  "scopes": ["passepartout"],
  "expires_at": "2026-07-25T13:00:00Z",
  "orgs": [
    { "id": "3b2f6f0e-8f0c-4a54-9f1d-0c7a5d1e2b10", "role": "member", "permissions": ["chat:read"] }
  ]
}
Status CodeMeaning
200 OKThe token is active or not — { "active": false } is an answer, not an error.
400 Bad RequestThe body is not JSON, or has no access_token (invalid_payload).
401 UnauthorizedMissing or invalid Bearer token.
502 Bad GatewayThe state of the user could not be checked (upstream_unavailable). Retry; the token is never reported active when this happens.

5. Deactivated Users

You can switch an end-user off without deleting them: PATCH /v1/gate/identities/{id}/state with { "state": "inactive" }, using a key with the gate scope of the same tenant and the identity_id that the completed login (and the token's subject) carries. See Identity State Management. While a user is inactive:

  • They cannot sign in. A login they start does not complete: Get Login Status answers 403 with account_disabled and no token is issued, and the bot tells the person the account is disabled.
  • Their existing tokens are inactive. Introspect Token answers { "active": false } for every token issued to them. The tokens are suspended, not revoked: set the user back to active and the same tokens are active again until they expire, and the person can sign in again as the same user.

Introspection checks the user's state each time it is called, so a deactivation takes effect on the next call.


Error Format

All errors, from every Passepartout route, follow one structure: the same envelope as the rest of the API. The SDKs surface these fields as a typed error:

{
  "error": {
    "code": "bot_not_configured",
    "message": "No Telegram bot is configured or enabled for this tenant.",
    "request_id": "req_abc123"
  }
}
Member
codeA string that names the kind of error. Branch on this, together with the HTTP status. The set is open: new codes are added, so keep a default arm. It is never a number.
messageA sentence for a person. Its wording can change.
request_idPresent on every error, whichever route produced it. Include it when contacting support.
StatuscodeMeaning
400invalid_payloadA malformed request: a missing nonce, a body that is not JSON, a body without access_token.
400bot_not_configured, bot_disabledThe account has no Telegram bot yet, or its bot is switched off.
401unauthorizedMissing or invalid API key.
402ACTIVE_USER_LIMIT_REACHEDA new person past the active-user limit. Does not lift by waiting.
403forbiddenThe key lacks the passepartout scope.
403account_disabledThe person is deactivated.
404unknown_nonce, not_foundThe login does not exist or has expired; no such endpoint.
429rate_limitedRetry after the Retry-After header.
500internal_errorContact support with the request_id.
502, 503, 504upstream_unavailableThe service could not reach what it needs to answer. Retry.

Before October 2026 a malformed request to the Passepartout API was invalid_request with no request_id, and a missing nonce or a body that was not JSON was a plain-text 400. They are invalid_payload in the envelope now; code that matched invalid_request should match invalid_payload. The statuses did not change.