Calling the i9Network APIs
Log in for an access token, then call any endpoint through the gateway with a Bearer token and your tenant id. Two requests get you your first authenticated response.
Base URLs
Every request goes through the API gateway. Read each product’s spec for paths and shapes, but always send requests to the gateway host — never a service’s internal host.
https://api.i9network.com/api/v1
Not yet available — use production for now.
The full URL for any endpoint is {gateway} + the endpoint path, e.g. https://api.i9network.com/api/v1/subscriber/subscribers.
Log in
Authenticate once with POST /auth/login. Take the accessToken from the response body and send it as a Bearer token on every later call.
# Public route: no X-Tenant-ID, no bearer needed to call it
curl -s -X POST "https://api.i9network.com/api/v1/auth/login" \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"••••••••"}'{
"success": true,
"message": "Login successful",
"data": {
"accessToken": "<JWT>",
"tokenType": "Bearer",
"expiresAt": "2026-01-01T00:15:00Z",
"refreshToken": "<JWT>"
}
}Login also sets HttpOnly cookies for browser sessions; scripts should use the Bearer header. If the response returns requiresMfa, complete POST /auth/login/mfa/verify. If it returns businessChoices, re-POST /auth/login with a businessId.
Required headers
| Header | Value | Required |
|---|---|---|
| Authorization | Bearer <accessToken>The token from the login response body. | Yes |
| X-Tenant-ID | a tenant id, or systemPresent on every authenticated call. Send it exactly once — a duplicate is rejected with 403. Omit it on /auth/login and /auth/refresh. | Yes |
| Content-Type | application/jsonRequired on POST / PUT / PATCH. | On writes |
Use systemfor platform-wide reads; use a specific tenant id to scope a request to one tenant. The header satisfies the gateway’s presence check and selects scope — real access is derived from your token.
API keys
COMING SOONToday every call uses a short-lived bearer token plus X-Tenant-ID. For server-to-server integrations we’re adding API keys: a single long-lived credential that embeds your tenant and scope, so you won’t send a separate tenant header.
- Sent as
X-API-Key: i9_…on every request. - Tenant and scope are bound to the key when it’s issued — no
X-Tenant-IDneeded. - Least-privilege scopes, independently revocable and rotatable.
The API Playground already has an API key mode you can preview. Provisioning — creating and rotating keys — ships with this feature; until then, use a bearer token.
Response envelope
Every response is wrapped in a consistent envelope. The payload you want is always under .data.
{
"success": true,
"message": "OK",
"data": { "items": [ /* your payload lives here */ ] }
}Token lifecycle
The access token lasts roughly 15 minutes. On a 401 mid-session, re-run /auth/login (scripts) or POST /auth/refreshwith the refresh token (browsers). Short-lived scripts don’t need to refresh proactively — just log in again.
Quickstart
Log in, then call an endpoint — two requests:
GW=https://api.i9network.com/api/v1
# 1. Log in → grab the accessToken from .data
TOKEN=$(curl -s -X POST "$GW/auth/login" \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"••••••••"}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["accessToken"])')
# 2. Call any endpoint — send X-Tenant-ID exactly once
curl -s "$GW/subscriber/subscribers?limit=5" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-ID: system"Errors & gotchas
- Always go through the gateway host above — calling a service’s internal host bypasses routing and TLS.
- A missing X-Tenant-ID fails at the gateway (400/401) before your endpoint ever runs.
- A duplicate X-Tenant-ID header returns 403.
- Sending X-Tenant-ID on /auth/login is rejected — login happens before a tenant is selected.
- A route can 404 at the gateway even if it exists in the spec, until it is published.
- The payload is always under .data, never at the top level of the response.
Status conventions
POST → 201 CreatedPUT → 200 OKDELETE → 204 No Content
