Concepts
Digital twins
A digital twin is a stateful, behavioural replica of a SaaS API — Salesforce, Stripe, Slack, GitHub — that your agent or test suite calls instead of the vendor. A Blobfish Twin environment mounts one or more of those replicas as account-bound MCP servers and vendor-path REST endpoints over one seeded, copy-on-write database, and describes the result as a content-addressed topology. It gives an agent production-shaped tools and state without production credentials or a production fallback.
Properties
- Stateful. Writes persist inside the environment and later reads reflect them: the payment intent you create is in the next list call, the channel you archive stays archived. State lives in a SQLite world seeded from the twin’s own spec; every session forks it copy-on-write, and reset restores the seeded baseline exactly.
- Behavioural. Every mounted operation validates its arguments against the vendor’s declared parameters — a missing required field is
400 invalid_arguments, an unknown field is rejected, not ignored. Lifecycles the spec declares (a Trolley recipient’s status, a Salesforce opportunity’s stage) are guarded transitions, not free writes. A path served under another method answers405with anAllowheader, an unknown path404, an operation outside the mounted slice400— never plausible filler. - Isolated. Environments are bound to the account that created them; sessions cannot see each other’s state; no call can fall through to the real vendor (
BLOBFISH_PRODUCTION_FALLBACK=0in every receipt). - Inspectable. Every call is ledgered — tool, arguments, outcome, session, request id — and every REST response carries
x-blobfish-world,x-blobfish-service,x-blobfish-operation-id, andx-blobfish-mutatedprovenance headers.
Supported services
Blobfish ships Backend twins only. The catalog names 31 services (29 available, 2 roadmap; 29 mounted on this deployment), and each mounted twin serves the vendor’s API surface: an MCP server, vendor-path REST at the vendor’s own URLs, per-operation calls, and a generated CLI. UI twins — a rendered clone of the vendor’s web application that a browser-driving agent could click through — are not offered: nothing in an environment serves HTML, and there is no hosted browser. The catalog’s API + UI label describes how much of the provider’s model the twin covers (records that have a UI counterpart as well as an API), not a hosted user interface. The per-twin facts — runtime rung, operations, entities, guarded lifecycles, the spec’s own limitations — are projected on the Twin reference.
Authentication handling
A twin authenticates you, never a vendor identity. Two headers matter. X-API-Key: bf_… (or Authorization: Bearer bf_…) is your Blobfish account key: reads and writes on a website environment’s shared seeded world are private to its owner (or an allowed delegated bft_ token), and a session — an isolated fork created with POST /api/v1/sandbox/worlds/<world_id>/sessions — always needs it. X-Blobfish-Session: <session_id> pins a call to that fork (Mcp-Session-Id is honoured as an alias).
What happens to the vendor’s own credential: nothing. A Stripe secret key, a Slack bot token, or a GitHub PAT is never validated, forwarded, or stored. X-API-Key is read first, and when it is present the Authorization header is ignored — so an SDK that insists on sending Authorization: Bearer sk_test_… works against the twin as long as you also set X-API-Key. Send only the vendor token and the twin reads it as a Blobfish key and answers 401 Invalid API key format. Keys are never echoed into descriptors, receipts, or ledgers. The one exception is opt-in: when the environment’s behavioural policy enables scopes, the vendor-path REST surface reads the Authorization bearer as the vendor token and compares it to the invented tokens you declared there: the token you present is compared, never persisted; the tokens you declare are stored beside the environment, so declare invented ones (live-secret shapes such as sk_live_, rk_live_, and pk_live_ are refused).
A browser visitor with neither a session nor a key is identified by the sandbox owner cookie (bf_sandbox_owner, HttpOnly — the same cookie Studio scopes anonymous worlds with, set by the first environment that browser creates): the same owner scoping as a key, under a demo quota of 2 live environments and 3 model runs per hour, each run capped at 6 tool steps. Past it the API answers 429 demo_quota — sign in or use an API key for more.
What a twin models on request
Four vendor behaviours are off by default and switch on per environment through its behavioural policy — GET / PUT / POST /api/v1/twin/environments/<env_id>/policy, or policy in the create body. The policy governs the vendor-path REST surface, the per-service MCP surface, and the environment’s world-level MCP endpoint (links.mcp) of a service-twin environment; the worked examples are on the gateway page.
- Token scopes. Declare invented vendor tokens with scope lists (
xoxb-…,ghp_…,rk_test_…); the twin then reads the vendor credential and answers the vendor’s own refusal when the operation’s scope is missing — Slackmissing_scopewithneededandprovided, GitHub403 Resource not accessible by personal access token(parent scopes grant children:repo⇒repo:status,public_repo, …), Stripe403 invalid_request_errornaming the<resource>:read|writepermission. Bot tokens always keepchat:writeandfiles:write. - Rate limiting. Rules per operation, vendor method,
METHOD /path, or*— a sliding window per credential. Over it, the twin answers429withRetry-Afterin the vendor’s shape: Slackratelimited, Striperate_limit_error, GitHubAPI rate limit exceededwithX-RateLimit-*headers. - A clock. Twins stamp writes from a deterministic epoch so scenarios replay identically; the policy clock offsets it (
clock.offset_seconds, orPOST …/policy/clock/advance), so an agent can be rehearsed against “an hour later” without waiting. - Unmounted-operation accounting. Every call to a path the twin does not serve is counted per environment (
GET …/unmounted-hits) and flaggedx-blobfish-unmounted: true, so “the twin has no such operation” is never mistaken for “the vendor refused”.
What a twin does not model
- Vendor tiers, workspace constraints, quotas. A twin does not simulate a vendor’s plan tier, seat or workspace limits, or monthly quotas; its rate limiting is the windowed kind you configure in the policy, not the vendor’s tiered one. Without a rule, the only limit you will meet is Blobfish’s own per-key API quota (
429 Rate limit exceeded), which is an account limit, not a vendor emulation. - OAuth flows and real credentials. Scopes are declared on invented tokens in the policy; there is no authorization-code flow, no token exchange, and a real vendor credential is never validated — the policy compares the bearer to the tokens you declared, nothing more.
- Vendor admin configuration endpoints. The vendor’s own admin surface is not emulated, and what a twin serves is fixed by its spec when the environment is assembled: the manifest is a read-only projection of it, and state changes only through the vendor operations,
reset, and scenarios. The policy endpoint configures the twin’s behaviour, never the vendor’s. - Pagination, webhooks, UI. The vendor-path REST surface answers in the vendor’s own envelope (Stripe’s
object: "list"/data, Slack’sok/channels, GitHub’s bare arrays) and accepts the body encodings its SDKs send, but every list is a single page — cursors andhas_moreare always exhausted; MCP tools keep the twin’s ownitems+count. No outbound webhooks are emitted; no page is rendered.
Twins vs integrations vs scenarios
| You want to | Use |
|---|---|
| Rehearse an agent or an integration test without touching production — deterministic state, honest failures, a ledger. | A twin. Provision it in seconds, point the code at it, tear it down. |
| Confirm the vendor's actual behaviour — a new API version, a plan tier, a webhook, an operation outside the mounted slice. | Your real integration against the vendor's own sandbox. A twin never certifies vendor behaviour it does not model. |
| Start every run, teammate, and CI job from the same rows, states, and edge cases. | A saved scenario applied to a twin (--scenario-id), a preset, or a plain-language prompt — the data a twin starts from. |
Twins vs scenarios, in one line: the twin is the runtime, the scenario is the data it starts from. The same twin serves a hundred scenarios; the same scenario seeds a fresh environment every time it is applied. Your real integration stays the source of truth for what the vendor does; the twin is where you rehearse what your code does with it.
The object you create
- Request: twin slugs, an optional scenario (a saved id, a preset, or a plain-language prompt), a display name, and a bounded TTL —
POST /api/v1/twin/environments. - Assembly (website plane,
twe_): the mounted twins’ spec slices are forged into one seeded SQLite world, scenario rows are injected before the database initialises, and the response reports what the runtime observed — usuallyready, in seconds, synchronously. - Cluster plane (
tenv_, selected explicitly withplane: "cluster"): Buildkite resolves repository agents to a commit, builds in an isolated queue, and publishes digest-pinned descriptors; Argo CD is the only Kubernetes reconciler, owning one Application per MCP and one for the agent. This plane answers only where an admitted provider catalog is configured — on blobfish.ai today it is not, and every cluster request answers503 twin_environment_control_failednaming that reason. - Projection: reports requested, building, deploying, ready/degraded/failed, draining, and terminated state from observed receipts — not optimistic browser state.
Why Twins differ from static mocks
| Approach | Mutable state | Resettable | Inspectable topology | Production side effects |
|---|---|---|---|---|
| Real provider | yes | no | no | possible |
| Static response mock | no | yes | no | none |
| Blobfish Twin | copy-on-write | exact baseline | nodes + edges | blocked |
State and scenarios
- Provider and scenario artifacts are content-addressed and mounted read-only before readiness.
- Each MCP session works on a copy-on-write SQLite database, so parallel agents cannot mutate the baseline or each other.
- Reset discards every session database for the selected provider and recreates the exact baseline. A second replay therefore starts from the same state.
- The website-plane ledger records tool, arguments, outcome, session, and request id for every REST, MCP, CLI, and model-run call. The cluster-plane ledger is payload-free: digests, operation, outcome, status, latency, opaque session reference, and timestamp only.
Provider fidelity
The public reference names 31 providers (29 available, 2 roadmap). The signed-in console reads the control plane’s exact admitted manifest, including provider digest, API/API+UI label, resources, visual metadata, seed schema, and fidelity rung. Roadmap entries remain non-executable.
Fidelity means implemented, deterministic behavior for the admitted surface. It does not mean arbitrary provider or browser cloning. An undocumented operation, unsupported call, prompt-only scenario, or unseen behavior is rejected rather than answered with plausible filler.
Public surfaces
Every executable provider exposes an account-bound MCP endpoint, and on the website plane vendor-path REST, per-operation calls, reset, sessions, and a generated CLI beside it. The environment console shows the endpoint card, the lease, scenario identity, reset controls, model runs, the call ledger, and the environment graph; where the cluster plane is admitted it adds Buildkite and Argo CD evidence, Kubernetes ownership, and agent/provider separation. Catalog API + UI describes provider model coverage; the self-serve environment does not claim an arbitrary vendor-domain or browser replacement.
Next
Start a twin run · Twins quickstart · Local testing · Gateway & health · Custom scenarios · Train on a twin · Models · Benchmark runs · Twin reference