POST /v1/auth/email/verify
Completes the email one-time-passcode (OTP) login flow: verifies the code, opens an authenticated console session, and returns the signed-in user.
https://triport.io/v1/auth/email/verify- Auth
- None required. On success the response sets the nl_session (HttpOnly) and nl_csrf (double-submit) session cookies.
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://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);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.