POST /v1/auth/email/verify
https://api.triport.io/v1/auth/email/verifyCompletes the email one-time-passcode (OTP) login flow: verifies the code, opens an authenticated console session, and returns the signed-in user.
This is the second step of the email login flow. The caller first requests a
code with POST /v1/auth/email/start, which returns a
request_id and emails a one-time passcode to the address. The user then
submits that request_id together with the code they received to this
endpoint.
On success the endpoint:
- verifies the code against the pending request and consumes the OTP
(it is single-use — a second verify with the same
request_idreturnsinvalid_request); - creates or updates the user record and the email identity;
- issues a new session and sets the
nl_sessionandnl_csrfcookies on the response; - returns the authenticated
userobject.
After a successful verify the platform also runs several best-effort background steps that never block or fail the login: free-subscription provisioning, generation of the user's primary referral code, and enqueueing of the welcome email. If a referral-attribution cookie is present on the request it is consumed and attributed to the new user.
Because this is a browser session flow, no API key or Bearer token is involved.
Send the request with credentials enabled so the Set-Cookie headers are
stored; subsequent authenticated calls (such as
GET /v1/auth/me) read the nl_session cookie, and mutating
requests must echo the nl_csrf cookie value in the X-CSRF-Token header.
Parameters
Request body (JSON):
request_idstring (UUID)requiredPOST /v1/auth/email/start. Must be a valid UUID for an unexpired, not-yet-consumed request.codestringrequiredResponse
200 OK
The response also carries two Set-Cookie headers:
nl_session— HttpOnly session cookie (sent automatically by the browser on later requests).nl_csrf— readable double-submit token; the frontend reads it and sends it back asX-CSRF-Tokenon mutating requests.
user.idstring (UUID)user.emailstringuser.display_namestringuser.avatar_urlstringuser.billing_customer_idstringuser.created_atstring (RFC 3339)user.updated_atstring (RFC 3339)Errors
| Code | HTTP status | Meaning | When it happens |
|---|---|---|---|
invalid_code | 400 | The passcode is wrong or malformed. | The submitted code does not match the pending request, or fails basic format validation. |
invalid_request | 400 | The request could not be matched. | request_id is not a valid UUID, is unknown, expired, or was already consumed. |
invalid_json | 400 | Body could not be parsed. | The request body is not valid JSON. |
session_issue_failed | 500 | The code verified but the session could not be created. | An internal failure while issuing the session after a valid code. |
All error responses use the shared error envelope { "error": "<code>" }.
See errors.md for the full envelope and handling guidance.
Examples
JavaScript (fetch)
const res = await fetch('https://api.triport.io/v1/auth/email/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include', // store nl_session / nl_csrf cookies
body: JSON.stringify({
request_id: requestId, // from /v1/auth/email/start
code: '123456',
}),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(error); // 'invalid_code' | 'invalid_request' | ...
}
const { user } = await res.json();
console.log('signed in as', user.email);TypeScript SDK (@triport/sdk)
import { TriportClient } from '@triport/sdk';
const client = new TriportClient(); // cookie-session based; no API key
const { requestId } = await client.auth.email.start({ email: '[email protected]' });
// ...collect the code from the user...
const { user } = await client.auth.email.verify({
requestId,
code: '123456',
});
// session cookies are now set on the clientPython (triport-sdk)
from triport import TriportClient
client = TriportClient() # cookie-session based; no API key
start = client.auth.email.start(email="[email protected]")
# ...collect the code from the user...
result = client.auth.email.verify(
request_id=start.request_id,
code="123456",
)
print("signed in as", result.user.email)Notes
- Single-use codes. Verifying consumes the OTP. A repeated verify with the
same
request_idreturnsinvalid_request; start a new flow withPOST /v1/auth/email/startto get a fresh code. - Cookies, not tokens. No raw session token is exposed to JavaScript — the
nl_sessioncookie is HttpOnly. Always send requests with credentials included so the browser stores and replays it. - CSRF on later calls. This endpoint itself takes no
X-CSRF-Token, but thenl_csrfcookie it sets must be echoed as theX-CSRF-Tokenheader on subsequent mutating requests. - Related:
POST /v1/auth/email/start,GET /v1/auth/me,POST /v1/auth/logout.