All docs pages

REST API#

Everything the TryFast app does goes through the same REST API, so your scripts can create projects, build and launch experiments and read results. AI agents get the same operations through the MCP server.

Authentication#

Credential How to send it Who uses it
API key tf_pk_... Authorization: Bearer tf_pk_... Scripts, CI, headless agents. Create one in the app under API keys, or get one when you create a workspace from a script.
Session cookie tryfast_session Sent by the browser The TryFast app, after sign-in.
Browser cookie tryfast_grant Sent by the browser The TryFast app for workspaces without an account.

API keys have one of three permissions, chosen when you create them: Read (experiments:read), Build (adds experiments:write; launches wait for approval) and Full (adds experiments:publish). A key never has more rights than the role of the person who created it, and it stops working when that person leaves the workspace. The key is shown once; TryFast stores only a hash.

The recovery token of an unsaved workspace (tfe_...) is not a credential for the API. It only saves the workspace to an account or opens it in a browser; give its link to the person.

Approving or rejecting an approval request accepts only a person's browser session, never a Bearer credential.

Quick start#

Who am I, and which workspaces can I see:

curl -s https://tryfast.eu/api/v1/me -H "Authorization: Bearer $TRYFAST_API_KEY"

Create a project and get its snippet:

curl -s https://tryfast.eu/api/v1/workspaces/ws_4fK9zQ2mPx/projects \
  -H "Authorization: Bearer $TRYFAST_API_KEY" -H 'Content-Type: application/json' \
  -d '{"name": "Acme shop", "domains": ["acme-shop.de", "www.acme-shop.de"]}'

curl -s https://tryfast.eu/api/v1/projects/prj_k2L9mQ0aZx/snippet \
  -H "Authorization: Bearer $TRYFAST_API_KEY"

Create a click goal and a complete draft experiment:

curl -s https://tryfast.eu/api/v1/projects/prj_k2L9mQ0aZx/goals \
  -H "Authorization: Bearer $TRYFAST_API_KEY" -H 'Content-Type: application/json' \
  -d '{"name": "Started checkout", "key": "started_checkout", "type": "click", "selector": "a.checkout"}'

curl -s https://tryfast.eu/api/v1/projects/prj_k2L9mQ0aZx/experiments \
  -H "Authorization: Bearer $TRYFAST_API_KEY" -H 'Content-Type: application/json' \
  -d '{
    "name": "Free shipping headline",
    "hypothesis": "Naming free shipping in the headline makes more visitors start checkout.",
    "targeting": {"pages": {"include": [{"match": "exact", "value": "/pricing"}]}},
    "variations": [
      {"name": "Control", "is_control": true},
      {"name": "Free shipping", "changes": [
        {"op": "set_text", "selector": "#hero h1", "value": "Free shipping on every order"}
      ]}
    ],
    "goals": [{"goal_id": "goal_Pu9rC4s3Ez", "role": "primary"}]
  }'

Launch it, then read the results:

curl -s -X POST https://tryfast.eu/api/v1/experiments/exp_7Hq2kD9xQa/start \
  -H "Authorization: Bearer $TRYFAST_API_KEY" -H 'Content-Type: application/json' -d '{}'

curl -s https://tryfast.eu/api/v1/experiments/exp_7Hq2kD9xQa/results \
  -H "Authorization: Bearer $TRYFAST_API_KEY"

With a Full key the start answers 200 and the experiment runs. With a Build key it answers 202 with "status": "awaiting_approval" and an approve_url for a person to open.

Start without an account#

A script can create a workspace without an account, with an optional project and API key:

curl -s https://tryfast.eu/api/v1/anonymous-workspaces \
  -H 'Content-Type: application/json' \
  -d '{"website_url":"https://shop.example","issue_api_key":true}'

The 201 response contains workspace, project and snippet, api_key (shown once), mcp_url, expires_at and claim_url. The claim_url carries the workspace's recovery token after #token=: it is for the person, never for the script or agent. Launches requested with the key wait for approval, and approving needs a signed-in person who opens the claim_url. Creation is limited to 3 workspaces per hour per network address. Creating a workspace accepts the Terms of Service and the Data Processing Agreement; the Privacy policy explains how we handle data. Workspaces and accounts explains the limits and the lifecycle of unsaved workspaces.

Conventions#

Endpoints#

The OpenAPI file documents every request and response. An overview:

Area Endpoints
Account GET /me, DELETE /me, GET /workspaces, POST /workspaces, GET, PATCH, DELETE /workspaces/{id}, GET /workspaces/{id}/usage, GET /workspaces/{id}/activity
Without an account POST /anonymous-workspaces, POST /anonymous-workspaces/redeem, POST /workspaces/{id}/claim, POST /workspaces/{id}/edit-token
Team (people only) GET, PATCH, DELETE /workspaces/{id}/members/..., GET, POST, DELETE /workspaces/{id}/invitations/..., GET /invitations/{token}, POST /invitations/{token}/accept
Access GET, POST /workspaces/{id}/api-keys, DELETE /api-keys/{id}, GET /workspaces/{id}/oauth-grants, DELETE /oauth-grants/{id}
Approvals GET /workspaces/{id}/approvals, GET /approvals/{id}, POST /approvals/{id}/approve, POST /approvals/{id}/reject
Projects GET, POST /workspaces/{id}/projects, GET, PATCH, DELETE /projects/{id}, GET /projects/{id}/snippet, GET /projects/{id}/install-status, POST /projects/{id}/install-check, POST /projects/{id}/inspect, GET /projects/{id}/events, POST /projects/{id}/estimate, GET /projects/{id}/activity, POST /projects/{id}/visitors/erase
Goals GET, POST /projects/{id}/goals, GET, PATCH, DELETE /goals/{id}
Audiences GET, POST /projects/{id}/audiences, GET, PATCH, DELETE /audiences/{id}
Experiments GET, POST /projects/{id}/experiments, GET, PATCH, DELETE /experiments/{id}, GET /experiments/{id}/preflight, POST /experiments/{id}/duplicate, GET /experiments/{id}/activity
Lifecycle POST /experiments/{id}/start, /pause, /resume, /traffic, /stop, /ship, /archive, /unarchive
Variations POST /experiments/{id}/variations, PATCH, DELETE /variations/{id}, PUT /variations/{id}/changes
Results GET /experiments/{id}/results, GET /experiments/{id}/results.csv, GET /experiments/{id}/estimate
Preview and editor POST /experiments/{id}/preview-links, POST /experiments/{id}/preview-links/reset, POST /experiments/{id}/editor-links
Reference GET /targeting/conditions (the targeting condition catalog), GET /docs/search?q=

The results format is explained in Statistics; the experiment format in Experiments, Variations and Targeting.

Errors#

Every error response (status 400 and above) has the same shape:

{
  "error": {
    "code": "validation_failed",
    "message": "The experiment has 2 problems.",
    "next_step": "Fix the listed fields and retry.",
    "doc_url": "https://tryfast.eu/docs/api#errors",
    "details": {},
    "field_errors": [
      {"field": "variations[1].js", "code": "compile_error", "message": "Unexpected \"}\"", "line": 3, "column": 1},
      {"field": "variations[0].weight_bp", "code": "invalid", "message": "Weights must sum to 10000 (got 9000)."}
    ]
  }
}

message says what happened, next_step what to do. details and field_errors appear when they apply. Server errors carry a details.request_id; include it when you write to support@tryfast.eu. The MCP server returns the same object inside its tool errors.

Code HTTP Meaning
invalid_request 400 Malformed JSON, wrong content type, an unknown field, a bad query parameter, or an Idempotency-Key on an operation that returns a secret.
unauthorized 401 No credential or an invalid one. The response carries WWW-Authenticate: Bearer realm="tryfast".
insufficient_scope 403 The credential is valid but its permission is too narrow (details.required_scope).
forbidden 403 Your role does not allow this, a Bearer credential tried to approve or reject, or an editor token was used for another project.
csrf_failed 403 A cookie request without same-origin proof.
claim_required 403 The action needs a workspace saved to an account or a signed-in person: custom JavaScript going live, members, invitations, more API keys, or approving in a workspace created by a script.
plan_limit_reached 403 A plan limit was reached. details holds limit (projects, running_experiments, monthly_tested_users or free_workspaces), current, max, plan and upgrade_url.
not_found 404 The object does not exist or is not visible to you. Objects of other workspaces always answer this.
method_not_allowed 405 The path exists but not with this method.
conflict 409 A unique key is taken or a precondition failed; details.reason is one of duplicate_key, last_owner, goal_in_use, goal_key_has_conversions, currency_locked, sole_owner_with_members, audience_in_use.
version_conflict 409 The object changed since you read it (details.current_version). Read it again and retry.
invalid_transition 409 The lifecycle action is not allowed from the current status (details.status, details.allowed).
experiment_running_immutable 409 The change would invalidate the statistics of a running experiment. Stop it and duplicate it instead.
approval_stale 409 The experiment or project changed after the approval was requested. The request was replaced; ask again.
idempotency_key_reused 409 The same Idempotency-Key was sent with a different request.
payload_too_large 413 The request body is over the limit.
validation_failed 422 The request is well formed but not valid. field_errors lists every problem.
preflight_failed 422 The experiment cannot start; details.checks lists the preflight checks.
bundle_too_large 422 Publishing would make the project's script larger than 200 KB uncompressed.
workspace_frozen 423 The unsaved workspace is paused after 30 days without use. Reading works; save it to an account to make changes.
rate_limited 429 Too many requests. Wait for the number of seconds in Retry-After.
fetch_failed 502 A page check could not fetch the URL (details.reason: blocked_address, timeout, too_large, status_<code>, redirect_off_domain or tls).
idp_unavailable 503 The sign-in service is not reachable. Sign-in only.
busy 503 The service is busy. Retry after the seconds in Retry-After.
internal 500 Something failed on our side. Include details.request_id when you contact support.

Field error codes: required, invalid, too_long, too_many, out_of_range, compile_error (with line and column), invalid_selector, invalid_regex, not_on_project_domain, unknown_goal, unknown_audience, unsafe_html, unsafe_url, missing_from_list, unknown_field. The field is a JSON path into your request, such as variations[1].changes[0].selector or targeting.audience.items[1].value.