Every call to the integration API needs Authorization: Bearer <token>. This
applies to every module - there is nothing transport-specific here.
Logging in
Log in once with POST /v1/Authentication/Login:
POST /v1/Authentication/Login
{
"username": "integration@example.com",
"password": "<secret>",
"systemTypeId": 1,
"identifier": "FABRIC"
}
The response carries both tokens:
{
"result": {
"success": true,
"token": {
"access_token": "eyJhbGciOi...",
"refresh_token": "k7Qp2f...",
"expires": 900,
"valid_until": "2026-08-27T09:15:00+00:00"
}
}
}
Access tokens now expire after about 15 minutes. A long-running load that logs in once at the start will start failing partway through with an authentication error - the records already sent are saved, the rest are not. If you are looping over a few thousand records, you must refresh the token during the run.
Rather than hardcoding 15 minutes, read expires (seconds) or valid_until from the login response and refresh a minute or two before that.
Refreshing the token
POST /v1/Authentication/RefreshToken. Send the expired access token together with the refresh token - the expired one is still needed, it is validated ignoring its lifetime to identify you:
POST /v1/Authentication/RefreshToken
{
"AccessToken": "<the access_token you were just using, even though it has expired>",
"RefreshToken": "<the refresh_token from your last Login or RefreshToken call>",
"SystemTypeId": 1,
"Identifier": "FABRIC",
"CurrentContextCompanyId": 101
}
| Field | Type | Required? | Notes |
|---|---|---|---|
AccessToken | string | Required | Your current access token, expired or not |
RefreshToken | string | Required | The most recent refresh_token you were given |
SystemTypeId | int | Required | Must match the value you logged in with (1), or the refresh token won't be found |
Identifier | string | Required | Must match your login identifier (e.g. FABRIC) |
CurrentContextCompanyId | int | Optional | The company context for the new token - send the same CompanyId you are loading against |
CurrentContextCountryId, CurrentContextProvinceId | int | Optional | Rarely needed for integration |
The refresh token rotates. Every successful refresh returns a new
refresh_token and invalidates the previous one. You must store the new value and use it on your next refresh - reusing an old refresh token fails with Invalid Refresh Token.The response shape is different to Login. Login nests the token under
result.token; RefreshToken returns it directly under result. So it's result.access_token and result.refresh_token, not result.token.access_token. If you reuse your Login parse step here you will get an empty token.{
"result": {
"access_token": "eyJhbGciOi...",
"refresh_token": "9dRt4x...",
"expires": 900,
"valid_until": "2026-08-27T09:30:00+00:00"
}
}
Simplest option for a batch loader: check the elapsed time at the top of each loop iteration (or each batch of records) and refresh when you're within a couple of minutes of expiry. Re-running
Login instead of RefreshToken also works and is easier to wire up - the refresh flow just avoids sending the password repeatedly.Once you have a token, see Platform for the
conventions every endpoint follows, then your module guide for the records themselves.