{"openapi":"3.1.0","info":{"title":"Hermes","version":"1.0.0","description":"Hermes is a read-only REST API over the Octiga tenant cache: the directory, mail-flow and risk data Octiga already holds for every tenant of an MSP, served in the shape of the Microsoft Graph and Exchange Online endpoints it came from, plus a live pass-through to Graph for what the cache does not hold. Every /v1 call authenticates with an API key (oct_<env>_...) or a Hermes OAuth 2.1 access token, is scoped to the caller's MSP and tenants, and is limited to 600 requests a minute per key (60 live Graph calls a minute per tenant)."},"servers":[{"url":"https://api.devoctiga.com"}],"security":[{"bearerAuth":[]}],"paths":{"/":{"get":{"summary":"Landing page","security":[],"responses":{"200":{"description":"An HTML page describing Hermes and linking its documents.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"tags":["OAuth"],"summary":"OAuth 2.1 authorization-server metadata (RFC 8414)","responses":{"200":{"description":"The metadata document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthMetadata"}}}}},"security":[]}},"/.well-known/oauth-protected-resource":{"get":{"tags":["OAuth"],"summary":"Protected-resource metadata (RFC 9728)","responses":{"200":{"description":"The metadata document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthMetadata"}}}}},"security":[]}},"/oauth/authorize":{"get":{"tags":["OAuth"],"summary":"Authorization endpoint (code + PKCE S256) — serves the consent page","parameters":[{"schema":{"type":"string","enum":["code"]},"required":true,"name":"response_type","in":"query"},{"schema":{"type":"string"},"required":true,"name":"client_id","in":"query"},{"schema":{"type":"string"},"required":true,"name":"redirect_uri","in":"query"},{"schema":{"type":"string","description":"S256 PKCE challenge, 43-128 characters."},"required":true,"description":"S256 PKCE challenge, 43-128 characters.","name":"code_challenge","in":"query"},{"schema":{"type":"string","enum":["S256"]},"required":true,"name":"code_challenge_method","in":"query"},{"schema":{"type":"string","enum":["read"]},"required":false,"name":"scope","in":"query"},{"schema":{"type":"string"},"required":false,"name":"state","in":"query"},{"schema":{"type":"string","description":"RFC 8707; repeatable. Hermes and Nereus resource URIs are accepted."},"required":false,"description":"RFC 8707; repeatable. Hermes and Nereus resource URIs are accepted.","name":"resource","in":"query"}],"responses":{"200":{"description":"The consent page (HTML).","content":{"text/html":{"schema":{"type":"string"}}}},"302":{"description":"An error redirected to the client (`error`, `error_description`, `state`, `iss`)."},"400":{"description":"The client or redirect URI is not valid; not redirected."}},"security":[]}},"/oauth/authorize/complete":{"post":{"tags":["OAuth"],"summary":"Complete an authorize request with the user’s Entra access token (called by the consent page)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeCompleteRequest"}}}},"responses":{"200":{"description":"Where to send the browser next.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeCompleteResponse"}}}},"401":{"description":"`invalid_token` — the Entra token did not verify."},"403":{"description":"`access_denied` — the Microsoft account has no Octiga user."},"404":{"description":"`invalid_request` — the authorize request is unknown or expired."}},"security":[]}},"/oauth/callback":{"get":{"tags":["OAuth"],"summary":"Where Entra returns the browser after sign-in (HTML, finishes the flow)","responses":{"200":{"description":"The sign-in page (HTML).","content":{"text/html":{"schema":{"type":"string"}}}}},"security":[]}},"/oauth/jwks.json":{"get":{"tags":["OAuth"],"summary":"The access-token verification keys (JWKS)","responses":{"200":{"description":"A JSON Web Key Set with one EC P-256 signing key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Jwks"}}}}},"security":[]}},"/oauth/register":{"post":{"tags":["OAuth"],"summary":"Dynamic client registration (RFC 7591) — public clients only","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientRegistrationRequest"}}}},"responses":{"201":{"description":"The registered client (no secret is ever issued).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientRegistration"}}}},"400":{"description":"`invalid_redirect_uri` or `invalid_client_metadata`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Too many registrations from this address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}},"security":[]}},"/oauth/revoke":{"post":{"tags":["OAuth"],"summary":"Revoke a refresh token (and its family) — RFC 7009","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/RevokeRequest"}}}},"responses":{"200":{"description":"Always, for any presented token."},"400":{"description":"`invalid_request` — no `token` parameter."}},"security":[]}},"/oauth/token":{"post":{"tags":["OAuth"],"summary":"Token endpoint: authorization_code (PKCE S256) and refresh_token (rotating)","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenRequest"}},"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}},"responses":{"200":{"description":"The tokens.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"description":"RFC 6749 §5.2 error (`invalid_request`, `invalid_grant`, `invalid_scope`, `invalid_target`, …).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"`invalid_client` — unknown `client_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited; see `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}},"security":[]}},"/openapi.json":{"get":{"summary":"This OpenAPI document","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 document of Hermes.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/v1/tenants":{"get":{"operationId":"listTenants","summary":"The caller’s tenants that Stolos ingests","description":"The caller’s MSP tenants from Aurora (active, within the key’s tenant scope) that have a `SUMMARY#FRESHNESS` item in the Stolos cache. Tenants Stolos does not ingest are not listed.","tags":["Tenants"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The tenants.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantList"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/exchange/{resource}":{"get":{"operationId":"exchangeGet1","summary":"Exchange / SharePoint cache","description":"A tenant-level configuration object, or the collection of a policy list / the mailboxes of the tenant. Cache only; the objects carry the cmdlet's property names, as stored. Only `$skiptoken` is honoured.","tags":["Exchange"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","description":"The kebab-case resource: organization-config, transport-config, admin-audit-log-config, transport-rules, remote-domains, dkim-signing-configs, malware-filter-policies, eop-protection-policy-rules, anti-phish-policies, hosted-content-filter-policies, hosted-outbound-spam-filter-policies, quarantine-policies, safe-attachment-policies, safe-links-policies, mobile-device-mailbox-policies, spo-tenant, mailboxes, cas-mailboxes."},"required":true,"name":"resource","in":"path"},{"schema":{"type":"string","description":"Opaque continuation token taken from `@odata.nextLink`. The only query option the cache honours; with any other option a Graph request is answered live."},"required":false,"name":"$skiptoken","in":"query"}],"responses":{"200":{"description":"A cached collection or object.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ExchangeCacheCollection"},{"$ref":"#/components/schemas/ExchangeCacheObject"}]}}}},"400":{"description":"`unsupported_query_option` (only `$skiptoken` is honoured) or `invalid_skiptoken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found, `unknown_resource` (the body lists the known names), or the object is not in the cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/exchange/{resource}/{id}":{"get":{"operationId":"exchangeGet2","summary":"Exchange / SharePoint cache","description":"One object of a collection resource. Cache only; the objects carry the cmdlet's property names, as stored. Only `$skiptoken` is honoured.","tags":["Exchange"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","description":"The kebab-case resource: organization-config, transport-config, admin-audit-log-config, transport-rules, remote-domains, dkim-signing-configs, malware-filter-policies, eop-protection-policy-rules, anti-phish-policies, hosted-content-filter-policies, hosted-outbound-spam-filter-policies, quarantine-policies, safe-attachment-policies, safe-links-policies, mobile-device-mailbox-policies, spo-tenant, mailboxes, cas-mailboxes."},"required":true,"name":"resource","in":"path"},{"schema":{"type":"string","description":"The object’s `Identity` (policies), or the user object id (mailboxes)."},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Opaque continuation token taken from `@odata.nextLink`. The only query option the cache honours; with any other option a Graph request is answered live."},"required":false,"name":"$skiptoken","in":"query"}],"responses":{"200":{"description":"A cached collection or object.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ExchangeCacheCollection"},{"$ref":"#/components/schemas/ExchangeCacheObject"}]}}}},"400":{"description":"`unsupported_query_option` (only `$skiptoken` is honoured) or `invalid_skiptoken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found, `unknown_resource` (the body lists the known names), or the object is not in the cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/exchange/{resource}/{id}/{sub}":{"get":{"operationId":"exchangeGet3","summary":"Exchange / SharePoint cache","description":"A per-mailbox sub-resource (`mailboxes/{oid}/<sub>`). Cache only; the objects carry the cmdlet's property names, as stored. Only `$skiptoken` is honoured.","tags":["Exchange"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","description":"The kebab-case resource: organization-config, transport-config, admin-audit-log-config, transport-rules, remote-domains, dkim-signing-configs, malware-filter-policies, eop-protection-policy-rules, anti-phish-policies, hosted-content-filter-policies, hosted-outbound-spam-filter-policies, quarantine-policies, safe-attachment-policies, safe-links-policies, mobile-device-mailbox-policies, spo-tenant, mailboxes, cas-mailboxes."},"required":true,"name":"resource","in":"path"},{"schema":{"type":"string","format":"uuid","description":"The user object id."},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"cas-mailbox, mailbox-settings, inbox-rules, mail-folders, mailbox-permissions, recipient-permissions."},"required":true,"name":"sub","in":"path"},{"schema":{"type":"string","description":"Opaque continuation token taken from `@odata.nextLink`. The only query option the cache honours; with any other option a Graph request is answered live."},"required":false,"name":"$skiptoken","in":"query"}],"responses":{"200":{"description":"A cached collection or object.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ExchangeCacheCollection"},{"$ref":"#/components/schemas/ExchangeCacheObject"}]}}}},"400":{"description":"`unsupported_query_option` (only `$skiptoken` is honoured) or `invalid_skiptoken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found, `unknown_resource` (the body lists the known names), or the object is not in the cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/exchange/{resource}/{id}/{sub}/{subId}":{"get":{"operationId":"exchangeGet4","summary":"Exchange / SharePoint cache","description":"One item of a per-mailbox collection sub-resource. Cache only; the objects carry the cmdlet's property names, as stored. Only `$skiptoken` is honoured.","tags":["Exchange"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","description":"The kebab-case resource: organization-config, transport-config, admin-audit-log-config, transport-rules, remote-domains, dkim-signing-configs, malware-filter-policies, eop-protection-policy-rules, anti-phish-policies, hosted-content-filter-policies, hosted-outbound-spam-filter-policies, quarantine-policies, safe-attachment-policies, safe-links-policies, mobile-device-mailbox-policies, spo-tenant, mailboxes, cas-mailboxes."},"required":true,"name":"resource","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string"},"required":true,"name":"sub","in":"path"},{"schema":{"type":"string","description":"The rule / folder id or the trustee."},"required":true,"name":"subId","in":"path"},{"schema":{"type":"string","description":"Opaque continuation token taken from `@odata.nextLink`. The only query option the cache honours; with any other option a Graph request is answered live."},"required":false,"name":"$skiptoken","in":"query"}],"responses":{"200":{"description":"A cached collection or object.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ExchangeCacheCollection"},{"$ref":"#/components/schemas/ExchangeCacheObject"}]}}}},"400":{"description":"`unsupported_query_option` (only `$skiptoken` is honoured) or `invalid_skiptoken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found, `unknown_resource` (the body lists the known names), or the object is not in the cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/graph/{version}/{graphPath}":{"get":{"operationId":"graphGet","summary":"Microsoft Graph (cache first, live otherwise)","description":"Read-only Microsoft Graph emulation. `graphPath` is any Graph path and may contain slashes (`users`, `users/{id}/mailFolders`, `identity/conditionalAccess/policies`).\n\nA path the route table holds, with no query option or `$skiptoken` alone, is answered from the Stolos cache: collections are `{ @odata.context, value, @odata.nextLink?, @octiga.* }`, a single object carries its own properties plus `@octiga.*`, a missing object is Graph’s `404 Request_ResourceNotFound`.\nAny other query option, or a path the table does not hold, is proxied to Microsoft Graph (GET only) on the allow-list; a denied path is `403 path_not_allowed`, the live budget is `429 live_graph_rate_limited`, and Microsoft’s own errors (including `429` with `Retry-After`) pass through unchanged.\n`@octiga.graphVersion` is the version asked for; the cache serves its one projection for both.","tags":["Graph"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","enum":["v1.0","beta"],"description":"The Graph version. The cache holds ONE (beta-shaped) projection per class and serves it for both versions; `@octiga.graphVersion` echoes the version asked for."},"required":true,"name":"version","in":"path"},{"schema":{"type":"string","description":"The Graph path after the version, e.g. `users/<id>/authentication/methods`."},"required":true,"name":"graphPath","in":"path"},{"schema":{"type":"string","description":"Opaque continuation token taken from `@odata.nextLink`. The only query option the cache honours; with any other option a Graph request is answered live."},"required":false,"name":"$skiptoken","in":"query"},{"schema":{"type":"string","description":"Forwarded to Microsoft on a live call (`eventual` for advanced queries)."},"required":false,"name":"ConsistencyLevel","in":"header"}],"responses":{"200":{"description":"A cached collection or object, or a live Graph response.","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/GraphCacheCollection"},{"$ref":"#/components/schemas/GraphCacheObject"},{"$ref":"#/components/schemas/GraphLiveResponse"}]}}}},"400":{"description":"Malformed path, `invalid_skiptoken`, or `invalid_request`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`path_not_allowed` — the live proxy refuses this path.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found (`not_found`), unknown Graph version, or a cached object that does not exist (Graph error shape).","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/Problem"},{"$ref":"#/components/schemas/GraphError"}]}}}},"429":{"description":"`live_graph_rate_limited`, or Microsoft’s own throttle (with `Retry-After`).","content":{"application/json":{"schema":{}}}},"502":{"description":"The cache or Microsoft could not be reached (`stolos_read_failed`, `live_graph_*`), or Microsoft’s own 5xx.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled` / `live_graph_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"504":{"description":"`live_graph_timeout`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/risks":{"get":{"operationId":"listRisks","summary":"Account risks, worst first","description":"The tenant’s `USER#RISK` items ordered by rank (critical first), 100 per page; follow `@odata.nextLink`.","tags":["Risks"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","description":"Opaque continuation token taken from `@odata.nextLink`. The only query option the cache honours; with any other option a Graph request is answered live."},"required":false,"name":"$skiptoken","in":"query"}],"responses":{"200":{"description":"A page of risks.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountRisks"}}}},"400":{"description":"`unsupported_query_option` or `invalid_skiptoken`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/v1/tenants/{tenantId}/users/{id}/profile":{"get":{"operationId":"getUserProfile","summary":"A user’s profile","description":"Identity, authentication methods, mailbox settings, inbox rules, account risk and group memberships of one user, from the Stolos cache. Parts that are not cached are `null` / `[]`.","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"The tenant (the Entra directory id). A tenant outside the caller’s scope or MSP is `404`."},"required":true,"name":"tenantId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"The user’s object id."},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"400":{"description":"`invalid_request` — the user id is not a GUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Tenant not found, or `user_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"`stolos_read_failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`stolos_unavailable` / `stolos_throttled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"parameters":{},"schemas":{"AccountRisks":{"type":"object","properties":{"value":{"type":"array","items":{"type":"object","additionalProperties":{}},"description":"One element per user, worst first: `userId`/`oid`, `rank`, `worstLevel`, `activeRules`, `activeLevels`, `states`, `outcomes`, `profile`, `coverageSummary`, `evaluatedAt`, …"},"@odata.nextLink":{"type":"string"},"@octiga.source":{"type":"string","enum":["cache"],"description":"The answer came from the Stolos cache."},"@octiga.projectionVersion":{"type":["integer","null"],"description":"The highest `projectionVersion` among the items that built the answer; `null` for an empty page."},"@octiga.tenantId":{"type":"string","format":"uuid"},"@octiga.syncedAt":{"$ref":"#/components/schemas/SyncedAtRange"}},"required":["value","@octiga.source","@octiga.projectionVersion","@octiga.tenantId","@octiga.syncedAt"]},"AuthorizeCompleteRequest":{"type":"object","properties":{"request_id":{"type":"string"},"entra_token":{"type":"string"}},"required":["request_id","entra_token"]},"AuthorizeCompleteResponse":{"type":"object","properties":{"redirect_to":{"type":"string","format":"uri"}},"required":["redirect_to"]},"ClientRegistration":{"type":"object","properties":{"client_id":{"type":"string"},"client_id_issued_at":{"type":"integer"},"redirect_uris":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string","enum":["none"]},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"scope":{"type":"string"},"client_name":{"type":"string"}},"required":["client_id","client_id_issued_at","redirect_uris","token_endpoint_auth_method","grant_types","response_types","scope"]},"ClientRegistrationRequest":{"type":"object","properties":{"redirect_uris":{"type":"array","items":{"type":"string"},"minItems":1,"description":"https URLs, or http://localhost / 127.0.0.1 / [::1] loopback URLs; no fragment."},"client_name":{"type":"string"},"token_endpoint_auth_method":{"type":"string","enum":["none"]},"grant_types":{"type":"array","items":{"type":"string","enum":["authorization_code","refresh_token"]}},"response_types":{"type":"array","items":{"type":"string","enum":["code"]}},"scope":{"type":"string","enum":["read"]},"client_uri":{"type":"string","format":"uri"},"logo_uri":{"type":"string","format":"uri"},"software_id":{"type":"string"},"software_version":{"type":"string"}},"required":["redirect_uris"]},"ExchangeCacheCollection":{"type":"object","properties":{"value":{"type":"array","items":{"type":"object","additionalProperties":{}},"description":"Cmdlet objects with their own property names (`Identity`, `RecipientTypeDetails`, …)."},"@odata.nextLink":{"type":"string"},"@octiga.source":{"type":"string","enum":["cache"],"description":"The answer came from the Stolos cache."},"@octiga.projectionVersion":{"type":["integer","null"],"description":"The highest `projectionVersion` among the items that built the answer; `null` for an empty page."},"@octiga.tenantId":{"type":"string","format":"uuid"},"@octiga.syncedAt":{"$ref":"#/components/schemas/SyncedAtRange"}},"required":["value","@octiga.source","@octiga.projectionVersion","@octiga.tenantId","@octiga.syncedAt"]},"ExchangeCacheObject":{"type":"object","properties":{"@octiga.source":{"type":"string","enum":["cache"],"description":"The answer came from the Stolos cache."},"@octiga.projectionVersion":{"type":["integer","null"],"description":"The highest `projectionVersion` among the items that built the answer; `null` for an empty page."},"@octiga.tenantId":{"type":"string","format":"uuid"},"@octiga.syncedAt":{"type":["string","null"]}},"required":["@octiga.source","@octiga.projectionVersion","@octiga.tenantId","@octiga.syncedAt"],"additionalProperties":{}},"GraphCacheCollection":{"type":"object","properties":{"@odata.context":{"type":"string"},"value":{"type":"array","items":{"type":"object","additionalProperties":{}},"description":"Microsoft Graph objects as projected by Stolos."},"@odata.nextLink":{"type":"string","description":"Present when more pages exist; follow it as is (a Hermes `$skiptoken`)."},"@octiga.source":{"type":"string","enum":["cache"],"description":"The answer came from the Stolos cache."},"@octiga.projectionVersion":{"type":["integer","null"],"description":"The highest `projectionVersion` among the items that built the answer; `null` for an empty page."},"@octiga.tenantId":{"type":"string","format":"uuid"},"@octiga.syncedAt":{"$ref":"#/components/schemas/SyncedAtRange"},"@octiga.graphVersion":{"type":"string","enum":["v1.0","beta"]},"@octiga.view":{"type":"string","enum":["detail","summary"],"description":"Only `/users/{id}/authentication/methods`: whether the per-user detail has been read."}},"required":["@odata.context","value","@octiga.source","@octiga.projectionVersion","@octiga.tenantId","@octiga.syncedAt","@octiga.graphVersion"]},"GraphCacheObject":{"type":"object","properties":{"@odata.context":{"type":"string"},"@octiga.source":{"type":"string","enum":["cache"],"description":"The answer came from the Stolos cache."},"@octiga.projectionVersion":{"type":["integer","null"],"description":"The highest `projectionVersion` among the items that built the answer; `null` for an empty page."},"@octiga.tenantId":{"type":"string","format":"uuid"},"@octiga.syncedAt":{"type":["string","null"],"description":"ISO-8601 UTC time the object was last written to the cache (the oldest, for an object composed from several items)."},"@octiga.graphVersion":{"type":"string","enum":["v1.0","beta"]},"@octiga.view":{"type":"string","enum":["detail","summary"]}},"required":["@odata.context","@octiga.source","@octiga.projectionVersion","@octiga.tenantId","@octiga.syncedAt","@octiga.graphVersion"],"additionalProperties":{}},"GraphError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"Request_ResourceNotFound"},"message":{"type":"string"}},"required":["code","message"]},"@octiga.source":{"type":"string","enum":["cache"]}},"required":["error"]},"GraphLiveResponse":{"type":"object","properties":{"@octiga.source":{"type":"string","enum":["live"]},"@octiga.graphVersion":{"type":"string","enum":["v1.0","beta"]},"@octiga.tenantId":{"type":"string","format":"uuid"}},"required":["@octiga.source","@octiga.graphVersion","@octiga.tenantId"],"additionalProperties":{},"description":"Microsoft Graph’s response with `@odata.nextLink` / `@odata.deltaLink` / `@odata.context` rewritten to Hermes; no `syncedAt` or `projectionVersion`."},"Jwks":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","additionalProperties":{}}}},"required":["keys"]},"OAuthError":{"type":"object","properties":{"error":{"type":"string"},"error_description":{"type":"string"}},"required":["error","error_description"]},"OAuthMetadata":{"type":"object","additionalProperties":{},"description":"A JSON metadata document (RFC 8414 / RFC 9728)."},"Problem":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string"},"details":{}},"required":["code","message"]}},"required":["error"]},"RevokeRequest":{"type":"object","properties":{"token":{"type":"string"},"token_type_hint":{"type":"string","enum":["refresh_token","access_token"]},"client_id":{"type":"string"}},"required":["token"]},"SyncedAtRange":{"type":"object","properties":{"oldest":{"type":["string","null"],"description":"The earliest `syncedAt` among the page’s items (ISO-8601 UTC); `null` for an empty page."},"newest":{"type":["string","null"],"description":"The latest `syncedAt` among the page’s items; `null` for an empty page."}},"required":["oldest","newest"]},"TenantList":{"type":"object","properties":{"value":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The tenant id — use it as `:tenantId` of every tenant route."},"name":{"type":"string"},"engine":{"type":"string","description":"`tenant.data_engine` (`v1 | testing | migrating | v2`); informational — Hermes serves any tenant Stolos ingests."},"stolos":{"type":"object","properties":{"ingested":{"type":"boolean","enum":[true],"description":"Always `true`: only tenants whose `SUMMARY#FRESHNESS` item exists are listed."},"freshness":{"type":"object","properties":{"state":{"type":"string","enum":["fresh","stale","degraded","unlicensed"],"description":"The roll-up freshness state."},"refreshMode":{"type":"string"},"backfilled":{"type":"boolean"},"backfilledAt":{"type":"string"},"origin":{"type":"string"},"syncedAt":{"type":"string","description":"When the freshness item was last written."},"health":{"type":"object","additionalProperties":{"anyOf":[{"type":"boolean"},{"type":"string"}]}},"resources":{"type":"object","additionalProperties":{"type":"object","properties":{"state":{"type":"string"},"syncedAt":{"type":"string"},"nextDueAt":{"type":"string"},"lastError":{"type":"object","additionalProperties":{}}},"required":["state"]}}},"required":["state","backfilled","health","resources"],"description":"The informative subset of the tenant’s `SUMMARY#FRESHNESS` item."}},"required":["ingested","freshness"]}},"required":["id","name","engine","stolos"]}},"@octiga.source":{"type":"string","enum":["cache"]},"@octiga.tenantId":{"type":"null"}},"required":["value","@octiga.source","@octiga.tenantId"]},"TokenRequest":{"type":"object","properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"code":{"type":"string"},"redirect_uri":{"type":"string"},"code_verifier":{"type":"string"},"refresh_token":{"type":"string"},"scope":{"type":"string"},"resource":{"type":"string","description":"RFC 8707; must be a subset of the grant’s resources."}},"required":["grant_type","client_id"]},"TokenResponse":{"type":"object","properties":{"access_token":{"type":"string","description":"An ES256 JWT, one hour."},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"number","enum":[3600]},"refresh_token":{"type":"string"},"scope":{"type":"string"}},"required":["access_token","token_type","expires_in","scope"]},"UserProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"identity":{"type":"object","additionalProperties":{},"description":"The Graph user object (`USER#<oid>` data)."},"authenticationMethods":{"type":["object","null"],"additionalProperties":{},"description":"`USER#<oid>#AUTHMETHODS` data: registration summary and, once read, the `methods` detail."},"mailboxSettings":{"type":["object","null"],"additionalProperties":{}},"inboxRules":{"type":"array","items":{"type":"object","additionalProperties":{}}},"risk":{"type":["object","null"],"additionalProperties":{},"description":"The stored `USER#<oid>#RISK` fields: `outcomes`, `worstLevel`, `activeRules`, `states`, `profile`, `rank`, …"},"memberOf":{"type":"array","items":{"type":"string"},"description":"Ids of the groups the user is a cached member of."},"@octiga.source":{"type":"string","enum":["cache"]},"@octiga.syncedAt":{"type":"object","properties":{"identity":{"type":["string","null"]},"authenticationMethods":{"type":["string","null"]},"mailboxSettings":{"type":["string","null"]},"inboxRules":{"type":["string","null"],"description":"The oldest rule’s `syncedAt`."},"risk":{"type":["string","null"]}},"required":["identity","authenticationMethods","mailboxSettings","inboxRules","risk"]},"@octiga.tenantId":{"type":"string","format":"uuid"}},"required":["id","identity","authenticationMethods","mailboxSettings","inboxRules","risk","memberOf","@octiga.source","@octiga.syncedAt","@octiga.tenantId"]}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key (`oct_<env>_…`) or a Hermes-issued OAuth 2.1 access token."}}},"webhooks":{}}