Runs onWrit Cloud
On this page
Authentication.
Five credential families, each opening exactly one surface. This page maps them, then goes deep on the two you will mint the most: wt_ API keys with their scopes, and OAuth for third-party apps.
tokens ▸ the map
Five credentials, five surfaces.
Each credential opens exactly one slice of Writ. The fastest way to debug a 401 is to check the pair: which token, on which surface.
| Credential | Prefix | Where it works |
|---|---|---|
| API key | wt_ | Your own servers, on the whole /api/* + /api/v1/* surface and on POST /mcp. |
| OAuth access token | wto_ | Third-party apps, on /api/* and on published /mcp/{slug} servers. Legacy pso_ tokens are still accepted. |
| Consumer key | csk_ | Your customers, only on the /v1/{slug}/{path} gateway — never on /api. |
| SCIM token | — | Your identity provider, only on /scim/v2/* — one token per organization. |
| Session (JWT) | — | The web app after login — 15-minute access tokens, revoked server-side on logout. |
wt ▸ keys + scopes
API keys: one header, scoped tight.
A key is wt_ followed by 43 URL-safe characters. The secret is shown once at creation and stored hashed — lists and logs show only a short, non-secret prefix. Send it as a Bearer token:
curl https://api.usewrit.app/api/v1/workflows \
-H "Authorization: Bearer $WRIT_API_KEY" headers = {"Authorization": f"Bearer {os.environ['WRIT_API_KEY']}"} const headers = { Authorization: `Bearer ${process.env.WRIT_API_KEY}` }; req.Header.Set("Authorization", "Bearer "+os.Getenv("WRIT_API_KEY")) let req = client.get(url).bearer_auth(std::env::var("WRIT_API_KEY")?); Scopes: resource:action
Actions are read, write, execute and delete, over seventeen resources. A key carries only the scopes you grant it — and the pinnable resources can be narrowed further, to specific ids.
| Resource | Actions | Note |
|---|---|---|
workflows | read · write · execute · delete | Pinnable to specific workflows. |
runs | read | |
monitors | read · write · execute · delete | Pinnable. |
datasets | read · delete | Pinnable. |
transfer | read · write | Bulk export and import of the whole account — never part of any preset. |
crawl | read · execute · delete | |
scrape | execute | Split from crawl: a single-page extraction key cannot start a site crawl. |
files | read · write · delete | |
personas | read · write · delete | |
secrets | read · write · delete | Values are never returned — names and metadata only. |
agents | read · write · execute | |
triggers | read · write · execute · delete | |
recorder | read · execute | |
streaming | read · execute · delete | |
mcp | read · write · execute · delete | |
marketplace | read · write | |
account | read |
Three presets cover most keys: read_only (every :read), run (:read + :execute) and full (everything, including delete). transfer is excluded from every preset and must be granted by hand. Wildcards expand to concrete scopes at grant time, so a key can never silently widen later — and enforcement is default-deny: a route not explicitly opened to API keys refuses them.
Rotation. Keys are independent: mint a new key with the same scopes, deploy it, then revoke the old one — no downtime. Store keys in your secrets manager; they cannot be re-displayed.
oauth ▸ third-party apps
OAuth: PKCE, no client secret.
The OAuth surface is built for public clients: PKCE is required, there is no client secret, and clients can self-register through RFC 7591 dynamic registration. Access tokens are prefixed wto_ (legacy pso_ still accepted) and work on /api/* and published /mcp/{slug} servers.
| Endpoint | Role |
|---|---|
GET /api/oauth/.well-known/oauth-authorization-server | Authorization-server metadata — endpoints, scopes, PKCE methods. |
POST /api/oauth/register | RFC 7591 dynamic client registration, for public PKCE clients. |
OAuth scopes are a separate, smaller set — thirteen — and a grant caps at the operator role, never admin:
| Scope | Grants |
|---|---|
targets:read | View monitored targets and their status. |
targets:write | Create, update and delete targets. |
changes:read | View detected changes and diffs. |
workflows:read | View workflows. |
workflows:write | Create and modify workflows. |
workflows:execute | Trigger workflow execution. |
triggers:read | View trigger rules and webhook configurations. |
triggers:write | Create and modify triggers. |
reports:read | View run results and reports. |
notifications:read | View notification settings. |
notifications:write | Manage notification settings. |
org:read | View organization info and team members. |
profile:read | View user profile information. |
account ▸ sign-in security
Locking the account itself.
Independent of API credentials, the account carries its own protections:
Enroll an authenticator, confirm a code to activate, disable with a valid code — with one-time recovery codes as the fallback. Verification is rate-limited against brute force.
Passwordless sign-in, or a second factor alongside the password.
Configured per connection — signed assertions on SAML, PKCE + nonce on OIDC.
Prove a domain with a DNS TXT record at _writ-sso-verify.{domain}; optionally set sso_enforced so members must sign in through SSO.
Your identity provider creates and deprovisions accounts on /scim/v2/*; deprovisioning immediately revokes the user’s live sessions and tokens.
secrets ▸ two syntaxes
Vault refs vs AI placeholders.
Two placeholder syntaxes, two channels — do not mix them. {{vault:key}} is the workflow-field syntax: it supports subfields like {{vault:name.username}}, and a bare credentials reference resolves to the password. {{secret:key}} is the AI-channel placeholder, for when an AI session needs a secret. A workflow field wants vault:; an AI instruction wants secret:.
faq