Skip to Content
APIAPI integration

API integration

Routix provides a versioned REST API that allows external applications, such as ERP systems, WMS platforms, BI tools, and AI agents, to securely access your transport management data.

The API is built on OAuth 2.1 and uses a proxy layer to provide stability, versioning, and fine-grained access control.

User level: Advanced

OAuth flow

Routix uses OAuth 2.1 to authenticate external applications. Two flows are available depending on your integration type.

Flow 1: Authorization Code + PKCE

Use this flow when your application needs to act on behalf of a Routix user. The user explicitly grants consent and selects the organization that the application may access.

Sequence

External App Routix Auth User | | | |-- redirect to /authorize --> | | | (client_id, scope, | | | code_challenge, state) | | | |-- consent screen -----> | | | (app name, scopes, | | | org selector) | | | <-- approve + org ----- | | <-- redirect with ?code= --- | | | | | |-- POST /oauth/token -------> | | | (code, code_verifier) | | | <-- { access_token } ------- | | | | | |-- GET /api/v1/{branch}/...-> | (API Proxy) | | <-- { data: [...] } -------- | |

Step 1: Generate PKCE challenge

Before starting the flow, generate a PKCE code verifier and challenge:

// Generate code_verifier (43-128 characters) const array = new Uint8Array(32); crypto.getRandomValues(array); const codeVerifier = btoa(String.fromCharCode(...array)) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, ''); // Generate code_challenge (SHA-256, base64url) const digest = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(codeVerifier) ); const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, '');

Step 2: Redirect to authorize

Redirect the user’s browser to:

https://api.routix.com/auth/v1/oauth/authorize ?client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &response_type=code &code_challenge=BASE64URL_SHA256_HASH &code_challenge_method=S256 &scope=routix:accounts:read routix:orders:read &state=RANDOM_STATE_VALUE
ParameterRequiredDescription
client_idYesYour OAuth app client ID (UUID).
redirect_uriYesMust exactly match one of your registered redirect URIs.
response_typeYesAlways code.
code_challengeYesBase64url-encoded SHA-256 hash of your code_verifier.
code_challenge_methodYesAlways S256.
scopeYesSpace-separated list of requested scopes.
stateRecommendedRandom string to prevent CSRF attacks.

Routix shows a consent screen where the user:

  • Sees the application name and all requested scopes.
  • Selects which organization to grant access to.
  • Approves or denies the request.

On approval:

  • A record is created in oauth_client_authorizations that links the app to the selected organization.
  • The user is redirected back to your redirect_uri with ?code=xxx&state=xxx.

Step 4: Exchange code for tokens

curl -X POST https://api.routix.com/auth/v1/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "code": "AUTHORIZATION_CODE", "redirect_uri": "https://yourapp.com/callback", "client_id": "YOUR_CLIENT_ID", "client_secret": "routix_a1b2c3d4...", "code_verifier": "YOUR_ORIGINAL_CODE_VERIFIER" }'

Response:

{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 3600, "refresh_token": "v1.MjQ0ZWY5..." }

Step 5: Refresh tokens

Access tokens expire after 1 hour. Use the refresh token to request a new one:

curl -X POST https://api.routix.com/auth/v1/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "refresh_token", "refresh_token": "v1.MjQ0ZWY5..." }'

Refresh tokens are single-use and rotate on every refresh.

Token claims: Authorization Code

The JWT is enriched by a custom access token hook that adds:

{ "sub": "user-uuid", "aud": "authenticated", "role": "authenticated", "organization_id": "org-uuid", "oauth_client_id": "client-uuid", "oauth_scopes": ["routix:accounts:read", "routix:orders:read"] }

These claims are enforced by Row Level Security (RLS) in the database.

Flow 2: Client credentials

Use this flow for server-to-server communication. No user interaction is required.

Sequence

Your Server Routix | | |-- POST /oauth-token -------> | | (client_id, client_secret) | | <-- { access_token } ------- | | | |-- GET /api/v1/{branch}/...-> | | <-- { data: [...] } -------- |

Request a token

curl -X POST https://api.routix.com/functions/v1/oauth-token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "client_credentials", "client_id": "YOUR_CLIENT_ID", "client_secret": "routix_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" }'

Response:

{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 3600, "scope": "routix:accounts:read routix:orders:read", "organization_id": "org-uuid", "branch_ids": ["branch-uuid-1", "branch-uuid-2"] }

How it works

  • Your server sends client_id and client_secret to the token endpoint.
  • Routix verifies the secret by comparing SHA-256 hashes.
  • A JWT is built with the app’s organization_id, allowed_scopes, and allowed_branch_ids.
  • The JWT is signed with HS256 and returned.
  • You use the token to call the API proxy.

Token claims: Client credentials

{ "iss": "supabase", "aud": "authenticated", "role": "authenticated", "sub": "app-uuid", "organization_id": "org-uuid", "oauth_client_id": "client-uuid", "oauth_scopes": ["routix:accounts:read"], "oauth_branch_ids": ["branch-uuid-1"], "oauth_app_name": "My ERP Sync" }

Comparison

AspectAuthorization Code + PKCEClient credentials
User interactionYes, consent screenNo
Token endpoint/auth/v1/oauth/token/functions/v1/oauth-token
Organization bindingSelected by user during consentFrom app registration
Refresh tokensYes, rotatingNo, request a new token
Use caseUser-facing apps, marketplaceBackend sync, BI, automation

Security best practices

PracticeWhy
Always use PKCE with S256Required for Authorization Code flow. Plain challenges are not supported.
Store secrets securelyThe client secret is shown only once. Use a secrets manager and never store it in source code.
Validate the state parameterPrevents CSRF attacks during the redirect flow.
Request minimal scopesOnly request the permissions your application actually needs.
Rotate secrets regularlyUse secret rotation to limit exposure.
Handle token expiryTokens expire in 1 hour. Refresh or request a new token.
Never expose secrets in frontend codeClient credentials must only be used from a backend server.

Architecture

The integration system consists of three layers:

LayerDescription
OAuth serverAuthenticates external apps and issues JWT tokens containing organization, scope, and branch claims.
API proxyVersioned REST endpoint that validates tokens, enforces scopes, resolves branches, and returns filtered data.
App managementDashboard inside Routix to register, configure, and manage OAuth apps.

How it fits together

External App | |-> POST /functions/v1/oauth-token | -> JWT with org_id, scopes, branches | '--> GET /functions/v1/api/v1/{branch}/accounts -> Verify JWT -> Check scope -> Resolve branch -> Query DB -> Return filtered fields

Key concepts

Scopes

Every API action is protected by a scope following this pattern:

routix:<resource>:<action>
ScopeDescription
routix:accounts:readRead customers, vendors, and carriers.
routix:accounts:writeCreate and update accounts.
routix:orders:readRead orders.
routix:orders:writeCreate and update orders.
routix:vehicles:readRead vehicles.
routix:vehicles:writeCreate and update vehicles.
routix:staff:readRead staff members.
routix:equipment:readRead equipment.
routix:planning:readRead planning and route data.
routix:invoices:readRead invoices.
routix:invoices:writeCreate and update invoices.

Branch scoping

The API is scoped per branch using a short public ID of 8 characters, for example RS2RDH3B. That means:

  • Every API call targets exactly one branch.
  • Apps can be restricted to specific branches during registration.
  • Branch data remains isolated.

API versioning

VersionBehavior
v1Stable, immutable field set that is safe for production integrations.
v2Superset of v1 with additional fields that may grow over time.

Both versions run simultaneously. Use v1 for stable contracts and v2 when you need additional data.

  • API calls & examples
  • Setup guide
Last updated on