Why digital twins?
Real integrations evolve. Static mocks don’t, so tests drift from production. And when staging hits real Stripe, Slack, or Notion, state leaks across runs and you can’t simulate failures, rate limits, or webhook errors. Digital twins solve both problems.Properties
Stateful
Stateful
The twin remembers state before an API call and updates state after the call completes. If you create a Stripe customer, subsequent calls reflect that customer’s existence — just like the real Stripe API.
Behavioural
Behavioural
The twin reacts the same way the real service would to an API call, including error responses, rate limits, and side effects. A payment to a non-existent customer returns the same error code Stripe would.
Supported services
Arga currently supports these sandbox twins. Twins are split into two categories:- UI twins expose a browsable sandbox surface in the Arga UI so you can interact with the twin directly during a run — useful for services with meaningful user-facing state (chats, files, dashboards, checkout pages).
- Backend twins intercept outbound API traffic only. They have no UI surface because the underlying service is consumed purely programmatically.
UI twins
Backend twins
Twins and integrations are different:
- Use integrations to connect context sources that help Arga understand your stack.
- Use digital twins to intercept outbound traffic during sandbox validation.
Stub alerting
Not every endpoint in a digital twin has a full stateful implementation. Endpoints that are not yet statefully implemented return stub responses — schema-valid mock data generated from the service’s API specification. Stub responses are clearly marked so you can distinguish them from real stateful behavior:X-Twin-Stubheader — set to"true"on every stub response._twin_stubfield — a boolean (true) injected into the JSON response body._twin_warningfield — a human-readable string explaining which endpoint is stubbed.
_twin_stub, _twin_warning, and a data key containing the original array.
X-Twin-Stub header to programmatically detect stub responses in your test suite and fail tests that rely on stubbed data.
Tracking stub hits
The GitHub twin exposes an admin endpoint to audit which stubbed endpoints were called during a session:
To clear the stub hit log:
{"ok": true} on success.
How Arga uses digital twins
When Arga spins up a sandbox for a PR, it forks only the services the PR touches and routes all their external API calls through the appropriate digital twin. The rest of your stack stays on your production services. This means:- No real side effects — A PR that sends a Slack notification or charges a Stripe card won’t do either during testing.
- Deterministic results — Tests produce consistent outcomes regardless of external service availability.
- Edge case coverage — Twins can simulate failure modes (timeouts, rate limits, malformed responses) that are hard to trigger against real services.
Authentication handling
When requests are routed through a digital twin, Arga controls which headers are forwarded to the twin. For most twins, theAuthorization header is stripped from proxied requests because the twin doesn’t need real credentials — it simulates the service regardless of auth tokens.
The Slack twin is an exception. Slack API calls include the Authorization header when forwarded to the twin, allowing the twin to validate token-scoped behaviour such as bot-token versus user-token permission differences. This means your sandbox tests exercise the same authentication paths your code uses in production, without touching the real Slack API.
The Slack twin enforces OAuth scope requirements on every authenticated API call. Each Slack Web API method requires a specific scope (for example, chat.postMessage requires chat:write, conversations.list requires channels:read). If a token does not include the required scope, the twin returns a missing_scope error — matching real Slack behaviour:
* bypass scope checks and are accepted for any method. You can selectively disable scopes using the disabled_scopes config field — any scope in the disabled list is removed from the effective scope set before enforcement, even if the token originally had it.
The Slack twin also supports the full OAuth 2.0 authorization code grant flow. When SLACK_TWIN_BASE_URL is set, Arga routes OAuth requests (/oauth/v2/authorize and /api/oauth.v2.access) through the twin so you can test “Add to Slack” install flows, token exchange, and scope negotiation in a sandbox. Issued tokens (xoxb- for bots, xoxp- for users, and xoxe.-prefixed enterprise tokens) are fully functional within the twin and can be used for subsequent API calls like auth.test and search.messages. The scopes granted to tokens issued via the OAuth flow are determined by the twin’s configurable oauth_bot_scopes and oauth_user_scopes settings (see configurable token scopes below).
The GitHub twin preserves the Authorization header and enforces OAuth scope requirements on API calls. Each API route requires a specific scope (for example, POST /repos/{owner}/{repo}/issues requires repo, GET /user requires read:user). Parent scopes automatically grant their children — repo implies repo:status, repo_deployment, repo:invite, public_repo, and security_events; admin:repo_hook implies write:repo_hook and read:repo_hook; and so on. If a token does not include a required scope, the twin returns a 403 matching GitHub’s real API response shape:
default_token_scopes config field. Scopes can also be selectively disabled using the disabled_scopes config field — any scope in the disabled list is removed from the effective scope set, even if the token originally had it.
The Google Drive twin also enforces OAuth scope requirements on every API call. Each Drive API operation requires at least one of a set of accepted scopes (for example, files.create requires drive or drive.file or drive.appdata). Parent scopes automatically grant their children — drive implies drive.readonly, drive.file, drive.appdata, drive.metadata, and drive.scripts. If a token lacks any accepted scope for an operation, the twin returns a 403 insufficientPermissions error matching the real Google Drive API. You can selectively disable scopes using the disabled_scopes config field.
The Notion twin enforces capability-based access on user info endpoints. The twin recognizes three user-information capability levels: read_user_information_with_email (full access including email), read_user_information_no_email (user info without email), and no_user_information (no access). The legacy read_user_information capability is treated as equivalent to read_user_information_with_email. If a token has no user info capability, user endpoints (/v1/users, /v1/users/me, /v1/users/{user_id}) return a 403 restricted_resource error. You can selectively disable capabilities using the disabled_capabilities config field.
The Stripe twin also preserves the Authorization header. The twin validates that API keys use a recognized prefix (sk_test_, sk_live_, rk_test_, or rk_live_) and returns Stripe-compatible authentication errors for missing or invalid keys. This means your code’s Stripe authentication logic runs the same way in the sandbox as it does in production.
The Jira twin preserves the Authorization header and requires every non-public request to use a Bearer token. Requests missing or with a malformed Authorization header receive a 401 matching Jira’s real API response shape:
POST /rest/api/3/issue requires write:jira-work, GET /rest/api/3/myself requires read:jira-user, and POST /rest/api/3/component requires manage:jira-project. Parent scopes automatically grant their children: write:jira-work implies read:jira-work, manage:jira-project implies read:jira-work, and manage:jira-configuration implies read:jira-work and manage:jira-project. Tokens that lack a required scope receive a 403 with a missing-scope message:
read:jira-work, write:jira-work, read:jira-user, manage:jira-project, manage:jira-webhook, and manage:jira-configuration. Configure these via the default_token_scopes config field, or selectively remove scopes via disabled_scopes — any scope in the disabled list is removed from the effective scope set even if a token would otherwise grant it. Tokens issued through the OAuth 2.0 flow (/authorize and /oauth/token) carry the scopes negotiated during authorization.
Slack twin tier limits
The Slack twin enforces tier-based file upload size limits, letting you test how your application handles free-tier restrictions and paid-tier upgrades. The twin also supports tier-dependent workspace constraints: message history limits, app install limits, and workflow availability.
When a file upload exceeds the current tier’s limit, the twin returns a
file_too_large error with details about the active tier and cap:
files.getUploadURLExternal with a length that exceeds the cap, and when uploading file content that exceeds the cap during the external upload step.
Switching tiers
Use the admin API to switch between tiers:Slack twin workspace constraints
The Slack twin supports workspace-level settings that let you simulate how your application handles disabled file uploads, storage quota limits, and rate limiting. Configure these via scenario seeding or the admin API.File upload toggle
Setfile_uploads_enabled to false to simulate a workspace where an admin has disabled file uploads. Any call to files.getUploadURLExternal returns a file_uploads_disabled error:
Storage quotas
Setmax_storage_bytes to enforce a workspace-level storage quota. The twin tracks total storage usage (the sum of storage_used_bytes and all uploaded file content) and rejects new uploads when the quota would be exceeded. A call to files.getUploadURLExternal with a length that would push usage over the quota returns a storage_limit_reached error:
For example, setting
max_storage_bytes to 500 and storage_used_bytes to 400 means only 100 bytes of new uploads are allowed before the quota is reached.
Rate limiting
Setrate_limiting_enabled to true and provide a rate_limits map to simulate per-method rate limiting. When a method exceeds its configured request limit within the time window, the twin returns a ratelimited error:
Slack twin configurable token scopes
You can customize the OAuth scopes granted to bot and user tokens in the Slack twin. This lets you test how your application handles missing permissions — for example, verifying that your app gracefully handles amissing_scope error when a required scope is not granted.
Bot tokens always receive
chat:write and files:write as minimum scopes, even if your seed configuration specifies a narrower set. The twin automatically adds these scopes to any bot token that lacks them. This ensures that bots can always send messages and upload files. To test missing_scope errors for chat:write or files:write, use a user token instead.Default scopes
The twin ships with these default scopes:Configuring scopes via scenario seeding
Passoauth_bot_scopes, oauth_user_scopes, or tokens in the Slack twin’s seed configuration to override the defaults:
When you configure a narrower set of scopes, any API call requiring a scope not in the list returns the
missing_scope error described in authentication handling. Note that bot tokens always retain chat:write and files:write even when you specify a narrower scope list — see the note above.
Scope requirements by API method
The Slack twin enforces the following scope requirements:
Methods not listed (such as
api.test and auth.test) do not require a specific scope.
Slack twin admin endpoints
The Slack twin exposes admin endpoints for inspecting and managing twin state during a session.Get config
Replace config (full reset)
SlackTwinConfig object. Any fields you omit revert to their defaults.
Response
Patch config (preserve state)
List users
File downloads
Uploaded files are accessible via direct download endpoints:Content-Disposition header for download. Returns 404 if the file does not exist.
Google Drive twin storage tiers
The Google Drive twin enforces storage quota limits based on a configurable storage tier, letting you test how your application handles quota-exceeded errors.
When a file upload would exceed the tier’s storage quota, the twin returns a
403 storageQuotaExceeded error matching the real Google Drive API:
about.get endpoint reflects the effective storage quota limit in its storageQuota.limit field.
Switching storage tiers
Use the admin API to get or set the storage tier:storage_quota_limit config field, which takes precedence over the tier-based default.
Dropbox twin account tiers
The Dropbox twin enforces storage quota limits based on a configurable account tier.
When a file upload would exceed the tier’s quota, the twin returns an
insufficient_space error matching the real Dropbox API:
get_space_usage endpoint reflects the allocated quota based on the active tier.
Switching account tiers
Use the admin API to get or set the account tier:Notion twin workspace plans
The Notion twin enforces workspace plan-based limits, letting you test how your application handles different plan tiers.
When a file upload exceeds the plan’s size limit, the twin returns a validation error. The twin also supports the
disabled_capabilities config field for selectively disabling API capabilities.
Switching workspace plans
Use the admin API to get or set the workspace plan:Discord twin boost levels
The Discord twin supports configurable server boost levels that control resource limits.
Configure the boost level via the
guild_boost_level config field. The max_file_upload_bytes and max_emoji_slots fields can also be set independently to override the boost-level defaults.
Slack twin conversations.open
The Slack twin supports theconversations.open API method for opening or creating direct-message channels.
Response (new DM)

