# Krutrim Ekam > India's agent-identity and access control plane. Every AI agent gets a real, owned identity — not a > shared API key. Tokens are short-lived, scoped, audience-bound, delegated (RFC 8693), and verified > offline against a JWKS. One control plane for humans and agents, DPDP-native, in open beta. Ekam issues governed credentials for non-human identities (agents) and humans, so an AI gateway or resource server can verify *who is acting, for whom, and within what bounds* — offline — and revoke access in seconds. Base URL: https://ekam.olakrutrim.com Ekam is HUMAN-ROOTED: every agent is owned by a human (or org). There is NO anonymous agent signup — that root of authority is what makes an agent attributable, revocable, and DPDP-accountable. ## Current production checkpoint - Live deployment: ekam:v149 from origin/main post-#228/#230 (feature commit 8fe2ddd; 2026-08-12). - Hyd + BLR digest parity: sha256:409baa3e2c608ac326513c12bf83ee67e3c032f9f25155fe35a185ac03fb65be. - Health: Postgres up, persistent Vault-backed signing key, issuer https://ekam.olakrutrim.com. - Provider-neutral BharatRouter CLI identity is live on the Ekam side; BR-side CLI/bridge rollout is separate. ## How an agent gets a credential 1. A human signs in at https://ekam.olakrutrim.com/account (any Google account in open beta) and creates a workspace. 2. The workspace yields an owner key (ekam_sk_…) — this is the OAuth client representing that human. 3. With the owner key: create agents (POST /v1/agents) and broker tokens (POST /oauth/token). 4. An agent that already holds a token can fan out SUB-agents under the same human via delegation (RFC 8693 token-exchange). For human OBO v2, the delegated human is in sub, the agent actor is in act.sub / agent_id, and the organization/owner is in owner_id. See the full flow: https://ekam.olakrutrim.com/llms-full.txt ## Broker + verify (the hot path) - Broker a token: POST https://ekam.olakrutrim.com/oauth/token (grant_type=urn:ietf:params:oauth:grant-type:token-exchange, agent_id, resource, scope) with Authorization: Bearer . - Verify offline: https://ekam.olakrutrim.com/.well-known/jwks.json (ES256, kid ekam-2026-06); validate iss, aud (RFC 8707), exp. - Live revocation: POST https://ekam.olakrutrim.com/oauth/introspect. ## BharatRouter CLI contract (Ekam side live) - Registered public client_id: bharatrouter-cli. - Audience/resource: https://api.bharatrouter.com. - Allowed scopes: openid profile email offline_access models:invoke models:chat models:embed. - Defaults: offline_access models:invoke. - Browserless login: POST https://ekam.olakrutrim.com/device_authorization, then poll POST https://ekam.olakrutrim.com/oauth/token with grant_type=urn:ietf:params:oauth:grant-type:device_code and the same client_id. - Refresh: rotating, revocable refresh tokens; replay revokes the family. - External Google/OIDC/Entra/SAML/GitHub logins are normalized into Ekam human tokens before OBO. Never pass an unregistered raw IdP token into agent exchange. - No-human agent_credentials still work and intentionally omit human attribution. ## Discovery (machine-readable) - [OAuth Authorization Server metadata](https://ekam.olakrutrim.com/.well-known/oauth-authorization-server) - [OpenID configuration](https://ekam.olakrutrim.com/.well-known/openid-configuration) - [Protected Resource metadata (RFC 9728)](https://ekam.olakrutrim.com/.well-known/oauth-protected-resource) - [JWKS](https://ekam.olakrutrim.com/.well-known/jwks.json) - [SSF / CAEP configuration](https://ekam.olakrutrim.com/.well-known/ssf-configuration) - [Health](https://ekam.olakrutrim.com/healthz) ## Docs - [API & developer docs](https://ekam.olakrutrim.com/docs) - [App login + logout — use Ekam as your app's OIDC login (authorize/token/userinfo/logout, oauth2-proxy)](https://ekam.olakrutrim.com/docs#app-login) - [Create agents (human-rooted)](https://ekam.olakrutrim.com/docs#create-agents) - [Cookbook (copy-paste recipes)](https://ekam.olakrutrim.com/cookbook) - [Postman collection](https://ekam.olakrutrim.com/ekam.postman_collection.json) - [Playground](https://ekam.olakrutrim.com/playground) ### Comprehensive reference docs (source tree, under docs/ — human- and LLM-readable) These deep-reference documents ship in the Ekam repository (path shown); read them for exhaustive, code-accurate detail. They cross-link each other. - docs/API-REFERENCE.md — every HTTP endpoint: method, path, auth gate, params/body (from the zod schemas), success shape, error codes, curl examples. - docs/ERROR-CODES.md — machine-readable catalogue of every error code (markdown table AND a JSON block) with HTTP status, meaning, likely cause, and fix. Answer support questions from this. - docs/OBSERVABILITY.md — pino logging + reqId correlation, audit_events / usage_events / login_events / webhook deliveries, the Loki/Grafana/Alloy setup, and a debugging playbook. - docs/LLM-SUPPORT.md — grounding contract, structured FAQ, "how do I…" recipes, and a troubleshooting decision tree for an LLM support/onboarding agent. - docs/ONBOARDING-PERSONAS.md — numbered end-to-end walkthroughs for the agent developer, enterprise admin, and operator personas. - docs/TUTORIALS.md — step-by-step tutorials for the flagship flows, plus video scripts and Playwright screen-capture authoring guides. - docs/integrations/bharatrouter-requirements.md — BR-side bridge/CLI implementation contract and compatibility strategy. - docs/migrations/provider-neutral-br-cli-identity.md — provider-neutral human identity, BR CLI device auth, refresh-token rotation, membership policy, and v2 OBO migration notes. - docs/HANDOFF-2026-08-12-BR-CLI.md — compact current-state memory for the live Ekam BR CLI rollout. ## Okta parity (workforce IdP lifecycle + governance) Ekam is a full CIDM/Okta-parity workforce IdP. Beyond SSO + SCIM-inbound birthright, it ships: - [SCIM outbound / Directory Sync](https://ekam.olakrutrim.com/docs#scim-out) — Ekam pushes users DOWN to a downstream app's SCIM endpoint. Targets: POST/GET/PATCH/DELETE https://ekam.olakrutrim.com/v1/tenants/:id/scim-targets ; provision: POST https://ekam.olakrutrim.com/v1/tenants/:id/scim-targets/:targetId/push . Downstream bearer token is sealed at rest. - [JML MOVER](https://ekam.olakrutrim.com/docs#mover) — a SCIM-inbound PATCH /scim/v2/Users/:id that changes attributes re-evaluates birthright + group scope and emits a signed identity.moved webhook (before/after attrs + previous_scope→scope). - [Access certification](https://ekam.olakrutrim.com/docs#certification) — campaigns over grants: POST/GET https://ekam.olakrutrim.com/v1/tenants/:id/certification-campaigns ; items GET .../:cid/items , approve/deny .../:cid/items/:itemId/(approve|deny) ; close .../:cid/close applies decisions (denied grants revoked). - [Delegated admin roles](https://ekam.olakrutrim.com/docs#delegated-admin) — GET/POST https://ekam.olakrutrim.com/v1/tenants/:id/admins ; PUT/DELETE .../admins/:target/role (target = email or hum_ id) with role in {admin, app-admin, user-admin, help-desk}. app-admin grants may carry an "apps":["sp_…"] scope — the admin then manages ONLY those apps (scoped admins cannot create apps or touch tenant IdPs). The last full admin can never be demoted/removed. Backs every "org admin" gate (tenantAdmin = admin token OR qualifying human role). - [Tenant-admin API keys](https://ekam.olakrutrim.com/docs#delegated-admin) — ekam_ak_… machine credentials for delegated admins: POST/GET https://ekam.olakrutrim.com/v1/tenants/:id/admin-keys , DELETE .../admin-keys/:kid . Bound to one admin in one tenant; role resolves LIVE (revoke the role and the key follows instantly); every action audited as the bound human. The sanctioned replacement for sharing EKAM_ADMIN_TOKEN. - [App launcher & tiles](https://ekam.olakrutrim.com/docs#launcher) — the org app registry drives My Apps: POST https://ekam.olakrutrim.com/v1/tenants/:id/saml-apps registers a tile (SAML app, or type:"link" with url for OIDC/plain-URL apps — link tiles have no SAML slice). Eligibility = entity / entities[] / orgWide; per-user assignment via PUT /v1/saml-apps/:id/require-assignment + POST/GET/DELETE .../assignments. Human-side: GET https://ekam.olakrutrim.com/v1/me/apps , PUT/DELETE .../me/apps/:slug/favorite , POST .../me/apps/:slug/launched . NOTE: OIDC clients from POST /register are login-only (no org mapping, no tile). - [Org branding](https://ekam.olakrutrim.com/docs#branding) — GET/PUT https://ekam.olakrutrim.com/v1/tenants/:id/branding { displayName, logoUrl, accentColor }. Per-app logos live on each app record (logoUrl), not here. - [SCIM lifecycle / leaver](https://ekam.olakrutrim.com/docs#birthright) — mint the per-tenant SCIM service token at POST https://ekam.olakrutrim.com/v1/tenants/:id/scim-token (static long-lived bearer, shown once, re-issue rotates — the SoR credential). Leaver: PATCH/PUT /scim/v2/Users/:id {active:false} or DELETE = SOFT deactivate (never hard-deletes) → fires identity.deactivated + pushes active:false to SCIM-outbound targets. It does NOT terminate upstream SSO accounts — disable at the source IdP too. - [SAML Response signing](https://ekam.olakrutrim.com/docs#saml-response-sign) — per-SP signResponse flag on POST https://ekam.olakrutrim.com/v1/tenants/:id/saml-apps signs the envelope in addition to the (Okta "Sign Response"). - [Adaptive risk + step-up](https://ekam.olakrutrim.com/docs#adaptive-risk) — GET https://ekam.olakrutrim.com/v1/tenants/:id/login-events (risk-scored sign-ins, always recorded). Env EKAM_ADAPTIVE_RISK gates enforcement: when on, high-risk logins must step-up MFA before a session is minted. ## Agent security (ISPM · attestation · remediation) The agent-security wedge — discover ungoverned identities, attest workloads, and drive findings to closure. All tenant-scoped + org-admin gated. - [NHI discovery + posture (ISPM)](https://ekam.olakrutrim.com/docs#nhi-discovery) — inventory + score non-human identities. Ingest: POST https://ekam.olakrutrim.com/v1/tenants/:id/nhi-discovery/ingest { items:[{source,kind,externalId,ownerRef?,privileges?,lastUsedAt?,lastRotatedAt?,status}] } (1..1000) -> 201 {ingested, items:[{id,risk,flags}]}. List/report/owner: GET https://ekam.olakrutrim.com/v1/tenants/:id/nhi-discovery (filters source,kind,risk,flag), GET .../nhi-discovery/report (byRisk/byFlag/bySource/byKind), PUT https://ekam.olakrutrim.com/v1/nhi-discovery/:id/owner {ownerRef}. Flags: ownerless | stale (active, lastUsedAt >90d) | no_rotation (active, lastRotatedAt >180d or never) | over_privileged (privilege matches *,admin,root,superuser,owner) | disabled_present. Risk=HIGH iff over_privileged OR (stale AND no_rotation); MEDIUM for any other substantive flag (ownerless alone=MEDIUM); LOW when clean. Unknown timestamp = not stale (fail to false). - [Discovery connectors (8)](https://ekam.olakrutrim.com/docs#nhi-connectors) — GET https://ekam.olakrutrim.com/v1/tenants/:id/nhi-discovery/connectors -> {connectors:[github,m365,okta,gcp,aws,snowflake,slack,datadog]}. Run: POST https://ekam.olakrutrim.com/v1/tenants/:id/nhi-discovery/connectors/:source/run (body=per-connector config incl. source token, NOT persisted) -> 201 (ok) / 200 (error) DiscoveryRun {id,source,status:ok|error,discovered,upserted,error,startedAt,finishedAt}. A source-side failure is a 200 status:error run row, never a 500. History: GET .../nhi-discovery/runs -> {total,runs}. - [Workload attestation P1/P2/P3](https://ekam.olakrutrim.com/docs#attestation) — issuers: POST/GET https://ekam.olakrutrim.com/v1/tenants/:id/attestation-issuers { issuer, jwksUri, kind:k8s_sa|cloud_oidc|spiffe|generic } , DELETE .../attestation-issuers/:aid. Present workload_attestation (a signed k8s SA / cloud OIDC / SPIFFE JWT) on ANY agent-token seam of POST https://ekam.olakrutrim.com/oauth/token (default broker, OBO, delegation, CIBA) and POST https://ekam.olakrutrim.com/nhi/token. P1 verify (sig + trusted iss) -> stamps att claim {workload,method,issuer,iat,exp}; workload id = k8s:/ | spiffe://… | cloud: | raw sub. P2: blueprint requireAttestation:true -> 400 attestation_required. P3 freshness: iat within blueprint.attFreshnessSeconds ?? tenant policy ?? 3600 (GET/PUT https://ekam.olakrutrim.com/v1/tenants/:id/attestation-policy {freshnessSeconds}); stale -> 400 attestation_stale (fail-closed, any blueprint). Continuous re-attestation is free (agents have no refresh grant). Metered attestation_verified/rejected/stale. - [Finding remediation lifecycle](https://ekam.olakrutrim.com/docs#findings) — findings are AUTO-CREATED (never POSTed) for every medium|high identity by the ingest/connector-run/shadow-scan paths, keyed by fingerprint=sha256(source,kind,name) so re-detection AUTO-REOPENS. GET https://ekam.olakrutrim.com/v1/tenants/:id/findings (filters status,type=nhi|shadow_agent|agent,risk,surface=open|risk_accepted) -> {total, byStatus{open,acknowledged,remediating,resolved,risk_accepted}, items:[{…,effectiveStatus}]}. PATCH https://ekam.olakrutrim.com/v1/findings/:id {status,assignee?,note?,reason?,ackExpiresAt?}: 409 illegal_transition, 422 reason_required (risk_accepted needs a reason), 404. Acknowledged auto-sets a 30-day ack window; an expired ack reads as open at the read boundary (effectiveStatus, never persisted). Human queue: https://ekam.olakrutrim.com/posture. - [Shadow-agent discovery](https://ekam.olakrutrim.com/docs#shadow-agents) — GET https://ekam.olakrutrim.com/v1/tenants/:id/shadow-agents (filters risk,governed=true|false,source), .../shadow-agents/report, .../shadow-agents/sources -> {sources:[openai]}. Scan: POST https://ekam.olakrutrim.com/v1/tenants/:id/shadow-agents/scan/:source (body incl. token, org?, maxPages? default 20; NOT persisted) -> 201 {source,status:ok,discovered,governed,shadow} or 200 {status:error,error,discovered:0}. A claimed ekam_agent_id counts as governed only if it RESOLVES to a real Ekam agent. Flags: ungoverned (core) | risky_tool (shell/exec/code_interpreter/admin/delete/payment/wire/sudo) | over_tooled (>8) | stale (>90d) | disabled_present. Risk=HIGH iff ungoverned OR (risky_tool AND active). - [Agent posture + owner JML](https://ekam.olakrutrim.com/docs#agent-posture) — score the agents Ekam DID mint: GET https://ekam.olakrutrim.com/v1/tenants/:id/agents/posture (filters risk,flag), .../agents/posture/report (+ orphaned:[{id,name,ownerId}]). Flags: orphaned | stale | never_used | over_privileged | retired_present | attestation_stale. Risk=HIGH iff orphaned OR over_privileged. Owner JML: POST https://ekam.olakrutrim.com/v1/owners/:id/offboard retires+revokes ALL the owner's agents (kill-switch + CAEP session-revoked each) and records the offboarding so survivors score orphaned (idempotent); POST https://ekam.olakrutrim.com/v1/owners/:id/reinstate clears the orphaned flag but does NOT un-revoke. ## Sign in with Microsoft / Entra + Dual-context - [Sign in with Microsoft / Entra (tid-gated)](https://ekam.olakrutrim.com/docs#ms-entra) — browser: GET https://ekam.olakrutrim.com/auth/microsoft/login (503 microsoft_login_unconfigured unless MS_CLIENT_ID+MS_CLIENT_SECRET; redirects to login.microsoftonline.com/{MS_TENANT default common}/oauth2/v2.0/authorize, scope openid email profile, prompt select_account) -> GET https://ekam.olakrutrim.com/auth/microsoft/callback (400 bad_state/missing_code, 502 upstream_unreachable, 401 token_exchange_failed) hands back a type:human token. Identity is anchored on the immutable directory id tid + object id oid, NEVER email. Register a tenant's directory (unscoped app-admin): POST/GET https://ekam.olakrutrim.com/v1/tenants/:id/entra { azureTid(GUID), entity(must belong to tenant), verifiedDomains? } ; DELETE https://ekam.olakrutrim.com/v1/entra/:id (platform-admin). A registered tid re-verifies with the issuer pinned to that azureTid and domain-auto-joins the org only when tid AND email-domain map to the same tenant; every other directory lands in a fail-closed personal workspace keyed on a synthetic ms:: principal. - [Dual-context (org + personal)](https://ekam.olakrutrim.com/docs#dual-context) — a person holds their org identity AND an isolated personal workspace (tenant tnt_personal_). GET https://ekam.olakrutrim.com/v1/me/contexts (human SSO token; 401 else) -> {activeContextId, contexts:[{contextId,kind:org|personal,tenant,entity,email,active}]}. POST https://ekam.olakrutrim.com/v1/me/contexts/switch {to} mints a token re-scoped to the target (different sub+tenant; never valid across contexts) -> {access_token,token_type,expires_in,principal,context}. The switchable set is EXACTLY the caller's own org identity (same tenant AND entity) + personal — never a sibling entity/tenant; a "to" outside it is 403 context_forbidden. BharatRouter parity. ## Concepts - Organization vs entity: an ORGANIZATION (tenant, e.g. "ola") is the company; ENTITIES (e.g. "olacabs_india") are legal-entity boundaries WITHIN it. A human carries one entity; an app admits entity / entities[] / orgWide; the My Apps launcher shows a human the apps in their tenant that admit their entity. An entity is never listed as an organization. - Principals: agents (type:agent, owned by a human/owner) and humans (type:human, via SSO). - Delegation: RFC 8693 records who acted and for whom. Human OBO v2 claims put the delegated human in sub, the agent actor in act.sub / agent_id, and the organization/owner in owner_id; compatibility normalization preserves older BR parsing during migration. - External identity: email is an attribute, not the durable key. Humans are keyed by immutable provider subject (Google/OIDC iss+sub, Entra tid+oid, SAML issuer+stable NameID, GitHub issuer+numeric id). - Membership: authentication does not grant org access. Invitation, SCIM, pre-provisioning, legacy migration, or explicit domain-auto-join policy creates membership. - Audience binding (RFC 8707): a token is valid for exactly one resource. - Kill-switch + CAEP push: revoke an agent and subscribed gateways evict its tokens immediately. - JIT-PAM, CIBA human-in-the-loop, SCIM birthright access, MCP authorization server, per-resource authz. - Identity graph + DPDP: one canonical Person above all accounts/agents for access & erasure. ## Blog - [The shared API key is over: agent identity for developer platforms](https://ekam.olakrutrim.com/blog/agent-identity-for-developer-platforms): API clients, test runners, CI jobs and MCP tools now act on their own — and they all authenticate with the same long-lived, unscoped, un-revocable key. Here's what a real agent identity unlocks for a developer platform and the users who live inside it. - [Identity and billing in one plane: metering agents by the owner](https://ekam.olakrutrim.com/blog/identity-and-billing-in-one-plane): Every agent-identity system tells you who an agent is. None tell you what it cost. Ekam fuses identity with inference metering — per agent, per owner, and across a delegation chain — so the token that authorizes a call also accounts for it. - [Why India needs its own agent-identity control plane](https://ekam.olakrutrim.com/blog/why-india-needs-an-agent-identity-control-plane): AI agents are about to outnumber human users. They authenticate with shared, long-lived API keys that can't be scoped, attributed, or revoked. For India to build AI the world trusts, agents need real identity — and a control plane that lives under our own law. - [How offline token verification works (and why it's fast)](https://ekam.olakrutrim.com/blog/how-offline-token-verification-works): An Ekam token is an ES256 JWT your gateway verifies in-process against a cached JWKS — no network call to authorize a request. Here's the architecture, the exact checks, and the measured numbers. - [10 things to build on Ekam this week](https://ekam.olakrutrim.com/blog/build-on-ekam-this-week): Every endpoint below is live in open beta. Copy a curl, get a working result. From governing an MCP server to a kill-switch drill — concrete use cases, not slideware. - [How BharatRouter runs on Ekam: agent identity for an AI gateway](https://ekam.olakrutrim.com/blog/how-bharatrouter-runs-on-ekam): A model gateway hands out one shared key and hopes for the best. BharatRouter swapped that for verifiable, revocable, human-rooted agent identity — here's the architecture and what it gains. ## Legal - [Privacy](https://ekam.olakrutrim.com/privacy) · [Terms](https://ekam.olakrutrim.com/terms) · [DPDP](https://ekam.olakrutrim.com/dpdp) ## Notes for automated clients - Open beta: usage is metered, billing is off. - Current prod checkpoint (2026-08-12): ekam:v149 from origin/main post-#228/#230 is live in Hyd + BLR with digest parity. - Rate limiting is per principal and returns a standard 429 + Retry-After when exceeded (no injected latency). Limits are generous (default 100 req/s per principal) — a real caller never trips them. Responses carry X-RateLimit-Limit / X-RateLimit-Remaining; a 429 adds Retry-After + X-RateLimit-Reset. On 429, honor Retry-After and back off with jitter.