API endpoint https://northerninference.ca/v1 Use this as the base URL in every integration.

curl / HTTPie

Talk to Northern Inference directly with curl

Get going in 30 seconds

export NI_API_KEY=ni_live_YOUR_KEY_HERE
curl https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-haiku-4-5-20251001-t4","messages":[{"role":"user","content":"hi"}],"max_tokens":20}'

The model field is the route contract. Copy exact route IDs from northerninference.ca/portal (Models page) or from curl https://northerninference.ca/api/billing/models | jq '.models[].model_id'.


Basic chat completion

curl https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5-20251001-t4",
    "messages": [{"role": "user", "content": "hi"}],
    "max_tokens": 20
  }'

Pick a route

curl https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6-t4",
    "messages": [{"role": "user", "content": "Which jurisdiction am I in?"}],
    "max_tokens": 80
  }'

The model ID is the route contract. Copy the exact routing key from /models or /api/billing/models. API key allowed route tiers authorize which routes the key can use.

Read the custody chain from response headers

curl -sS -D /tmp/headers https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-haiku-4-5-20251001-t4","messages":[{"role":"user","content":"hi"}],"max_tokens":5}' \
  > /dev/null

grep -i '^x-ni-' /tmp/headers

You'll see something like:

x-ni-request-id: 8f2e3b7d9a1c4f08
x-ni-resolved-tier: managed_canadian_cloud
x-ni-resolved-provider: Bedrock
x-ni-resolved-region: ca-central-1
x-ni-resolved-jurisdiction: CA
x-ni-custody-path: NI-CA -> Bedrock-CA
x-ni-credential-source: platform
x-ni-resolved-litellm-model: claude-haiku-4-5-20251001-t4

Streaming

curl -sS https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5-20251001-t4",
    "messages": [{"role": "user", "content": "count to five"}],
    "max_tokens": 30,
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

Full custody pull after the call

curl https://northerninference.ca/api/usage/custody/8f2e3b7d9a1c4f08 \
  -H "Authorization: Bearer $NI_API_KEY"

Disable fallbacks

curl https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -d '{
    "model": "claude-sonnet-4-6-t4",
    "messages": [{"role":"user","content":"hi"}],
    "max_tokens": 10,
    "allow_fallbacks": false
  }' \
  -H "Content-Type: application/json"

Enable PII substitution

curl https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-haiku-4-5-20251001-t4",
    "messages": [{"role": "user", "content": "My name is Jane Doe, email jane@example.com"}],
    "max_tokens": 30,
    "pii_substitution": true
  }'

Entities (name, email, phone, SSN, credit card, IP, location) are replaced with deterministic Faker-generated substitutes before leaving NI infrastructure, and the originals are restored in the response before it reaches you. The pii_audit_log table records what was substituted; admins see it under portal → Admin → PII Audit.

Vision (image input)

Send the image as an image_url content block to any route whose supports_vision is true:

B64=$(base64 -i screenshot.png | tr -d '\n')
curl -sS https://northerninference.ca/v1/chat/completions \
  -H "Authorization: Bearer $NI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6-t4",
    "messages": [
      {"role": "user", "content": [
        {"type": "text", "text": "What does this image say?"},
        {"type": "image_url", "image_url": {"url": "data:image/png;base64,'"$B64"'"}}
      ]}
    ],
    "max_tokens": 300
  }'

See vision-and-capabilities.md for the full guide, including how a client with a hardcoded model table can be told an NI route supports images.

Listing models: two endpoints, two shapes

NI has two model-list endpoints. They return DIFFERENT JSON shapes on purpose, so match your jq path to the endpoint:

EndpointAuthWrapperID field
GET /v1/modelsAPI key required.data[].id
GET /api/billing/modelsnone (public).models[].model_id (also .id)

/v1/models is the OpenAI-compatible list. It requires a key and returns the routes that key is allowed to call, in the standard OpenAI shape ({"object": "list", "data": [{"id": ...}]}). Point any OpenAI SDK at https://northerninference.ca/v1 and client.models.list() just works.

curl -sS https://northerninference.ca/v1/models \
  -H "Authorization: Bearer $NI_API_KEY" \
  | jq '.data[] | {id, supports_vision, recommended_for_agentic, capabilities}'

/api/billing/models is the unauthenticated public catalog. It returns every public route with full metadata (pricing, privacy tier, data residency, capabilities), wrapped in {"models": [ ... ]}.

curl -sS https://northerninference.ca/api/billing/models \
  | jq -c '.models[] | {id: .model_id, name: .display_name, tier: .privacy_tier.label, in: .input_cost_per_m_usd, out: .output_cost_per_m_usd}'

Common mistake: /v1/models puts the array under .data (not .models), and each item uses .id (there is no .name). Using .models[] against /v1/models yields Cannot iterate over null. An unauthenticated /v1/models call returns a 401 whose body links back to the public /api/billing/models catalog.


Source: tests/user_run_tests/integrations/curl.md. Spot a problem? Let us know.