Skip to documentation
Rat Things
Browse documentation
Documentation/Integrations, accounts, and permissions

Integrations, accounts, and permissions#

Command convention: this guide uses the installed rat-things shorthand. From a source checkout, use npm run rat-things -- before the same arguments, or run npm run build && npm link once.

Rat Things turns trusted external APIs into agent-visible tools. One deployment can serve one person, a team, or an embedded product; every connection remains scoped to the authenticated owner. An owner can connect several accounts for the same service and grant each account different access. This is the account-connection step in the Rat Things operating model.

Built-in integrations use the same reviewed contract as services added in trusted host code:

discover integration -> supply credential -> verify provider account -> choose Rat access
                     -> configure notification bindings or declared Session tools -> launch autonomously

The Integration Contract v1#

Object Purpose Secret material?
Integration manifest Describes authentication fields and typed operations; implemented by a trusted plugin No
Connection One verified provider account for one Rat owner No
Connection health Bounded verification state and timestamps; never provider bodies No
Credential binding Host-only pointer to the encrypted credential Reference only
Grant Persistent Rat-side permission ceiling for one connection No
Connection set Reusable selection of accounts, including multiple accounts for one plugin No
Source binding / schedule Selects an Agent, environment and notification destinations No

The API accepts a credential, but it does not accept claims about which account or permissions that credential represents. The plugin verifies the credential against its fixed provider API and derives the account label, tenant/subject identifiers, provider access, and provider scopes. Only a successful verification creates the secret and connection metadata.

The credential value is never returned. It is not copied into an Agent or Session definition, DynamoDB record, MicroVM launch payload, App Server tool schema, model-visible environment variable, URL, or log.

1. Discover what the deployment supports#

Every UI, agent, and integration client starts with the same endpoint:

GET /v1/integrations/plugins

A manifest describes the exact authentication fields and operations installed in that deployment:

{
  "id": "stripe",
  "version": "1",
  "title": "Stripe",
  "authentication": [
    {
      "scheme": "api-key",
      "title": "Secret API key",
      "fields": [
        { "key": "api_key", "label": "Secret key", "secret": true }
      ]
    }
  ],
  "operations": [
    {
      "id": "stripe.customers.search",
      "kind": "search",
      "access": "read",
      "risk": "routine",
      "requiredProviderScopes": ["customers.read"]
    }
  ]
}

Consumers should render or request only the declared fields. Do not hard-code provider credential forms separately from the manifest. If a plugin offers more than one authentication scheme, the consumer asks the operator to choose one.

2. Connect and verify one account#

For the CLI, create a short-lived, owner-readable credential file containing only the fields from the selected authentication definition:

umask 077
printf '{"api_key":"replace-with-issued-key"}\n' > /secure/tmp/stripe.json

rat-things plugins
rat-things connect stripe \
  --credential-file /secure/tmp/stripe.json \
  --access read-only
rat-things connections

connect discovers the manifest, validates the exact field names, chooses the sole authentication scheme when unambiguous, and defaults the Rat grant to read-only. Use --auth-scheme oauth2 when a plugin has several schemes. --alias is optional: Rat derives a readable alias from the verified provider label and adds -2, -3, and so on when needed.

The equivalent API request is deliberately small:

{
  "version": "1",
  "pluginId": "stripe",
  "authScheme": "api-key",
  "credential": {
    "api_key": "supplied-only-to-this-management-call"
  },
  "grant": {
    "version": "1",
    "preset": "read-only"
  }
}

The authenticated principal becomes the owner; callers cannot submit ownerId. The response contains derived metadata and the initial grant, never the credential or vault reference:

{
  "connection": {
    "version": "1",
    "connectionId": "...",
    "ownerId": "api:authenticated-principal",
    "pluginId": "stripe",
    "alias": "stripe-acme-shop",
    "label": "Acme Shop",
    "externalTenantId": "acct_...",
    "authorization": {
      "scheme": "api-key",
      "access": "full",
      "scopeModel": "unknown",
      "scopes": []
    },
    "status": "active",
    "createdAt": "2026-08-22T12:00:00.000Z",
    "updatedAt": "2026-08-22T12:00:00.000Z"
  },
  "grant": {
    "version": "1",
    "grantId": "...",
    "ownerId": "api:authenticated-principal",
    "connectionId": "...",
    "preset": "read-only"
  }
}

A rejected credential returns 400 invalid_request, creates no secret, and is safe to correct and resubmit. Provider identity is established before persistence, so callers cannot label an unrelated token as another tenant or fabricate its scopes. Provider throttling, 5xx responses, and network failure return retryable 503 integration_unavailable rather than blaming the credential.

3. Inspect and maintain a connection#

The desktop Connections workspace and CLI expose the same safe management view:

rat-things connection show stripe-acme-shop
rat-things connection test stripe-acme-shop
rat-things connection consumers stripe-acme-shop
rat-things connection rename stripe-acme-shop --name "Acme billing"
rat-things connection reconnect stripe-acme-shop --credential-file /secure/tmp/stripe.json
# OAuth account:
rat-things connection reconnect slack-work --oauth --wait

show returns the verified Connection, its Rat-side grant, and bounded health metadata. A new or untested account reports unknown/not-tested. test asks the trusted host-side credential broker to verify the stored credential against the fixed plugin and returns one of:

  • healthy/verified when the same provider tenant and subject verify;
  • degraded/provider-unavailable without expiring the account when the provider is temporarily unavailable; or
  • reauth-required when the credential is missing, rejected, or resolves to another provider identity.

The test response never includes the credential, vault reference, raw provider response, or error body. Health is operational metadata stored separately from the secret. consumers derives the owner's Sessions, schedules, connection sets, and source bindings that select the account from their authoritative definitions; it does not read the credential.

The optional display name is presentation only. Renaming does not change the stable alias or ID used by notification bindings, schedules and the CLI. The desktop details view also shows provider scopes, installed operation access/risk, health, and the “used by” projection before an operator changes or disconnects an account.

The AWS reference deployment checks a rotating bounded slice of connections every 15 minutes and re-verifies only health older than 60 minutes by default. It uses a dedicated Lambda and IAM role, never an agent Run, and stores only lifecycle, status/code, and timestamps. Tune or disable it with connection_health_schedule_expression, connection_health_stale_minutes, connection_health_check_limit, connection_health_check_concurrency, and enable_connection_health_monitor.

These are authenticated control-plane operations. They are not dynamic integration tools and are never registered with Codex. A prompt, webpage, repository, or provider message therefore cannot start OAuth, test or read a credential, rename/reconnect an account, change a grant, or install a new capability. Those changes apply only through trusted UI/CLI/API management and affect later Runs after a fresh envelope is resolved.

4. Connect multiple accounts#

Run connect again for every account. Aliases are unique only within the Rat owner:

rat-things connect slack --auth-scheme oauth2 \
  --credential-file /secure/tmp/client-a.json --alias slack-client-a
rat-things connect slack --auth-scheme oauth2 \
  --credential-file /secure/tmp/client-b.json --alias slack-client-b

The accounts share a plugin implementation but never credentials or authority. An agent tool call selects the exact account alias. If only one eligible account is configured as a default, the generated tool schema can omit the account; ambiguous multi-account tools require an explicit alias.

Connection sets make a selection reusable:

rat-things connection-set --file /secure/config/customer-ops.json
{
  "version": "1",
  "name": "customer-ops",
  "connections": ["slack-client-a", "slack-client-b", "stripe-acme-shop"],
  "defaults": {
    "support": "slack-client-a",
    "billing": "stripe-acme-shop"
  }
}

Connection sets group installed accounts and defaults for the application integration layer. They are not an Agent tool declaration. Configure standard Session tools through explicit MCP or application-function definitions; their trusted implementation must apply provider scopes, account grants and operation/resource constraints before reading credentials. See Agents API.

Session provider bindings and schedules may select a connection set for notification delivery. Agent tools require their own declared configuration and attached Vaults; a notification set does not grant execution tools. See provider bindings.

5. Understand effective permission#

Effective integration operations are the intersection of provider authorization, the persistent account grant, the capability profile, and Session tool or local execution narrowing. A deny at any layer wins.
Permission is always an intersection. The resulting operation set is fixed before launch and autonomous during the Run.

Integration permissions follow the capability envelope: provider authorization, the persistent grant, the profile ceiling, and Session tool or local execution narrowing must all permit the operation. A full-access provider key can be exposed to Rat as read-only; a Rat grant cannot widen a read-only provider token.

Rat preset Eligible operation access
read-only read
read-write read, write
full read, write, full
custom IDs explicitly listed in allowOperations

Provider and Rat permission remain separate. When a provider reports granular scopes, both layers enforce them. When an API key is broad or the provider does not expose scope metadata, the connection honestly records coarse or unknown; Rat still enforces its grant, but the provider credential itself remains broad.

For tighter control on a write-only account alias, replace the persistent grant:

rat-things grant slack-customer-post --file /secure/config/slack-customer-post-grant.json
{
  "version": "1",
  "preset": "custom",
  "allowOperations": ["slack.messages.post"],
  "denyOperations": [],
  "resourceConstraints": {
    "channel": ["C01234567"]
  },
  "expiresAt": "2026-12-31T23:59:59Z"
}

resourceConstraints match operation input fields before the credential is read, and every constraint applies to every operation admitted by that grant. An operation whose input omits a constrained field is rejected. For example, do not combine slack.messages.search—whose input is only query—with the channel-constrained grant above. Use a separate read-only alias for search and a write-only, channel-constrained alias for posting.

Every exposed operation is autonomous during the Run, so omit or deny it unless its full admitted input range is safe.

6. Rotate or revoke safely#

Rotation uses the same credential-only file journey as connection setup:

rat-things rotate stripe-acme-shop --credential-file /secure/tmp/stripe-rotated.json
rat-things revoke stripe-old-account

Rat verifies a rotated credential before replacing the stored value and rejects it if its provider tenant or subject differs from the existing connection. Revocation marks the connection inactive and asks the vault to delete the credential. Existing run requests cannot bypass revocation because connection status is checked again when an agent tool session is created.

The raw rotation API request is {"version":"1","credential":{...}}.

Self-hosted OAuth installation#

Rat Things includes an authorization-code/PKCE callback and automatic refresh lifecycle in each AWS deployment. It is still bring your own OAuth application: the operator registers the provider app, accepts any provider review, chooses the installed plugin scopes, and stores the app credential in that deployment's AWS Secrets Manager. Rat never operates a central OAuth client or receives the credential.

The first apply can leave OAuth unconfigured. Read terraform output -raw oauth_callback_url, register that exact HTTPS URL with the provider, and create a secret containing:

{
  "client_id": "provider-application-id",
  "client_secret": "provider-application-secret"
}

Then pass only its ARN and apply again:

integration_oauth_app_secret_arns = {
  slack = "arn:aws:secretsmanager:us-west-2:111122223333:secret:rat/oauth/slack-AbCdEf"
}

GET /v1/integrations/plugins reports oauthInstallation.status as configured or host-required and returns the exact callback URL. Start a connection from the desktop Connections page or the CLI:

rat-things connect slack --oauth --wait --access read-write --alias slack-work
# Add --no-browser on a headless operator host and open the printed URL elsewhere.
rat-things slack-events slack-work --profile read-only --json

--wait polls only the owner-scoped connection catalog and returns the verified connection bundle after the callback succeeds. Omit it when the shell should return the expiring authorization URL immediately. The URL and callback never contain the provider application secret or issued token.

Reconnect an installed OAuth account with:

rat-things connection reconnect slack-work --oauth --wait

Reconnect state is bound server-side to the authenticated owner and existing connection ID. The callback preserves that connection's stable alias, Rat grant, schedules/source bindings, and provider scopes selected by the trusted plugin. Rat verifies the exchanged credential resolves to the exact same provider tenant and subject before replacing the old secret. Choosing a different provider account fails closed and leaves the stored credential unchanged.

The authenticated start call creates a ten-minute, owner-bound state, stores only its SHA-256 hash, and generates an S256 PKCE challenge. The public callback atomically consumes that state before code exchange, verifies the resulting provider identity through the ordinary connection service, and only then persists the credential. Provider endpoints, scopes, and token authentication method come from the reviewed compiled plugin; callers cannot substitute them.

When a provider issues expires_in and a refresh token, the credential broker refreshes two minutes before expiry behind a short per-connection DynamoDB lease. Refresh responses replace the same Secrets Manager value. Concurrent workers wait for that replacement. Terraform gives the MicroVM only the configured application-secret ARN map; the execution role may resolve only those exact secrets, and neither the application secret nor issued tokens enter the agent's prompt or tool arguments. A provider that does not issue a refresh token requires reconnection after expiry.

Slack uses two independently rotating OAuth token families in one owner-scoped credential. The bot token receives app_mentions:read, chat:write, and reactions:write; the delegated installing-user token receives search:read. Message search therefore sees only what that Slack user is allowed to see. Rat uses the user token only for slack.messages.search and the bot token for identity, posts, replies, reactions, and source delivery. Each access/refresh/expiry family is refreshed separately; a response to one refresh is not required to repeat the other token family.

slack-events derives the workspace selector from the verified Connection rather than accepting a caller-supplied team ID. It creates one owner Connection Set and a team-wide source binding, idempotently repairs the service Connection grant to read-write, and binds the source to the selected Agent and environment. Only one Connection may route mentions for a workspace; attempting to enable another returns a conflict instead of silently changing credentials. The trusted notifier may use the bound service Connection to reply in the source thread even though the Agent tools remain explicitly declared and separately credentialed.

For supported behavior and current limitations, see Status and roadmap.

Never place a token or application secret in a command-line argument, webhook body, Agent, Session request, DynamoDB record, or URL. Keep provider exchange logs redacted. Already-issued OAuth tokens and API keys remain supported through the manifest-driven credential-file flow.

Source-bound permissions#

A verified webhook source selects an owned Agent, environment and optional delivery connection set only after provider signature verification:

rat-things bind-source --file /secure/config/client-channel-binding.json
{
  "version": "1",
  "sourceKind": "slack",
  "selector": {
    "teamId": "T01234567",
    "channelId": "C01234567"
  },
  "agentId": "agent_example",
  "environment": { "type": "openai_hosted", "environment_template_id": "envtpl_example" },
  "connectionSetId": "customer-ops"
}

Selectors match trusted normalized source fields. The authenticated operator owns the binding and its Sessions; provider attribution does not grant ownership or embed a credential. Generic source-binding creation is currently a trusted operator action; do not delegate arbitrary selectors to tenants. For Slack, prefer slack-events: it derives teamId from the verified OAuth Connection and atomically refuses a competing exact workspace claim.

Built-in and fixture integrations#

Plugin Operations Availability
Linear Team/workflow discovery, issue search/get/create/update, comment create Built in
Slack API test, message search/post, reaction add Built in
Stripe Customer search, invoice list, refund create Built in
Fixture CRM Record search/create with two permission-distinct accounts Tests only

The HTTP adapters pin credential-free API base URLs, reject redirects and origin escapes, bound request/response bodies, and attach credentials only inside trusted code. Fixture CRM is compiled only when INTEGRATION_PLUGIN_BASE_URLS supplies its base URL. It exists to prove onboarding, verified identity, provider scopes, multiple accounts, autonomous read/write intersection, exactly-one fixture mutation, and secret non-disclosure without depending on a customer's third-party account.

Add a trusted integration#

Integration plugins are trusted TypeScript adapters compiled into the MicroVM image:

  1. Define an IntegrationPluginManifest in src/plugins/integrations with a lowercase plugin ID, authentication definitions, and namespaced operation IDs.
  2. Implement verifyCredential. Call a fixed provider identity endpoint and return a bounded label, provider authorization, and stable tenant/subject IDs when available. Never trust those values from the connection-create request.
  3. Define each operation's access, risk, required provider scopes, and closed JSON input schema.
  4. Prefer TrustedHttpIntegrationPlugin: use a credential-free HTTPS base URL and construct only relative paths from validated inputs. Never let the model provide an origin.
  5. Register the adapter in src/plugins/integrations/builtins.ts and rebuild the trusted image.
  6. Add contract, simulation, LocalStack, and disposable live-AWS tests. Prove a read, an autonomously admitted write, a denied credential/scope, account selection, and absence of secret values.

Ingress signature parsing remains in src/ingress/src/channels; outbound result notification remains in src/delivery. Agent-callable integration operations belong here. The architecture check enforces those boundaries.

For failures, follow integration diagnostics. For consumer architecture and OAuth ownership, see embedding and self-hosting.