> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cessy.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cessy.ai/_mcp/server.

# Adapters

> Use Cessy adapters for controlled outbound service calls from policies, workflows, and custom page support code.

Adapters define how Cessy calls external services. They keep endpoint, auth, credential, and request behavior out of ad hoc policy code.

## Use adapters for

* calling external APIs after modeled events
* sending data to integration services
* fetching external state needed by controlled workflows
* centralizing credentials and auth behavior
* making policy side effects inspectable

## Do not use adapters for

* public app API contracts
* bypassing commands
* hiding business decisions outside the event model
* arbitrary frontend calls with user-provided secrets

External consumers should call generated runtime APIs, webhooks, subscriptions, or a BFF.

## Auth and credentials

Store credentials in configured credential surfaces, not in page bundles, docs, prompts, or custom code strings. Name the target environment when testing adapter behavior.

Policy code calls adapters through `ctx.adapters`. Cessy resolves and injects adapter authentication server-side, so Workspace E2E tests do not require injecting a public runtime API key into the policy worker just to call an adapter. Named Credential Store records used by `session_token` and Custom Page principal assertions are selected for the active Production or Workspace runtime source; static adapter secrets are not source-scoped.

Static `bearer`, `basic`, `api-key`, and `oauth_client_credentials` secrets are encrypted before persistence on the server-side adapter record. They are not embedded in browser/page code or returned by adapter read surfaces. These auth types do not automatically bind named Credential Store records: creating `LIGHTRAG_API_KEY`, for example, does not attach it to an `api-key` adapter. Use `update_adapter` to rotate the protected adapter secret.

Use `inspect_adapter_credentials` to verify provenance. Its `schemaVersion: 2` receipt reports static auth as `storage: "protected_adapter_secret"` and `sourceScoped: false`, which distinguishes protected server-side adapter storage from plaintext inline configuration, and returns no secret fields. Malformed stored auth reports `storage: "unknown"` instead of claiming unverified protection. For `session_token`, the tool shows which named, source-scoped Credential Store records the adapter would use in Production or a Workspace. That receipt contains names, resolution status, selected source scopes, update times, and opaque encrypted-revision fingerprints only. It never returns credential values or encrypted envelopes. Authorization-code OAuth reports `oauth_connection`: provider access tokens live on the connection record, while the adapter client secret remains encrypted at rest on the server-side adapter record.

## Signed Custom Page principals

An exposed named operation can authenticate the current Custom Page principal to its downstream service without trusting browser input:

```json
{
  "exposure": {
    "customPage": {
      "enabled": true,
      "authRules": ["'knowledge-user' in $principal.roles"],
      "principalAssertion": {
        "audience": "knowledge-adapter",
        "credentialName": "KNOWLEDGE_PRINCIPAL_ASSERTION_SECRET",
        "keyId": "current"
      }
    }
  }
}
```

Those three values are the complete builder contract. Cessy owns the fixed `X-CES-Principal-Assertion` header and mints a new compact JWT for every actual outbound attempt after authenticating the Page principal and authorizing the operation. The protected header is exactly `alg: HS256`, `typ: ces-principal-assertion+jwt`, and the configured `kid`. The token expires after 60 seconds, is limited to 8 KiB, and binds `sub` to the server principal plus the deployment issuer, audience, tenant, app, runtime source, adapter, and operation. Browser identity, roles, attributes, groups, tags, request data, and other PII are not claims.

The header is server-owned. Caller headers, adapter defaults, auth-header configuration, raw adapter calls, Runtime MCP, policies, and other surfaces cannot create or override it in any capitalization. A signing or credential error stops before downstream I/O, redirect handling cannot forward it to another origin, and Cessy masks the assertion from captures, diagnostics, and the browser response.

The verifier must require TLS and fail closed: accept exactly one bounded header, require the fixed algorithm and type, select only an expected `kid`, and verify signature, issuer, exact audience, `iat`, `exp`, `jti`, version, and all signed request-context claims. Use only the verified `sub` as identity. Never fall back to a `principalId`, group, role, or tag from browser-controlled body, path, query, or headers. Missing, malformed, expired, duplicated, oversized, or wrongly scoped assertions deny the request.

HS256 makes every holder of the shared secret a co-signer. Use at least 32 random bytes, isolate one key per adapter trust domain/audience and environment, and provision it only to Cessy and that verifier. `keyId` is a non-secret rotation label, not a key or permission. Never reuse the signing key as an adapter API key or Runtime API key.

Multiple adapter `authRules` are OR-combined. If role and attribute checks must both pass, put them in one expression, for example `"'knowledge-user' in $principal.roles && $principal.attr.suspended != true"`.

## Inspect and change endpoint schemas safely

`list_adapter_endpoints` is a redacted troubleshooting summary. It tells you
whether request and response schemas exist, but it intentionally does not return
their contents. Never infer an unseen schema and replace it.

Before editing an endpoint, call `get_adapter_endpoint` with the app, adapter,
and operation IDs. The result contains the complete non-secret endpoint
definition and a deterministic `revision`. Pass that value as
`expectedRevision` to `update_adapter_endpoint`. If another writer changed the
endpoint since inspection, Cessy rejects the update without writing; inspect it
again before deciding how to proceed.

Omitted fields remain unchanged. Use `null` to remove an optional
`pathParams`, `bodySchema`, `responseSchema`, `description`, or `exposure`
field. A successful fenced update returns the exact before/after definitions and
a `restore` object. To undo the update, call `update_adapter_endpoint` again
with the restore object's `expectedRevision` and patch fields. This reversal is
also conflict-checked and recorded as an endpoint update.

Request schemas govern browser-provided business input only. Do not add a
`principal`, roles, groups, or tags to a Page request body as an authorization
workaround. For authenticated Custom Pages, identity continues to come only
from the server session and signed principal assertion described above.

If the downstream needs fine-grained tags, keep them in an app-owned `EffectiveKnowledgeAccess` projection keyed by principal ID. After verification, use only `sub` to call the existing exact Runtime projection-document endpoint with a dedicated least-privileged Runtime API key. Missing, inactive, malformed, or empty access denies; browser-provided IDs or tags are never a fallback. See [Permissions](/build-a-backend/permissions) and [Build Custom Pages](/build-a-frontend) for the pull pattern and tampering test.

## Session-token cache control

Session tokens have no independent cache TTL. A cached token remains in use until the configured expiry response triggers refresh, adapter auth or base URL changes, an operator explicitly invalidates the session, or the process exits. Runtime materialization expiry by itself does not evict the token.

Use `invalidate_adapter_session` for one adapter and one Production or Workspace source. It makes no upstream request. Cessy records a durable source generation and evicts the matching local token when the event is observed, forcing later materializations in every process onto a fresh login. A request already in flight when the receipt is issued may still finish with its old token.

## Verification

Use `test_adapter` with a named, parameterless `GET` or `HEAD` operation to verify connectivity. When no operation is supplied, Cessy prefers a safe operation named `healthCheck`, then the first safe read operation. It never selects a mutating operation or invents a request to the base URL. An explicit unknown, mutating, parameterized, risk-declared, or approval-requiring operation is rejected before authentication or adapter I/O. If no safe read operation exists, Cessy resolves credentials and authentication settings without a business request and reports connectivity as inconclusive. Only session-token authentication performs a remote login in that mode.

`sourceScope` accepts exactly `production` (the default), `workspace:<workspaceId>`, or legacy `*`. Cessy checks that a Workspace belongs to the app before login or adapter I/O. Production and Workspace tests report `sourceBinding: runtime`; this describes the selected runtime credential/source context. The probe uses the active base runtime adapter configuration, not an unpromoted Workspace draft. The `*` scope reports `sourceBinding: global_credentials_only`: it selects globally scoped credentials and is not an exact runtime binding for Production or a Workspace.

The result identifies the operation, method, redacted structural path shape, execution phase, `httpResponseReached`, elapsed time, terminal `attempts`, `retryDecision` and `refreshDecision` evidence when available, `sourceBinding`, and an opaque `runtimeFingerprint`. At result level, `httpResponseReached` is true when any login or safe-probe leg reached response headers, even if a later retry failed before its response. The fingerprint is a `sha256:` value used for equality comparisons; it does not reveal deployment metadata. Literal path segments are masked. `POST`, `PUT`, `PATCH`, and `DELETE` operations are not available through the connectivity test.

Adapter failures and non-success HTTP responses add bounded, redacted diagnostics to the policy execution. Each entry can include adapter and operation identity, phase, method and structural path shape, status, `attempts`, optional `elapsedMs`, `retryDecision`, `refreshDecision`, `httpResponseReached`, `transportCategory`, an optional allowlisted `transportCode`, runtime source, and `runtimeFingerprint`. `httpResponseReached: true` means that at least response headers arrived; `false` distinguishes a pre-HTTP transport failure. Older evidence can report this field as unknown. A receipt retains at most 16 chronological entries while preserving the terminal evidence.

The finite transport categories and allowlisted low-level codes are:

| `transportCategory`    | Meaning                                                | Allowlisted `transportCode` values                                                                                                                                                                                                                                                                                                        |
| ---------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dns_resolution`       | DNS lookup failed                                      | `ENOTFOUND`, `EAI_AGAIN`, `EAI_FAIL`, `EAI_NODATA`, `ENODATA`, `CES_DNS_RESOLUTION_FAILED`                                                                                                                                                                                                                                                |
| `tcp_refused`          | The remote endpoint refused the TCP connection         | `ECONNREFUSED`                                                                                                                                                                                                                                                                                                                            |
| `tcp_reset`            | A connection was reset or broken                       | `ECONNRESET`, `EPIPE`                                                                                                                                                                                                                                                                                                                     |
| `tcp_timeout`          | A connect, header, body, or socket timeout occurred    | `ETIMEDOUT`, `ESOCKETTIMEDOUT`, `UND_ERR_CONNECT_TIMEOUT`, `UND_ERR_HEADERS_TIMEOUT`, `UND_ERR_BODY_TIMEOUT`                                                                                                                                                                                                                              |
| `tls_handshake`        | TLS protocol or negotiation failed                     | `EPROTO`, `ERR_TLS_HANDSHAKE_TIMEOUT`, `ERR_TLS_REQUIRED_SERVER_NAME`                                                                                                                                                                                                                                                                     |
| `tls_certificate`      | Certificate validation failed                          | `DEPTH_ZERO_SELF_SIGNED_CERT`, `SELF_SIGNED_CERT_IN_CHAIN`, `UNABLE_TO_VERIFY_LEAF_SIGNATURE`, `UNABLE_TO_GET_ISSUER_CERT`, `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, `CERT_HAS_EXPIRED`, `CERT_NOT_YET_VALID`, `CERT_REVOKED`, `CERT_UNTRUSTED`, `INVALID_CA`, `ERR_TLS_CERT_ALTNAME_INVALID`, `ERR_TLS_CERT_SIGNATURE_ALGORITHM_UNSUPPORTED` |
| `egress_denied`        | Cessy's outbound policy rejected the target            | `EACCES`, `EPERM`, `CES_EGRESS_DENIED`                                                                                                                                                                                                                                                                                                    |
| `upstream_unreachable` | No route was available or low-level outcomes disagreed | `ENETUNREACH`, `EHOSTUNREACH`, `ENETDOWN`, `EHOSTDOWN`, `UND_ERR_CONNECT_ERROR`, `UND_ERR_SOCKET`                                                                                                                                                                                                                                         |
| `aborted`              | The request was aborted                                | `ABORT_ERR`, `UND_ERR_ABORTED`                                                                                                                                                                                                                                                                                                            |
| `unknown_transport`    | No safe finite classification was available            | none                                                                                                                                                                                                                                                                                                                                      |

Unrecognized codes are omitted rather than copied into evidence. Unknown `ERR_TLS_*` and `ERR_SSL_*` families may still map to `tls_handshake` without retaining the raw code. Read the recorded `retryDecision` (`not_triggered`, `retried`, or `exhausted`), `refreshDecision` (`not_applicable`, `not_triggered`, `refreshed`, or `refresh_failed`), and `retriable` flag instead of inferring retry behavior from a category.

This evidence is retained even if policy code handles the response and the policy completes successfully. Inspect the affected policy execution through `get_execution`; it validates app ownership, re-normalizes stored diagnostics at read time, and omits free-form upstream summaries plus adjacent free-form policy and correlated execution context whenever an adapter receipt is present. Share only its normalized `adapterDiagnostics` subsection as the adapter receipt. `get_error_context` can also contain raw input data, policy source, stack traces, and details, so its full output is not a safe-share receipt. Credential values, auth headers, cookies, literal path segments, hostnames, query strings, full upstream URLs, arbitrary error codes, and nested causes are not rendered in normalized adapter diagnostics.

## Safe troubleshooting workflow

Use Cessy's allowlisted diagnostic surfaces in this order:

1. Call `list_adapters` with the app ID, then `list_adapter_endpoints` with the app ID and adapter name. Use `get_adapter_endpoint` only when you need the exact authoring configuration for one operation; treat that detail as app configuration rather than a generally shareable diagnostic receipt.
2. Call `inspect_adapter_credentials` with the app ID and adapter name.
3. Call `test_adapter` with the app ID, adapter name, `sourceScope: "production"`, and an explicit safe GET/HEAD operation ID. Use `workspace:<workspaceId>` for an exact Workspace binding. Reserve legacy `*` for global-credential troubleshooting; it is not an exact runtime test.
4. For a separate policy call that exhibits the problem, call `get_execution` with the app ID and that affected execution or correlation ID. Share only the normalized `adapterDiagnostics` subsection; literal paths are shaped again at read time, and upstream summaries plus adjacent free-form policy and correlated execution context are omitted whenever an adapter receipt is present.

For a Workspace, pass `workspaceId` to `inspect_adapter_credentials` and `get_execution`, and pass `sourceScope: "workspace:<workspace-id>"` to `test_adapter`. If a policy also creates an Error Log entry, `get_error_context` can help interactive debugging, but review it as sensitive and do not share its full output.

The list tools return allowlisted summaries: authentication/default-header values, free-form descriptions, parameter names, and auth rules are omitted; URL hosts and literal path segments are masked; only scheme, non-default port, path depth, normalized `{param}` markers, and bounded exposure flags remain. Adapter and operation identifiers remain visible for correlation, so never embed credential material in an identifier. Do not replace these receipts with unformatted `docker inspect`, `docker exec ... env` or `printenv`, resolved `docker compose config`, environment-file output, raw credential rows, auth headers/cookies, or token-bearing URLs. Those outputs can put live credentials into terminals, logs, and transcripts before redaction. If that happens, rotate or revoke the exposed material immediately; for `session_token`, also invalidate the affected source-scoped session.