Skip to content

Privacy

An API spec and a response body are among the most sensitive things a developer handles. This page is the specific accounting.

Out of the box, with no settings changed, Tinspec sends nothing anywhere except the requests you explicitly make to your own APIs.

There is no telemetry, no analytics, and no account. The only call Tinspec makes to a Tinspec server is the update check against a static JSON file on tinspec.dev.

Two background calls do go out, and both go where you pointed them:

  • A live-bound spec source re-fetches its document on an interval — 10 s by default, changed under Settings → General → Spec refresh interval. That request goes to the spec URL you configured, which is the whole point of the binding staying live.
  • A file-backed spec is watched on disk instead, by comparing an mtime:size stamp across the root document and every local $ref it reaches. No network, no file contents read unless the stamp changed. Remote $refs cannot be watched this way and are deliberately left to the ordinary poll.

Everything that could send your data somewhere else is off by default and behind its own consent switch.

ThingWhere
Collections, chains, auth definitions, providers, route matchers, overlaysPlain YAML in your project folder
Request pre/post hooksInline in the collection YAML, as block scalars — the reviewed text is the executed text
Reusable snippets and standalone scriptsPlain .js in .tinspec/lib/ and .tinspec/functions/
Run history, spec cache, search index.tinspec/tinspec.db — local SQLite, rebuildable
SecretsOS keychain, or a machine-local AES-256-GCM encrypted store (chosen per secret, at save time)
Your Anthropic API key (if AI assist is on)The encrypted store — never handed back to the UI
The agent-access tokenThe encrypted store

Secrets are never written to the YAML files or the database. Auth definitions reference secrets by name — including the newer kinds, where an OAuth client secret, a refresh token and an AWS secret access key are all stored as …Ref names rather than values. There is no secret_get command for the UI to call. This is what makes the project folder safe to commit and safe to sync.

A request can carry pre and post hooks, and an auth provider can be a script. They run in the engine’s sandboxed QuickJS runtime, and the ts.* standard library deliberately hands them real power: ts.secret(name) reads a secret value, and ts.fetch(url, opts) makes a network call.

So the honest statement is: a script can read a secret and send it wherever it likes. That is what signing a body or minting a token requires. Treat a collection you pulled from a teammate the way you would treat any code you are about to run.

Two things make that auditable rather than alarming:

  • There is no module loader and no require. Nothing is fetched at run time, so the script that runs is exactly the text in the YAML that a reviewer read. .tinspec/lib files are snippets the editor inserts into the request; .tinspec/functions files are scripts you run yourself, not on a send.
  • You can run a hook without sending anything, from the Scripts tab, so a signing hook is testable against nothing rather than against production.

The contract is on the auth script reference.

AI assist is off by default, and its surfaces stay hidden until you turn it on and store your own Anthropic key. With it on:

What is sent: a minimal OpenAPI subset — the operations of the endpoints you are working with, their parameters and schemas. Nothing else.

What is never sent:

  • Response bodies. Whatever your API returned stays on the machine.
  • Environment values.
  • Secrets, tokens, or keychain contents.
  • Run history, your project folder, or any file.

How to verify it rather than trust it: the AI panel has a collapsible “context sent with prompts” preview showing the exact payload, verbatim, before it goes. Open it once.

Requests go directly from your machine to Anthropic with your key. There is no Tinspec-hosted inference tier, and nothing routes through Tinspec’s servers.

Agent access is off by default, behind a consent switch separate from AI assist.

With it on, the app serves MCP on 127.0.0.1 only — never a network interface — behind a bearer token, and refuses requests without it.

What an agent can read: through 21 tools and four kinds of resource — the open project’s endpoints, saved requests, chains, run history, contract-drift findings, environment variable names, the identities it can send as, and — this is the important one — response bodies from requests it sends.

That last point deserves to be stated plainly rather than buried: response bodies do reach whichever model your agent uses. That is the purpose of the feature, and it is why it is off by default and why you approve sends one at a time.

What never crosses:

  • Environment values. Keys only.
  • Auth tokens. A sensitive request header is masked to Bearer **** in what the agent sees.
  • The keychain. Not reachable through any tool.
  • Credentials and secret names, from the identity tools. list_auth_providers and list_senders emit identities — name, kind, tenant, which sender is the default, whether a sender is usable — and nothing else. No provider config, no secretRef names, no script source. A sender that is missing its password is reported as unusable, without naming the secret it wants, let alone its value.

What needs your approval: every tool that sends traffic or writes a file — send_request, run_chain, save_request, save_chain — plus set_environment, the one otherwise-read-adjacent tool that changes app state. Read-only tools run immediately and never prompt. Safe-method sends can be auto-allowed with a setting; everything else always asks.

If you publish your own API as tools: a published endpoint arrives at the agent as a tool like createOrder, carrying a provider and sender resolved here. The agent is never told which identity it is calling as and the tool schemas contain no credential field, so it cannot learn the JWT. Publishing nothing is the default. Deprecated and x-internal endpoints are never published, and each published call goes through the same approval toast as any other send.

What is recorded: every agent send lands in run history tagged mcp with the client name, alongside your own runs. The Agent activity panel shows every tool call, its outcome, and each approval decision live.

A spec-bound mock serves your API’s declared shapes back to you. It is a server, so it is worth saying where it listens and what it serves.

  • It binds loopback by default. Putting a mock of an internal API on the LAN is something you would have to type.
  • There is no TLS, and no state: a POST does not make the next GET return the row.
  • Response bodies can come from your own run history — real 2xx payloads the API actually returned, scrubbed of secrets by the same path the Body autofill uses. That data never leaves the machine; it is read out of the local SQLite history and served back on loopback.
  • Nothing auto-starts. Options persist between launches, the server does not, and mocks stop when you switch project.

An OAuth 2.0 / OIDC provider signs in against your authorization server, never a Tinspec one. The browser half is the only part the app owns: it opens one URL in your system browser and captures the redirect on a 127.0.0.1 listener on an ephemeral port. The state nonce is validated before the code is exchanged. The device grant is the same flow with no listener at all.

A refresh token is kept in memory only — never written to disk by the engine, and deliberately never exposed as a substitutable {{variable}}, because a variable can end up in a URL or a body and a refresh token has no business in either.

Not available to sign up for at this release. When it is:

What syncs: collections, chains, auth provider definitions, API providers, route matchers.

What does not: secret values, ever. Auth definitions carry secret names; the values stay on the machine that holds them. A teammate who pulls a synced auth provider gets the flow and must supply their own credential. Endpoint overlays and the .tinspec/lib and .tinspec/functions script files are not synced kinds at this release either.

The tinspec CLI has no account, no sign-in and no Tinspec URL. It reads secrets from a --secrets-file (refused outright if it lives inside the workspace, so it cannot be committed by accident) or from a --secrets-command you supply — the escape hatch for vault kv get, op read, aws secretsmanager. tinspec secrets list prints reference names, never values.

The reports it writes are files that get uploaded to a CI server and attached to a merge request, so what goes into them is a security decision rather than a formatting one:

  • No request header, no request body and no credential ever enters a report. That is enforced by what the report builders collect, not by hiding fields at render time.
  • A response body is attached only for a failing case, truncated, and passed through a redaction list of the literal secret values the run was given.

On startup the app fetches a static JSON file from tinspec.dev to see whether a newer version exists. It is a plain file read — no identifiers, no usage data, nothing about your project. The CLI has no update check at all; Homebrew handles that.

FeatureDefaultWhat leaves
The app itselfOnNothing but the requests you make
Spec refreshOn (10 s)A fetch of the spec URL you configured — nothing to us
Update checkOnNothing — a static file read
Request & auth scriptsNone until a collection carries oneWhatever the script itself sends
Mock serverOffNothing — a loopback listener
AI assistOffAn OpenAPI subset, previewable verbatim
Agent accessOffSpec and response bodies, to your own agent, per approved send
Cloud syncOff (not open)Documents, never secret values
CI reportsOpt-in per runResults, drift, and a failing case’s redacted body