Authentication

InCRM supports two authentication strategies. Choose based on your use case:

Strategy Best for Header format
JWT Bearer User sessions, interactive apps Authorization: Bearer <accessToken>
API Key Scripts, integrations, AI agents X-Api-Key: <key>

JWT Bearer Authentication

Sign In

Obtain an access token by posting credentials:

curl -X POST https://api.incrm.app/api/v1/auth/sign-in \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "yourPassword123"}'

Response:

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

The response also sets a refreshToken as an httpOnly cookie. This cookie is used exclusively for token refresh — it is never returned in the JSON body.

Using the Access Token

Include the token in all subsequent requests:

curl https://api.incrm.app/api/v1/client \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

TypeScript SDK:

import { InCRMClient } from '@incrm-app/sdk'

const client = new InCRMClient({
  baseUrl: 'https://api.incrm.app',
  accessToken: 'eyJhbGciOiJIUzI1NiIs...',
})

Token Expiration

Token Default TTL
Access token 30 minutes
Refresh token 24 hours

Refreshing Tokens

When the access token expires, use the refresh cookie to get a new one:

curl -X POST https://api.incrm.app/api/v1/auth/token/refresh \
  -b "refreshToken=<cookie-value>"

Response:

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

A new refresh token cookie is also set. If the refresh token is missing or invalid, you receive:

{
  "success": false,
  "error": {
    "code": 401012,
    "message": "Refresh token not found in whitelist"
  }
}

Logout

curl -X POST https://api.incrm.app/api/v1/auth/logout \
  -H "Authorization: Bearer <accessToken>"

Returns HTTP 204 No Content. Invalidates both the access token and refresh token.

API Key Authentication

API keys are designed for server-to-server integrations, CI/CD pipelines, and AI agent access.

Creating an API Key

Create a key via the API (requires JWT auth with apiKeys.create permission):

curl -X POST https://api.incrm.app/api/v1/api-key \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Integration", "scopes": ["clients.read", "invoices.read"]}'

Response:

{
  "success": true,
  "data": {
    "id": "abc123",
    "name": "My Integration",
    "key": "incrm_dGhpcyBpcyBhIHRlc3Qga2V5...",
    "keyPrefix": "incrm_dGhpcyB",
    "scopes": ["clients.read", "invoices.read"],
    "expiresAt": null,
    "isActive": true,
    "createdAt": "2025-01-01T00:00:00.000Z"
  }
}

Important: The key field is only returned once at creation time. Store it securely — it cannot be retrieved later.

Using an API Key

Send the key via the X-Api-Key header:

curl https://api.incrm.app/api/v1/client \
  -H "X-Api-Key: incrm_dGhpcyBpcyBhIHRlc3Qga2V5..."

Alternatively, you can pass it as a Bearer token (the incrm_ prefix signals API key auth):

curl https://api.incrm.app/api/v1/client \
  -H "Authorization: Bearer incrm_dGhpcyBpcyBhIHRlc3Qga2V5..."

TypeScript SDK:

import { InCRMClient } from '@incrm-app/sdk'

const client = new InCRMClient({
  baseUrl: 'https://api.incrm.app',
  apiKey: 'incrm_dGhpcyBpcyBhIHRlc3Qga2V5...',
})

Scopes

API keys can be scoped to specific permissions. If scopes is empty or omitted, the key inherits all permissions of the user who created it. When scopes are specified, the key is restricted to only those actions.

Scope format: resource.action (e.g., clients.read, invoices.create).

Key Lifecycle

Auth Error Responses

HTTP Status Error Code Message
401 401001 Invalid credentials
401 401010 Invalid access token
401 401011 Access token not found in whitelist
401 401012 Refresh token not found in whitelist
401 401013 Invalid refresh token
401 401014 Insufficient permissions for this action
401 401015 Business access required
401 401020 API key has expired
401 401021 API key has been revoked
401 401022 Invalid API key
403 403000 Forbidden resource

CORS

When using API keys, CORS is configured with credentials: false and origin: true (any origin). This allows API keys to be used from any domain without credential cookies.

JWT-based auth requires requests from an allowed origin and uses credentials: true for cookie-based refresh tokens.