Skip to main content

Authentication

These endpoints handle user login and token refresh. They do not require an access token.

POST /v1/auth/login

Authenticate a user via an OAuth provider (Apple or Google). If the user doesn't exist yet, an account is created automatically.

Request Body

{
"provider": "google",
"token": "<oauth_authorization_code_or_id_token>",
"name": "John"
}
FieldTypeRequiredDescription
providerstringYes"google" or "apple"
tokenstringYesOAuth token from the provider
namestringApple onlyUser's display name (Apple only sends this on first sign-in)

Provider Details

Google: Send the ID token from Google Sign-In. The backend validates it against the appropriate client ID based on the Client-Info os field (iOS vs web).

Apple: Send the authorization code from Sign in with Apple. The backend exchanges it with Apple's servers. Include name on the first login — Apple only provides it once.

Response

{
"status": 200,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expire_at": "1735689600000",
"refresh_token": "dGhpcyBpcyBhIHJlZnJl...",
"user_id": "1234567890123456789"
}
}
FieldTypeDescription
access_tokenstringJWT for authenticating subsequent requests
expire_atstringToken expiry as Unix timestamp in milliseconds
refresh_tokenstringOpaque token for refreshing the session
user_idstringSnowflake ID of the authenticated user

Error Responses

StatusErrorCause
400VALIDATION_FAILEDMissing token or provider, or missing Client-Device-Id header
401UNAUTHORIZEDOAuth token is invalid or expired

Session Policy

Only one active session is allowed per user. Logging in from a new device invalidates all previous refresh tokens.


POST /v1/auth/refresh

Exchange a valid refresh token for a new access token + refresh token pair. The old refresh token is rotated (invalidated and replaced).

Request Body

{
"refresh_token": "dGhpcyBpcyBhIHJlZnJl..."
}
FieldTypeRequiredDescription
refresh_tokenstringYesThe refresh token from login or a previous refresh

Response

{
"status": 200,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"expire_at": "1735689600000",
"refresh_token": "bmV3IHJlZnJlc2ggdG9r...",
"user_id": "1234567890123456789"
}
}

Same shape as the login response. The refresh_token is a new token — the old one is no longer valid.

Error Responses

StatusErrorCause
400VALIDATION_FAILEDMissing refresh_token
401UNAUTHORIZEDToken is invalid, expired, or the Client-Device-Id doesn't match the session

Device Binding

The refresh token is bound to the Client-Device-Id header sent during login. If the device ID on the refresh request doesn't match, the request is rejected with 401.


Token Lifecycle

1. User signs in with Google/Apple
2. POST /v1/auth/login → access_token + refresh_token
3. Use access_token for all authenticated requests
4. When access_token expires (check expire_at):
POST /v1/auth/refresh → new access_token + new refresh_token
5. Repeat from step 3

Token Expiry

TokenLifetime
Access tokenCheck the expire_at field in the response
Refresh token3 months