Connect a confidential partner backend and provision one protected gateway for each external key.
Use this guide to implement experiences such as Protect with AIRCTRL. The partner
registers one App, then each customer connects it once to the AIRCTRL organization they
choose. Every isolated customer installation can protect any number of external keys without receiving
a different Client ID or secret.The contract is provider-neutral and applies to every provider registered and enabled in
AIRCTRL.
This is a confidential-backend integration. Never request an AIRCTRL PAT or Service Account token,
and never send the OAuth client secret, provider key, access token, refresh token, or runtime gateway
token to browser storage, URLs, analytics, or logs.
The first successful protection creates one internal Service Account and one dedicated project for the
OAuth installation. The user who approved the connection is added to that project as ProjectAdmin.
Every protected external key then receives its own:
encrypted provider credential;
independently configured gateway and model bindings; and
runtime gateway token limited to the data-plane run scope.
Disconnecting revokes OAuth/runtime access and disables the internal Service Account. It does not
delete projects, credentials, gateways, settings, request logs, or audit history.
An organization member with oauth-clients:create creates an OAuth App under
Settings → Integrations → My OAuth Apps. Organization owners receive that permission by default.
Enable Allow other AIRCTRL organizations to install this app for a public App. The App definition,
Client ID, secret, redirects, and publisher controls remain in the creator’s organization; every
installation and resource created with its tokens belongs to the customer organization selected at
consent. Leave it off for a private App that can connect only to its creator organization.
The creator provides the partner developer with:
the Client ID;
the one-time Client secret; and
the exact HTTPS redirect URI registered for the partner backend.
Register these scopes:
protections:provision — Resolve providers, validate a provider key, discover models,
run preflight, and provision resources atomically.
protections:read — Read only protections created by this OAuth connection.
protections:update — Update only protections created by this OAuth connection.
protections:manage — Revoke, reactivate, or rotate credentials only for protections created by
this OAuth connection.
protections:read-logs — Read request metadata only for protection runtime tokens created by
this OAuth installation. Request and response bodies are never exposed through OAuth.
protections:read-spend — Read token usage and monetary spend only for protections created by
this OAuth installation.
offline_access — Renew access automatically with rotating refresh tokens. The integration must be
authorized again after 90 days without a successful refresh.
Enable all seven scopes for the complete protection lifecycle and observability. A deliberately limited integration may
enable a subset, but its authorization request must match that subset exactly. OAuth rejects the
complete authorization request when it contains even one permission the App does not allow.
protections:provision is the required minimum and the UI keeps it enabled; offline_access cannot
replace it.Permission changes are least-privilege and fail closed. Removing a permission narrows every existing
installation immediately and revokes current access tokens. Removing offline_access also revokes
refresh-token families. Adding a permission never upgrades an existing installation silently: the user
must authorize the connection again.Redirect URIs use exact HTTPS matching. Wildcards, fragments, and runtime-selected return URLs are not
accepted.The redirect URI is owned by the partner backend, not by AIRCTRL. Production integrations
normally use a route such as https://partner.example.com/oauth/callback. A local reference app may
register an HTTPS tunnel ending in /callback, provided the authorization request uses that exact same
value.
Open Discover the authorization server
in the API Reference. Its Mintlify base-URL selector switches the request URL and generated examples
between Production and QA from the OpenAPI servers contract. Local development values are listed in
Environments.Never infer one environment from credentials issued by another.AIRCTRL uses OAuth 2.0 Authorization Code with PKCE S256 for confidential web clients. It is not an
OpenID Connect login provider and intentionally does not expose ID Tokens or UserInfo.Use OAuth protocol for complete discovery, authorization, callback, token,
refresh, revocation, success, and error payloads for production, QA, and local environments.
Use @airctrl/sdk when the confidential partner backend runs Node or TypeScript. It provides typed
helpers for PKCE, token exchange and rotation, provider/model discovery, preflight, provisioning,
resource reads, updates, and revocation.
Use any standards-compliant OAuth and HTTP client in another language. No AIRCTRL package
is required. Read the machine contract from
https://docs.airctrl.dev/api-reference/openapi.json and the authorization-server endpoints from the
discovery URL above.The examples below use the SDK for brevity. Direct HTTP implementation
maps the same flow to raw requests.For every request and response payload, lifecycle endpoint, and multi-key mapping, see
OAuth protection API.
For a public App, AIRCTRL lists only organizations where the approving person is an active
member, holds oauth-installations:connect, and has active product access. If more than one qualifies,
the consent screen requires an explicit organization selection. A private App shows only its creator
organization. Active subscription, trial, and payment-retry (past_due) states qualify.The selected organization becomes the permanent tenant boundary for that installation. Its plan,
quotas, billing, project, Service Account, credentials, gateways, tokens, logs, and spend are used;
the publisher organization does not receive access to unrelated customer resources.The confidential partner backend uses the same authorization, token, protection, logs, and spend
operations for private and public Apps. Distribution changes only which organization may be selected
during consent and therefore which organization becomes the installation boundary.
The external platform already knows the selected key’s stable ID, display name, and secret. These
values come from its backend—not from AIRCTRL UI fields.
Preflight performs no writes and receives no provider key. Use it before provisioning so subscription,
entitlement, and quota failures appear before the partner sends a provider key.
AIRCTRL uses the OAuth-only /v1/oauth/protections/providers/{providerId}/models operation.
It uses the key only for upstream validation and live model discovery at this stage; it does not
persist it. The confidential partner backend chooses the gateway name and allowed model IDs that it
sends in the provisioning command. A partner may offer an optional review screen, but AIRCTRL
does not require another user step. Names, models, limits, logging, guardrails, and entitled OTel settings
can be changed later through the installation-bound update operation.
externalResourceId must be the stable, non-secret key ID from the partner platform. Generate one
idempotency key per protection attempt and persist it until the final outcome is known.
Provisioning is all-or-nothing. AIRCTRL validates the key and selected models again before writing,
encrypts the provider key at rest, and never returns it. A successful response includes the runtime
gateway token plaintext once:
For a one-click partner experience initiated from Protect withAIRCTRL, keep that one stable external
key ID in the partner’s server-side OAuth state. After consent, run provider resolution, preflight, model
discovery and this provisioning command only for that pending key. Store the resulting protectionId
beside it; the user does not repeat these inputs in AIRCTRL. A connection-only flow must stop
after token exchange and list existing protections—it must not call POST /v1/oauth/protections without
an explicit pending protection intent.Retry an uncertain result with the same idempotency key and identical body. Reusing that key with a
different body returns 409.
Use the returned gateway slug and runtime token from the partner backend. Do not append the provider’s
native_base_path to a gateway URL: that field describes the provider upstream and is not an
AIRCTRL data-plane route. Use the SDK’s shared connection builder so the environment and
encoded gateway slug follow the same contract as the Web app and MCP:
Use compatBaseUrl for the compatibility surface. For a provider-native operation, select the typed
base returned by buildGatewayConnectionConfig() for that provider. See
Gateway connection URLs and Connect an AI client.The OAuth installation may later list its own protected resources or update the gateway name, model
bindings, and allowlisted settings:
Changing gatewayName also changes the derived gateway slug. Use updatedProtection.gateway.slug to
rebuild connection URLs before sending more traffic.It cannot read or modify gateways created outside its own OAuth installation.With the matching scopes, read request metadata and spend for one protection without OTel:
These endpoints include only traffic attributed to runtime tokens owned by this OAuth installation’s
internal Service Account, including rotated replacements. Customer-created tokens on the same gateway
are excluded. Log cost is null unless the token also has protections:read-spend.
The behavior is identical for a private same-organization installation and a public installation bound
to the customer organization selected during consent.
AIRCTRL builds one canonical request-log event and exposes the same fields through its
internal logs and OpenTelemetry export; only the transport envelope differs. This prevents internal
storage and exported telemetry from disagreeing about identity, provider, model, operation, status,
errors, tokens, cost, bytes, latency, streaming, guardrails, trace ID, or span ID.OAuth access tokens can poll protection-scoped request metadata with protections:read-logs and totals
with protections:read-spend. They cannot read stored request or response bodies. Authorized
AIRCTRL users retain the broader human observability surfaces.OTel remains optional for real-time delivery to customer-controlled infrastructure. It is not required
to read the OAuth log or spend endpoints.OTel is subject to the organization’s effective feature entitlement. Call preflight with otel=true
before offering it. otel_not_available blocks only the requested OTel configuration, not a protection
that omits OTel. Request/response bodies can be exported only when gateway logging and body capture are
both enabled; disabling either automatically disables OTel body export.
The REST path follows the same order as the SDK path:
Read /.well-known/oauth-authorization-server and redirect the browser to its
authorization_endpoint with response_type=code, exact redirect_uri, registered scopes,
high-entropy state, and PKCE S256.
Exchange the callback code at POST /v1/oauth/token using HTTP Basic client authentication and a
form-encoded body.
Call GET /v1/oauth/protections/providers with the OAuth bearer and resolve the provider
by slug.
Call GET /v1/oauth/protections/preflight?providerId={providerId} before sending the key.
Call POST /v1/oauth/protections/providers/{providerId}/models with { "apiKey": ... }
from the confidential backend.
Call POST /v1/oauth/protections with the OAuth bearer, JSON body, and a stable
Idempotency-Key header.
Store the returned protectionId next to the external key ID. Use GET and
PATCH /v1/oauth/protections/{protectionId} only for that protection.
Use the protection lifecycle endpoints to revoke, reactivate, or rotate only that external key.
Runtime-token rotation and reactivation require a stable Idempotency-Key.
Poll GET /v1/oauth/protections/{protectionId}/logs and
GET /v1/oauth/protections/{protectionId}/spend only when the App and installation grant the
corresponding scopes.
Rotate at POST /v1/oauth/token with grant_type=refresh_token; revoke through
POST /v1/oauth/revoke.
GET /v1/oauth/protectionsGET /v1/oauth/protections/{protectionId}PATCH /v1/oauth/protections/{protectionId}GET /v1/oauth/protections/{protectionId}/logsGET /v1/oauth/protections/{protectionId}/spendPOST /v1/oauth/protections/{protectionId}/rotate-provider-keyPOST /v1/oauth/protections/{protectionId}/rotate-runtime-tokenPOST /v1/oauth/protections/{protectionId}/revokePOST /v1/oauth/protections/{protectionId}/reactivate
Runtime-token rotation and reactivation require Idempotency-Key. Request/response examples for every
operation are in OAuth protection API. Do not put the provider key or
OAuth bearer in a URL, browser form, shell history, or committed file.
Access tokens expire after 15 minutes. With offline_access, use the latest stored refresh token and replace
it after every successful refresh. Each successful refresh renews the 90-day inactivity window; replaying an
older token revokes the token family.Serialize refreshes per AIRCTRL installation. If two workers use the same refresh token concurrently,
AIRCTRL treats the second use as replay and revokes the complete token family. Use a distributed lock or
an atomic compare-and-swap in the encrypted token store. When refresh returns invalid_grant, stop retrying
and start authorization again.
RFC 7009 token revocation ends that OAuth token or refresh family; it does not delete AIRCTRL resources.
When an AIRCTRL user disconnects the installation from Settings → Integrations → Connected apps,
AIRCTRL additionally disables runtime gateway tokens and the installation’s internal Service Account
without deleting resources or audit history. Reauthorizing the same OAuth App for the same AIRCTRL
organization reuses that installation, its dedicated project, Service Account, provider credentials,
gateways, and stable protection IDs. The partner should list the existing protections and call
reactivate only for those it wants to restore. Each reactivation rechecks current entitlements and
returns a new runtime gateway token once; it does not create replacement resources. Connect and
disconnect events remain available in the connection history.Each connection-history event stores the exact scope snapshot granted at that moment. Reauthorization
replaces the installation’s current scopes with the newly approved set; the historical snapshots remain
unchanged for audit comparison.Protection list/read responses expose state as active, connection_suspended, or revoked.
After reauthorization, automate restoration only for connection_suspended protections selected by
the partner’s current intent. Never automatically restore revoked protections. The same OAuth access
token may reactivate several existing protections and provision additional external keys; each command
is independently authorized and idempotent.
oauth_redirect_uri_invalid — The callback does not exactly match registration. Use the
registered HTTPS URI verbatim.
oauth_state_invalid / oauth_state_mismatch — State is missing, malformed, or different from
the stored transaction. Abort and start a new authorization.
oauth_authorization_request_expired — The pending consent request expired or was already
decided. Start authorization again; do not reuse it.
oauth_scope_insufficient — The token lacks the required delegated scope. Reconnect requesting
only registered required scopes.
oauth_access_not_allowed — The endpoint is not part of the OAuth surface. Do not substitute a
PAT; use documented OAuth operations.
subscription_inactive — The owning organization no longer has active product access. Active
subscription, trial, and payment-retry (past_due) states qualify; other states must return to
Billing before integration continues.
provider_key_invalid — The provider rejected the supplied key. Ask the user to repair the key
in the external platform.
provider_model_not_available / provider_model_not_supported — A requested model is absent
from the live catalog or AIRCTRL cannot safely route it. Refresh model discovery and let
the user choose only supported IDs.
provider_endpoint_not_configured / provider_validation_unavailable — Provider discovery or
validation is temporarily unavailable. Do not persist the key; retry with bounded backoff.
Preflight blockers — Subscription, entitlement, or quota prevents protection. Show blockers
before sending the provider key.
idempotency_key_invalid — The key is shorter than 16 characters or longer than 200. Generate a
new stable operation key before sending the write.
idempotency_key_reused / idempotency_operation_in_progress — The key was reused with a
different body or the original transaction has not committed. Never change the body; reconcile or
retry the identical request.
idempotency_operation_superseded — A newer runtime-token operation already replaced the
result of the retried lifecycle command. Keep the newest successful token and do not repeat the old
operation with a new key.
oauth_external_resource_already_protected — This connection already mapped the same stable
external resource ID. Read the existing protections instead of creating a duplicate.
oauth_gateway_protection_revoked / oauth_gateway_protection_connection_suspended — The
protection cannot be updated or rotated in its current state. Reauthorize the connection when
suspended, then call reactivate deliberately.
oauth_project_unavailable / oauth_service_account_inactive /
oauth_service_account_unrecoverable — The preserved internal project or Service Account cannot
currently serve the connection. Stop automation and require an authorized AIRCTRL user
to review the installation.
otel_endpoint_required — The protection has no OTel configuration yet. Include endpoint the
first time; later PATCH requests may omit it.
otel_headers_invalid — The request exceeded 32 headers, used an invalid/forbidden name, or a
value exceeded 2,048 characters. Correct the server-side OTel configuration.
otel_not_available — Effective account entitlements do not enable OTel. Continue without OTel
or let an authorized user review internal logs; do not mention internal entitlement grants.
log_bodies_require_logging / otel_bodies_require_gateway_log_bodies — Enable gateway logging
and body capture before requesting body export.
429, 500, 502, 503 — Temporary capacity or upstream failure. Retry with bounded backoff
and the same idempotency key when provisioning.