Appearance
Authentication
The primaTime API uses JWT (JSON Web Token) authentication. All authenticated requests must include a valid access token in the Authorization header.
Authentication Flow
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 1. Login/ │────▶│ 2. Request │────▶│ 3. Use API │
│ Register │ │ Access Token │ │ with Token │
└─────────────────┘ └─────────────────┘ └─────────────────┘Step 1: Login or Register
First, authenticate using email and password:
Login
graphql
mutation Login {
loginWithPassword(data: {
email: "user@example.com"
password: "your-password"
rememberMe: true
}) {
success
errors {
value
}
}
}The rememberMe parameter affects session duration:
true— Session lasts 30 daysfalse— Session lasts 1 day
Register
graphql
mutation Register {
registerWithPassword(data: {
email: "user@example.com"
password: "secure-password-123"
agreeWithTerms: true
language: EN
timeZone: EUROPE_PRAGUE
}) {
success
errors {
value
}
}
}Email Verification
After registration, users must verify their email before they can request access tokens. Check for AccountNotVerifiedError in the errors array.
Step 2: Get an Account-Level Access Token
Skip to Step 4 if you know the Tenant ID
If you already have the tenant ID saved from a previous session (e.g., stored in your application), you can skip steps 2 and 3 and go directly to Step 4: Request a Tenant Access Token.
After login, first request an account-level access token (without specifying a tenant). This token allows you to query your available organizations:
graphql
mutation GetAccountToken {
requestAccessToken(data: {}) {
success
accessToken
expiration
errors {
value
}
}
}Account vs Tenant Tokens
- Account-level token (no
tenantId) — Can query user account info and list organizations - Tenant-level token (with
tenantId) — Can access all data within that organization
Step 3: Obtain the Tenant ID
Now, using the account-level token, query which organizations (tenants) the user has access to:
bash
curl -X POST https://api.next.primatime.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ACCOUNT_TOKEN" \
-d '{"query": "{ authenticationContext { accesses(first: 50) { edges { node { organization { id profile { title } } } } } } }"}'Full query:
graphql
query GetMyOrganizations {
authenticationContext {
accesses(first: 50) {
edges {
node {
id
organization {
id # ← This is the Tenant ID
profile {
title
urlPrefix
}
}
}
}
pageInfo {
totalCount
}
}
}
}Response:
json
{
"data": {
"authenticationContext": {
"accesses": {
"edges": [
{
"node": {
"id": "access_abc123",
"organization": {
"id": "org_xyz789",
"profile": {
"title": "Acme Corporation",
"urlPrefix": "acme"
}
}
}
}
],
"pageInfo": {
"totalCount": 1
}
}
}
}
}The organization.id field (e.g., "org_xyz789") is the Tenant ID you'll use in subsequent requests.
New Users
Users who just registered won't have any organizations yet. They need to create one first using createOrganization, or accept an invitation to join an existing organization.
Step 4: Request a Tenant Access Token
Now request an access token for the specific tenant you want to work with:
graphql
mutation RequestToken {
requestAccessToken(data: {
tenantId: "org_xyz789"
}) {
success
accessToken
expiration
errors {
value
}
}
}Step 5: Use the Access Token
Include the token in all subsequent requests:
bash
curl -X POST https://api.next.primatime.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..." \
-H "X-Tenant-ID: org_xyz789" \
-d '{"query": "{ authenticationContext { account { profile { email } } } }"}'Cookies and Session Management
The API uses HTTP-only cookies for secure session management.
The refreshToken Cookie
When you successfully log in, the server sets an HTTP-only cookie containing a refresh token:
| Property | Value |
|---|---|
| Name | refreshToken |
| HttpOnly | Yes |
| Secure | Yes |
| Max-Age | 604,800 seconds (7 days) |
| Path | / |
Browser Clients
When using fetch or other HTTP clients in the browser, you must include credentials to send/receive cookies:
javascript
fetch(API_URL, {
method: 'POST',
credentials: 'include', // ← Required for cookies
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: '...' })
});Cookie Lifecycle
- Login/Register — Server sets the
refreshTokencookie - Request Access Token — Server reads the cookie and issues a new access token + new refresh token cookie
- Logout — Server invalidates the session; client should clear the cookie
Non-Browser Clients
For server-to-server or mobile applications, you need to:
- Extract the
Set-Cookieheader from login/token responses - Store the refresh token value securely
- Send it back in the
Cookieheader for subsequent token requests
Token Lifecycle and Refresh
Token Expiration Times
| Token Type | Validity | Purpose |
|---|---|---|
| Access Token | 12 hours (720 minutes) | Short-lived token for API requests |
| Refresh Token | 7 days | Used to obtain new access tokens |
| Session | 1–30 days | Depends on rememberMe setting |
Refreshing Access Tokens
Access tokens expire after 12 hours. Before expiration, request a new access token using the refresh token cookie:
graphql
mutation RefreshAccessToken {
requestAccessToken(data: {
tenantId: "org_xyz789"
}) {
success
accessToken
expiration
errors {
value
}
}
}What happens during refresh:
- Server validates the refresh token from the cookie
- Server invalidates the old refresh token (one-time use)
- Server issues a new access token
- Server sets a new refresh token cookie
- Response includes the new access token and expiration
Automatic Token Rotation
Each time you call requestAccessToken, the refresh token is rotated. This improves security by ensuring each refresh token can only be used once.
Required Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | Must be application/json |
Authorization | For authenticated endpoints | Bearer <access_token> |
X-Tenant-ID | For tenant operations | The organization/tenant ID |
Public Operations
The following operations do not require authentication:
| Operation | Purpose |
|---|---|
loginWithPassword | Email/password login |
registerWithPassword | New user registration |
requestRecovery | Request password recovery |
recoverPassword | Complete password recovery |
recoveryDetails | Get recovery status |
verifyEmail | Verify email address |
resendEmailVerification | Resend verification email |
inviteDetails | Get invitation details |
requestAccessToken | Get access token (requires valid refresh token cookie) |
All other operations require a valid access token.
Session Management
View and manage active sessions:
graphql
# List all sessions
query MySessions {
sessions(first: 10) {
edges {
node {
id
}
}
}
}
# Invalidate current session (logout)
mutation Logout {
invalidateCurrentSession {
success
}
}
# Invalidate all sessions (logout everywhere)
mutation LogoutAll {
invalidateAllSessions {
success
}
}
# Invalidate specific session
mutation InvalidateSession {
invalidateSession(id: "session_xyz") {
success
}
}Authentication Context
After authentication, retrieve the current user's context:
graphql
query WhoAmI {
authenticationContext {
account {
id
profile {
title
firstName
lastName
email
}
}
user {
id
access
owner
}
organization {
id
profile {
title
}
}
permissions
}
}Error Responses
Authentication failures return errors in the response:
json
{
"data": {
"loginWithPassword": {
"success": false,
"errors": [
{
"value": "Invalid email or password"
}
]
}
}
}Common Authentication Errors
| Error | Cause | Solution |
|---|---|---|
Invalid access token | Token expired or malformed | Refresh the token |
Invalid refresh token | Refresh token expired or reused | Re-authenticate (login) |
Invalid session | Session invalidated or expired | Re-authenticate (login) |
AccountNotVerifiedError | Email not verified | Verify email first |
If a request requires authentication but no valid token is provided:
json
{
"errors": [
{
"message": "Invalid access token",
"extensions": {
"classification": "UNAUTHORIZED"
}
}
]
}