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
keyfield 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
- Keys can have an optional
expiresAtdate. Expired keys are rejected with error401020. - Keys can be revoked (deactivated) via
DELETE /api/v1/api-key. Revoked keys are rejected with error401021.
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.