Authenticate API

This is where every Olanzo integration starts. Exchange your credentials for an access token here, then send that token to the CRM, Email, SMS and Insights APIs.

One token, four APIs

The token you get here is accepted by CRM, Email, SMS and Insights. You do not authenticate separately against each one. (Transactional Messaging is the exception — it issues its own tokens.)

Three secrets, none of them belong in a client

Your password and private token authenticate your whole account, and the access token they produce can read and write your customers' data. Call this endpoint from your backend and keep all three off any browser page or mobile app.

Download for AI review

Copy or download this guide as Markdown to paste into an AI assistant for help integrating against it.

Environment

You are reading the guide for the host that served this page. The badge in the header and every example below already point at it — nothing to substitute by hand, and no other environment's addresses appear on this page.

PropertyValue
Environment
Base URLhttps://authenticate-dev.jirafix.net
Credentials are per environment

A private token issued for one environment will not authenticate against another. Use the credentials that belong to the environment you are calling.

Get an access token

Send your username, password and private token. Your private token identifies your account and comes from the configuration section of the portal — ask support if you do not have one.

POST /v1/token

Exchanges your credentials for an access token.

Tokens last 3600 seconds by default. Request a new one when it expires — there is no separate refresh call, though the response does include a refresh_token.

Parameters

NameTypeRequiredDescription
usernamestringYesThe account's username, usually an email address.
passwordstringYesThe account's password. Server-side only.
privatetokenstringYesYour account's private token, from the portal's configuration section. Note the spelling — one word, all lower case.
validityintegerNoHow long the token should last, in seconds. Defaults to 3600.

Responses

StatusMeaning
200Returns access_token, refresh_token, token_type and expires_in.
400The body was missing or a required field was absent.
401The username, password or private token was not accepted.
curl -X POST https://authenticate-dev.jirafix.net/v1/token \
  -H "Content-Type: application/json" \
  -d '{"username":"you@yourcompany.com","password":"<your-password>","privatetoken":"<your-private-token>","validity":3600}'
const response = await fetch("https://authenticate-dev.jirafix.net/v1/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    username: "you@yourcompany.com",
    password: process.env.OLANZO_PASSWORD,
    privatetoken: process.env.OLANZO_PRIVATE_TOKEN,
    validity: 3600,
  }),
});

const { access_token } = await response.json();
using var http = new HttpClient { BaseAddress = new Uri("https://authenticate-dev.jirafix.net") };

var response = await http.PostAsJsonAsync("/v1/token", new
{
    username = "you@yourcompany.com",
    password = Environment.GetEnvironmentVariable("OLANZO_PASSWORD"),
    privatetoken = Environment.GetEnvironmentVariable("OLANZO_PRIVATE_TOKEN"),
    validity = 3600,
});
import os, requests

response = requests.post(
    "https://authenticate-dev.jirafix.net/v1/token",
    json={
        "username": "you@yourcompany.com",
        "password": os.environ["OLANZO_PASSWORD"],
        "privatetoken": os.environ["OLANZO_PRIVATE_TOKEN"],
        "validity": 3600,
    },
)

access_token = response.json()["access_token"]
200 OK

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600
}
The response is snake_case

The fields are access_token, refresh_token, token_type and expires_in — not accessToken or expiresIn. Reading the camelCase spelling gives you nothing, with no error to explain it.

POST /token

The same operation at the host root.

Identical behaviour and body — this path exists so integrations written against the root can keep working. New integrations should prefer /v1/token.

Parameters

NameTypeRequiredDescription
usernamestringYesAs above.
passwordstringYesAs above.
privatetokenstringYesAs above.

Responses

StatusMeaning
200Same response as /v1/token.
400The body was missing or a required field was absent.
401The credentials were not accepted.
curl -X POST https://authenticate-dev.jirafix.net/token \
  -H "Content-Type: application/json" \
  -d '{"username":"you@yourcompany.com","password":"<your-password>","privatetoken":"<your-private-token>","validity":3600}'
const response = await fetch("https://authenticate-dev.jirafix.net/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    username: "you@yourcompany.com",
    password: process.env.OLANZO_PASSWORD,
    privatetoken: process.env.OLANZO_PRIVATE_TOKEN,
    validity: 3600,
  }),
});

const { access_token } = await response.json();
using var http = new HttpClient { BaseAddress = new Uri("https://authenticate-dev.jirafix.net") };

var response = await http.PostAsJsonAsync("/token", new
{
    username = "you@yourcompany.com",
    password = Environment.GetEnvironmentVariable("OLANZO_PASSWORD"),
    privatetoken = Environment.GetEnvironmentVariable("OLANZO_PRIVATE_TOKEN"),
    validity = 3600,
});
import os, requests

response = requests.post(
    "https://authenticate-dev.jirafix.net/token",
    json={
        "username": "you@yourcompany.com",
        "password": os.environ["OLANZO_PASSWORD"],
        "privatetoken": os.environ["OLANZO_PRIVATE_TOKEN"],
        "validity": 3600,
    },
)

access_token = response.json()["access_token"]

Using the token

Send it as a bearer token on every request to the other APIs. Each has its own host and its own guide at /docs — open that guide on the environment you are integrating against and it will show you the right host, the same way this page shows you this one.

curl -X GET <crm-host>/v1/contacts/lists \
  -H "Authorization: Bearer <access_token>"
const response = await fetch(`${CRM_HOST}/v1/contacts/lists`, {
  headers: { Authorization: `Bearer ${access_token}` },
});
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);
headers = {"Authorization": f"Bearer {access_token}"}
Re-authenticate on a 401, once

A 401 from any of the other APIs usually means the token expired. Fetch a new one and retry the request once. Retrying in a loop against a genuinely rejected credential will not start working.

Errors

StatusWhat it meansWhat to do
200Authenticated.Read access_token — note the underscore.
400The body was missing, or one of the three required fields was absent.Check the JSON parses and includes username, password and privatetoken.
401The credentials were not accepted.Confirm the private token belongs to this environment — one issued elsewhere will not work here.
Most 401s here are the wrong environment

Credentials are per environment. A private token that works against one host will be rejected by another, and the response cannot tell you which mistake you made.