Skip to content

Working without an SDK

The IntelliAuth SDKs wrap REST APIs. Everything an SDK does — sign-in, token refresh, MFA, fetching the user profile, managing applications — is HTTP calls underneath. If you'd rather skip the SDK, talk to those same REST APIs directly.

This page is for you if:

  • You work in a language we don't ship an SDK for today (Go, Python, Ruby, Java, .NET, PHP, Rust, Elixir, you name it).
  • You prefer fewer dependencies and would rather own the HTTP layer yourself.
  • You're integrating from a context where running JS isn't practical — a shell script, a Postman test, a serverless function in another language.
  • You're evaluating IntelliAuth and want to see the wire format before committing.

The three formats:

  • OpenAPI YAML — the canonical human-readable spec. Drop it into your code-gen tool of choice (openapi-generator, oapi-codegen for Go, etc.) to scaffold a typed client in any language.
  • OpenAPI JSON — the same spec in JSON. Some tools (Swagger UI, certain IDE plugins) prefer this format.
  • Postman 2.1 collection — every integrator-facing endpoint as a named Postman request, grouped by resource. Import into Postman, Insomnia, or Bruno and explore by clicking.

The bundle is scoped to the integrator surface — the subset of endpoints the SDKs wrap. See "What's in this surface" below for the exact list.

The platform speaks plain OAuth 2.0 / OIDC. Two flows cover almost every direct-API case.

The client credentials grant is the right flow. Get a token, cache it, refresh near expiry.

Terminal window
curl -X POST https://banking-cymmetri.intelliauth.local/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=$INTELLIAUTH_CLIENT_ID" \
-d "client_secret=$INTELLIAUTH_CLIENT_SECRET" \
-d "audience=https://api.cymmetri.com" \
-d "scope=users:read audit:read"

Response:

{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "users:read audit:read"
}

Cache the token for expires_in seconds minus a 60-second safety margin; refresh before expiry. No refresh_token is returned — repeat the call to get a new one.

Without the SDK you implement authorization-code-with-PKCE by hand. The wire format is in that topic. The hard bits the SDK handles for you that you must now handle yourself:

  • Generating + storing the code_verifier for PKCE.
  • Generating + storing + validating the state parameter for CSRF defence.
  • The redirect / callback handling.
  • Silent refresh on a background timer.
  • Tab synchronisation if your app is multi-tab.
  • The MFA flow ID stash if your sign-in policy includes MFA.

This is enough work that a thin custom SDK in your language usually pays back. The OpenAPI spec gives you typed types; the patterns above plus the OAuth topics give you the flow shapes. Most teams build a ~300-line client and ship it.

Same authorization-code-with-PKCE flow as browser, with the platform handoff documented in the native PKCE topic.

Once you have a token:

Terminal window
curl https://banking-cymmetri.intelliauth.local/api/v1/me/profile \
-H "Authorization: Bearer $ACCESS_TOKEN"

The response envelope:

{
"data": {
"id": "usr_01HZ...",
"email": "anita@cymmetri.com",
"name": "Anita Singh",
"email_verified": true
},
"meta": {}
}

Every JSON response uses the same { data, meta } shape. List endpoints have a meta.next_cursor for pagination.

When the platform refuses or fails:

{
"error": "permission_denied",
"message": "You don't have permission to do that.",
"details": { "required_scope": "users:write" },
"request_id": "req_01HZ..."
}

The full enumeration of error codes is in the error code index — the same list the SDKs match on. Branch on error, never on message text. Log request_id so support tickets can correlate to the exact call.

For non-SDK languages, this is the shape of a minimum-viable client:

import time
import requests
class IntelliAuth:
def __init__(self, tenant_url, client_id, client_secret, audience):
self.tenant_url = tenant_url.rstrip('/')
self.client_id = client_id
self.client_secret = client_secret
self.audience = audience
self._token = None
self._token_expires_at = 0
def _get_token(self):
# Refresh 60s before expiry
if self._token and time.time() < self._token_expires_at - 60:
return self._token
res = requests.post(f"{self.tenant_url}/oauth2/token", data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"audience": self.audience,
"scope": "users:read users:write",
})
res.raise_for_status()
body = res.json()
self._token = body["access_token"]
self._token_expires_at = time.time() + body["expires_in"]
return self._token
def request(self, method, path, **kwargs):
headers = kwargs.pop("headers", {})
headers["Authorization"] = f"Bearer {self._get_token()}"
res = requests.request(method, f"{self.tenant_url}{path}", headers=headers, **kwargs)
if res.status_code >= 400:
body = res.json() if res.headers.get("content-type", "").startswith("application/json") else {}
raise IntelliAuthError(body.get("error", "unknown"), body.get("message", res.text), body.get("request_id"))
return res.json()
def list_users(self, **params):
return self.request("GET", "/api/v1/users", params=params)
def create_user(self, **fields):
return self.request("POST", "/api/v1/users", json=fields)
class IntelliAuthError(Exception):
def __init__(self, code, message, request_id=None):
super().__init__(message)
self.code = code
self.message = message
self.request_id = request_id

That's the pattern — token caching, header injection, error wrapping. Scale up by adding methods for the endpoints your code actually touches. If you use the OpenAPI spec for code generation, typed versions of these come for free.

The Go, Ruby, Java, and .NET equivalents all have the same shape. The OpenAPI + Postman downloads give you the contract; this skeleton gives you the structure; your code fills in the verbs you care about.

The downloads above contain the integrator-facing endpoints — the subset the SDKs wrap. Concretely:

Path prefixPurpose
/oauth2/*Token issuance, revocation, introspection, OIDC discovery
/.well-known/*OIDC discovery + JWKS
/api/v1/auth/*Sign-in, sign-out, MFA flow, WebAuthn ceremony
/api/v1/me/*Self-service for the signed-in user
/api/v1/users/*Admin user CRUD + bulk import
/api/v1/groups/*RBAC: groups + membership
/api/v1/applications/*Application CRUD + secret rotation
/api/v1/resources/* + /api/v1/resource-types/*ReBAC: resources + relations + checks
/api/v1/federation/*Programmatic SSO connection management
/api/v1/webhooks/*Webhook subscription management

The full request / response / error shapes for each are in the per-resource API reference topics. Or click through the Postman collection — every endpoint has the request shape, an example body, and the auth header pre-configured.

These exist on the platform but are intentionally not part of the integrator REST API. They're operated by humans in the tenant admin console:

  • Flows and Actions — custom auth-flow orchestration is web-configured in the tenant admin console.
  • Reports — the tenant admin's analytics catalogue is web-only.
  • Audit reading — the tenant admin reads audit logs in the console. For integrator-side event consumption, subscribe to webhooks instead.
  • Threat intelligence feeds — feed configuration is web-only.
  • Branding — logo, theme, email templates are web-only.
  • Breach incident management — read + resolve in the console. Integrators receive the security.breach_incident_opened webhook and act on it.
  • The control plane entirely — orgs, tenants, plans, billing, CP-level members. Web-driven at https://manage.<DOMAIN_BASE>. See Control plane — for context.

The split is deliberate. Things that should happen with deliberate human attention happen on the web; things that need automation happen in the API.

The OpenAPI spec above is versioned with the platform. We bump it on every release. To pull the latest in CI:

Terminal window
curl -fsSL https://docs.intelliauth.local/openapi/intelliauth.openapi.yaml \
> vendor/intelliauth.openapi.yaml

Re-run codegen on the updated YAML to pick up any new endpoints or fields automatically.