Project layout
The tree
Section titled “The tree”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 cacheWhat each holds
Section titled “What each holds”| Path | Contents | Commit? |
|---|---|---|
*.yaml (root) | Collections: requests, environments, defaults, spec sources, auth providers, route matchers | Yes |
.tinspec/chains/ | One chain per file, formatVersion: 3 | Yes |
.tinspec/auth/ | Auth provider definitions — config and secret names, never values. A kind: script provider’s JS sits beside its YAML | Yes |
.tinspec/providers/ | API providers: the spec binding, poll interval, auth binding, transport settings, mock and publish options | Yes |
.tinspec/overlays/ | Your persistent edits to spec-generated endpoints, plus saved examples | Yes |
.tinspec/route-matchers/ | Gateway configs stored inline | Yes |
.tinspec/lib/, .tinspec/functions/ | Project JavaScript — plain .js, no build step | Yes |
.tinspec/tinspec.d.ts | Generated ts.* type declarations for an external editor | Your call |
.tinspec/themes/ | User-authored themes: presentation tokens, one JSON per theme | Your call |
.tinspec/sync.yaml | Per-document synced versions. Non-secret; safe to delete | Optional |
.tinspec/tinspec.db | Run history, spec cache, search index | No |
.gitignore
Section titled “.gitignore”.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.
The rules that govern this layout
Section titled “The rules that govern this layout”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.
A provider file
Section titled “A provider file”.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 APIurl: https://billing.internal/openapi.jsonkind: openapipollMs: 300000groupBy: tagauth: provider: keycloak-dev defaultSender: qa-admin rules: - match: /admin/** sender: tenant-admin - match: /health none: truesettings: timeoutMs: 30000 proxy: url: http://proxy.corp:3128| Field | Notes |
|---|---|
id | Generated once. The overlay file is named after it, so it must never change |
name | Display name. The file stem is derived from it, so the two may legitimately disagree after a rename |
url | An OpenAPI document, a .proto path, a grpc:// reflection target, a GraphQL endpoint, or an MCP server endpoint |
kind | openapi (the default when absent) · grpc · grpcReflection · graphql · mcp |
target | For kind: grpc only — the grpc://host:port requests go to. A .proto describes methods but carries no address |
pollMs | Per-provider poll override; absent uses the global interval |
groupBy | tag · path · method · none — how the navigator groups these endpoints |
auth | provider, defaultSender, and ordered path rules (first match wins; a rule may switch provider, pick a sender, or set none: true to send unauthenticated) |
routeMatcher | Rewrites every generated endpoint URL to its gateway-facing form |
settings | The provider layer of send settings — “this whole API is behind the corporate proxy, with this client certificate” |
mock | How this provider’s mock server is configured when started. Persisted; whether one is running is not, so opening a project never binds a port |
publish | Which 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.
An overlay file
Section titled “An overlay file”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: 60000The 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 disk | Means |
|---|---|
| absent | Never touched. The generated value stands |
| a value | Replaces 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.
Scripts on disk
Section titled “Scripts on disk”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.
The spec cache
Section titled “The spec cache”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.
Folderless projects
Section titled “Folderless projects”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.
Where machine-local state goes instead
Section titled “Where machine-local state goes instead”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.