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 Code | Meaning |
|---|---|
200 OK | Login started; nonce and deep_link returned. |
401 Unauthorized | Missing or invalid Bearer token. |
403 Forbidden | Key lacks the passepartout scope. |
400 Bad Request | No 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 Requests | Rate 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
| Parameter | Type | Required | Description |
|---|---|---|---|
nonce | string | Yes | The 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 Code | Meaning |
|---|---|
200 OK | Status returned (pending or completed). |
400 Bad Request | The nonce query parameter is missing (invalid_payload). |
401 Unauthorized | Missing or invalid Bearer token. |
403 Forbidden | The person who tried to sign in is deactivated (account_disabled). No token is issued. |
402 Payment Required | ACTIVE_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 Found | Unknown 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 Requests | Polling 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
| Field | Type | Required | Description |
|---|---|---|---|
access_token | string | Yes | The 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 Code | Meaning |
|---|---|
200 OK | The token is active or not — { "active": false } is an answer, not an error. |
400 Bad Request | The body is not JSON, or has no access_token (invalid_payload). |
401 Unauthorized | Missing or invalid Bearer token. |
502 Bad Gateway | The 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
403withaccount_disabledand 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 toactiveand 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 | |
|---|---|
code | A 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. |
message | A sentence for a person. Its wording can change. |
request_id | Present on every error, whichever route produced it. Include it when contacting support. |
| Status | code | Meaning |
|---|---|---|
400 | invalid_payload | A malformed request: a missing nonce, a body that is not JSON, a body without access_token. |
400 | bot_not_configured, bot_disabled | The account has no Telegram bot yet, or its bot is switched off. |
401 | unauthorized | Missing or invalid API key. |
402 | ACTIVE_USER_LIMIT_REACHED | A new person past the active-user limit. Does not lift by waiting. |
403 | forbidden | The key lacks the passepartout scope. |
403 | account_disabled | The person is deactivated. |
404 | unknown_nonce, not_found | The login does not exist or has expired; no such endpoint. |
429 | rate_limited | Retry after the Retry-After header. |
500 | internal_error | Contact support with the request_id. |
502, 503, 504 | upstream_unavailable | The 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.