Skip to content

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

  1. 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.
  2. 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 answers 405 with an Allow header, an unknown path 404, an operation outside the mounted slice 400 — never plausible filler.
  3. 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=0 in every receipt).
  4. 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, and x-blobfish-mutated provenance 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.

  1. 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 — Slack missing_scope with needed and provided, GitHub 403 Resource not accessible by personal access token (parent scopes grant children: repo ⇒ repo:status, public_repo, …), Stripe 403 invalid_request_error naming the <resource>:read|write permission. Bot tokens always keep chat:write and files:write.
  2. Rate limiting. Rules per operation, vendor method, METHOD /path, or * — a sliding window per credential. Over it, the twin answers 429 with Retry-After in the vendor’s shape: Slack ratelimited, Stripe rate_limit_error, GitHub API rate limit exceeded with X-RateLimit-* headers.
  3. A clock. Twins stamp writes from a deterministic epoch so scenarios replay identically; the policy clock offsets it (clock.offset_seconds, or POST …/policy/clock/advance), so an agent can be rehearsed against “an hour later” without waiting.
  4. Unmounted-operation accounting. Every call to a path the twin does not serve is counted per environment (GET …/unmounted-hits) and flagged x-blobfish-unmounted: true, so “the twin has no such operation” is never mistaken for “the vendor refused”.

What a twin does not model

  1. 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.
  2. 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.
  3. 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.
  4. Pagination, webhooks, UI. The vendor-path REST surface answers in the vendor’s own envelope (Stripe’s object: "list" / data, Slack’s ok / channels, GitHub’s bare arrays) and accepts the body encodings its SDKs send, but every list is a single page — cursors and has_more are always exhausted; MCP tools keep the twin’s own items + count. No outbound webhooks are emitted; no page is rendered.

Twins vs integrations vs scenarios

You want toUse
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

  1. 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.
  2. 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 — usually ready, in seconds, synchronously.
  3. Cluster plane (tenv_, selected explicitly with plane: "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 answers 503 twin_environment_control_failed naming that reason.
  4. 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

ApproachMutable stateResettableInspectable topologyProduction side effects
Real provideryesnonopossible
Static response mocknoyesnonone
Blobfish Twincopy-on-writeexact baselinenodes + edgesblocked

State and scenarios

  1. Provider and scenario artifacts are content-addressed and mounted read-only before readiness.
  2. Each MCP session works on a copy-on-write SQLite database, so parallel agents cannot mutate the baseline or each other.
  3. Reset discards every session database for the selected provider and recreates the exact baseline. A second replay therefore starts from the same state.
  4. 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