Authentication
Sign in to CONNEX and generate an API key — a client id and secret. Your server trades it for a one-hour access token, and sends that token with every API call.
How it works
CONNEX uses the OAuth 2.0 client credentials flow — your server authenticates as your CONNEX account, with no extra sign-in. Access tokens are short-lived, so a leaked one stops working within the hour, and the long-lived secret never leaves your server except to fetch them.
Get an API key
- Sign in to CONNEX with your usual account.
- Open API keys and choose Generate API key. Give it a name you'll recognise — “Shop”, “ERP” — and choose when it expires: 30 days, 90 days, 1 year, a date of your own, or never.
- Copy the
client_id(cnx_client_…) andclient_secret(cnx_sk_…) into your server's configuration.
Make one key per integration, so each can be rotated or deleted on its own. An account can have up to five active keys; expired ones don't count.
Expiry
An expired key can't get tokens, and tokens already issued with it stop working at the same moment — a token never outlives its key, so expires_in can be under an hour near the end. The key list in CONNEX warns you a week ahead; use Change expiry to extend it. The secret stays the same, so nothing using the key needs updating — and an expired key can be renewed the same way.
Scopes & your account
A key belongs to the CONNEX account it was generated in, and reaches that account's data only — there is no parameter to name another one. Tokens carry every scope unless you ask for fewer.
| Scope | Grants |
|---|---|
rates:read | Get rates — live shipping options. |
tracking:read | Get tracking for your account's orders. |
orders:write | Create orders in your account. |
webhooks:write | Webhook endpoints for your account. |
Signed in to more than one CONNEX account? Keys belong to whichever account is selected when you generate them.
Get an access token
/oauth/tokenCall from your server with your key as HTTP Basic auth (or client_id / client_secret form fields). The body is application/x-www-form-urlencoded. There is no refresh token: when the hour is up, ask for a new token the same way.
Body parameters
grant_typestringrequiredclient_credentialsscopestring- Space-separated, to ask for fewer scopes than all. Omit it to get every scope.
Response
access_tokenstring- Send as
Authorization: Bearer …on every API call. expires_ininteger- Seconds until the token expires — 3600.
scopestring- The scopes this token carries.
accountobject- The CONNEX account the key belongs to —
{ fastShipId, name }.
| Error | Meaning |
|---|---|
401 invalid_client | Wrong client id or secret — or the secret was rotated, the key deleted, or the key expired (the message says which). |
400 unsupported_grant_type | grant_type is not client_credentials. |
400 invalid_scope | A scope that doesn't exist. |
curl https://api.connexglobal.co/oauth/token \
-u "$CONNEX_CLIENT_ID:$CONNEX_CLIENT_SECRET" \
-d grant_type=client_credentials{
"access_token": "cnx_at_eyJ0eXAiOiJjbnhfYXQi…",
"token_type": "bearer",
"expires_in": 3600,
"scope": "rates:read tracking:read orders:write webhooks:write",
"account": { "fastShipId": 137971, "name": "21APRIL Jewelry" }
}Use the token
Send the token in the Authorization header of every /v1 request. Reuse it for its full hour — requesting a fresh token per call is slow and unnecessary.
- 401 — the token expired or was revoked, or the key itself expired. Get a new token and retry once; if that is refused, check the key in CONNEX.
- 403 — the token was requested without the scope this endpoint needs.
- Revoking — rotate the secret or delete the key in CONNEX → Tools → API keys. Its tokens stop working within 30 seconds.
let cached = { token: '', expiresAt: 0 };
async function connexToken() {
// Refresh a minute early, so a request never leaves with a token about to expire.
if (Date.now() < cached.expiresAt - 60_000) return cached.token;
const res = await fetch('https://api.connexglobal.co/oauth/token', { /* as above */ });
if (!res.ok) throw new Error('CONNEX key rejected — check or rotate it');
const t = await res.json();
cached = { token: t.access_token, expiresAt: Date.now() + t.expires_in * 1000 };
return cached.token;
}
const rates = await fetch('https://api.connexglobal.co/v1/rates', {
method: 'POST',
headers: {
authorization: `Bearer ${await connexToken()}`,
'content-type': 'application/json',
},
body: JSON.stringify({ Country: 'USA', Weight: 1200, Length: 30, Width: 20, Height: 10 }),
});