Skip to content

Project layout

my-api-tests/
├── users.yaml # a collection
├── orders.yaml # another collection
└── .tinspec/
├── chains/<name>.yaml # one file per chain
├── auth/<name>.yaml # auth provider definitions
├── auth/<name>.js # the script, for a script provider
├── providers/<stem>.yaml # API providers (spec bindings)
├── overlays/<provider-id>.yaml
├── route-matchers/<name>.yaml
├── lib/<name>.js # script snippets you insert
├── functions/<name>.js # standalone scripts you run
├── tinspec.d.ts # generated ts.* declarations (on request)
├── themes/<id>.json
├── sync.yaml # per-document synced versions (cloud tier)
└── tinspec.db # SQLite: run history + spec cache
PathContentsCommit?
*.yaml (root)Collections: requests, environments, defaults, spec sources, auth providers, route matchersYes
.tinspec/chains/One chain per file, formatVersion: 3Yes
.tinspec/auth/Auth provider definitions — config and secret names, never values. A kind: script provider’s JS sits beside its YAMLYes
.tinspec/providers/API providers: the spec binding, poll interval, auth binding, transport settings, mock and publish optionsYes
.tinspec/overlays/Your persistent edits to spec-generated endpoints, plus saved examplesYes
.tinspec/route-matchers/Gateway configs stored inlineYes
.tinspec/lib/, .tinspec/functions/Project JavaScript — plain .js, no build stepYes
.tinspec/tinspec.d.tsGenerated ts.* type declarations for an external editorYour call
.tinspec/themes/User-authored themes: presentation tokens, one JSON per themeYour call
.tinspec/sync.yamlPer-document synced versions. Non-secret; safe to deleteOptional
.tinspec/tinspec.dbRun history, spec cache, search indexNo
.tinspec/tinspec.db
.tinspec/tinspec.db-*

That is all that needs excluding. Everything else in the folder is meant to be reviewed in a merge request.

Files are the source of truth. Collections, chains, auth definitions, providers, route matchers, overlays, and project scripts are authoritative. Nothing about your API tests exists only in a database.

SQLite is a rebuildable cache. tinspec.db holds run history, the cached OpenAPI documents, and search indexes. Delete it and everything is rebuilt by re-reading the files. The only thing genuinely lost is run history. This is why it is the one entry in .gitignore — it is derived, machine-local, and large.

Secrets are in neither. Not in the YAML, not in the database. They live in your OS keychain or a machine-local encrypted store, chosen in Settings → Security, and the files reference them by name. Committing this folder does not commit a credential. That holds for the newer fields too: a client certificate is tls.clientKeyRef, a proxy password is proxy.auth.passwordRef, an inline API key is valueRef — names, every one of them.

.tinspec/providers/<stem>.yaml is one API provider: a live spec binding and everything the app should do with what it finds there. The file stem comes from the name; the id inside is the identity, which is why renaming a provider does not orphan its overlays.

id: 2f6c1a5e-…
name: Billing API
url: https://billing.internal/openapi.json
kind: openapi
pollMs: 300000
groupBy: tag
auth:
provider: keycloak-dev
defaultSender: qa-admin
rules:
- match: /admin/**
sender: tenant-admin
- match: /health
none: true
settings:
timeoutMs: 30000
proxy:
url: http://proxy.corp:3128
FieldNotes
idGenerated once. The overlay file is named after it, so it must never change
nameDisplay name. The file stem is derived from it, so the two may legitimately disagree after a rename
urlAn OpenAPI document, a .proto path, a grpc:// reflection target, a GraphQL endpoint, or an MCP server endpoint
kindopenapi (the default when absent) · grpc · grpcReflection · graphql · mcp
targetFor kind: grpc only — the grpc://host:port requests go to. A .proto describes methods but carries no address
pollMsPer-provider poll override; absent uses the global interval
groupBytag · path · method · none — how the navigator groups these endpoints
authprovider, defaultSender, and ordered path rules (first match wins; a rule may switch provider, pick a sender, or set none: true to send unauthenticated)
routeMatcherRewrites every generated endpoint URL to its gateway-facing form
settingsThe provider layer of send settings — “this whole API is behind the corporate proxy, with this client certificate”
mockHow this provider’s mock server is configured when started. Persisted; whether one is running is not, so opening a project never binds a port
publishWhich slice of this API is published to your agent as MCP tools, and as which sender. Absent means nothing is published

auth and publish name a provider and a sender; neither ever holds a credential.

The file carries no formatVersion at this release. The app reads and writes it as plain YAML with no version key, and a file without one is read as the original version, so there is nothing to migrate and nothing for you to add.

A spec-generated endpoint is regenerated from scratch on every poll, so an edit has to be recorded against the generated form and re-applied afterwards. That record is an overlay: .tinspec/overlays/<provider-id>.yaml, a mapping of endpoint id to the edit made on it.

"GET https://billing.internal/invoices/{id}":
baseSignature: 9f3c21ab
headers:
X-Tenant: acme
body: null
settings:
timeoutMs: 60000

The key is the endpoint’s stable identity across re-fetches, and it is not always the URL — GET https://…/invoices/{id} for HTTP, but grpc package.Service/Method, graphql query.user and mcp <toolName> for the others, because every method of a gRPC service, every root field of a GraphQL endpoint and every tool on an MCP server share one URL.

Each entry holds a baseSignature — the endpoint’s spec signature when the edit was made, so a later poll that changes the endpoint underneath can flag the edit as stale instead of silently merging — plus only the request groups that differ from the generated form. There are nine: body, query, headers, auth, grpc, graphql, prerunChain, routeMatcher, settings. A group replaces the generated one wholesale; there is no per-key merge, so what will be sent is exactly what you last saw.

url and method are deliberately not overlayable — they are the endpoint’s identity.

Every group is three-state, which is what makes a reset stick:

On diskMeans
absentNever touched. The generated value stands
a valueReplaces the generated group
null (or an empty map for query/headers)You deleted the generated value. Without this state, the next poll simply brings it back

An entry may also carry examples: responses you kept under a name. Those are not a request group — applying an overlay ignores them, so saving an example never changes what the endpoint sends. They are capped (256 KB each, 20 per endpoint) and text-only, because base64 in a committed YAML file is not a diff anybody can read.

Overlay files, like provider files, carry no formatVersion at this release.

Two folders of plain, committable JavaScript, both new in v0.1.0-preview.10:

  • .tinspec/lib/ — reusable snippets. The Scripts tab inserts one at the caret, and the inserted text is what gets committed with the request, so the collection stays self-contained.
  • .tinspec/functions/ — standalone scripts you run in the app for a one-off computation: mint a signature, decode a token, derive a value into a variable. Running one sends nothing.

Neither folder is loaded when a request is sent. There is no require and no import, and that is a decision rather than a gap: this code is pulled from teammates and executed against real credentials, so module resolution would turn “review this collection’s scripts” into “audit a dependency tree fetched at run time”. What a send runs is always the inline hook on the request — see Request.scripts.

A file name must be a single component ending in .js. A path, a nested folder or a .. is refused rather than sanitised, so these commands cannot be aimed at something outside the two folders.

.tinspec/tinspec.d.ts is written only when you ask for it, from the Scripts view’s Write declarations button. The app’s embedded editor already knows ts.*; this file is for the other editor — VS Code opening .tinspec/lib/sign.js has a TypeScript service and needs a declaration to use it. It is generated from the same list the in-app completion is built from, so it is regenerated rather than maintained. Commit it if your teammates edit these files outside Tinspec.

Each provider’s last successfully fetched OpenAPI document is cached in tinspec.db. It backs three things: endpoints that keep working when the service is offline, the copy a spec subset action, and the field-by-field explanation in Spec Changes.

A project without a folder writes nothing. Requests live in memory for the session, and run history, the spec cache, endpoint overlays and project scripts are unavailable — there is nowhere to put them.

Fine for a scratch session. Not where real work should live.

Some state is deliberately not in your project folder, because it should not travel with the repository:

  • Secrets — OS keychain or the machine-local encrypted store.
  • The cloud device token — the encrypted store, so signing in never triggers a keychain prompt.
  • Your Anthropic API key, if AI assist is on — the encrypted store.
  • The agent-access token and port — the encrypted store and app settings.
  • App settings, the recent-project list, keybinding overrides, the active theme — an app settings store, not the project.