{
  "openapi": "3.1.0",
  "info": {
    "title": "Testpad API",
    "version": "1.0",
    "description": "Welcome to the reference manual for the Testpad API.\n\nThis manual assumes you know what Testpad is, how the user interface works, and most importantly, what everything is called.\n\n<br>\n\n---\n**Initial Access**\n<br>\n<br>\n\nPlease note, for now:\n\n- the API feature has to be enabled, by Testpad Support, to be able to access it at all  \n- only account “owner” users can configure API access (i.e. issue access tokens)\n\n<br>\n\n---\n**An Evolving API**\n<br>\n<br>\n\nThe API is brand new for Testpad and will be updated in response to feedback from customers.\n\nPlease see the [Change Log](/openapi/change-log) for information on what might have changed since you last looked.\n\nPlease also see the [Unsupported Capabilities](/openapi/unsupported-capabilities) section for some discussion of what can't be done yet. \n\nAnd if you have a scenario or use-case in mind for the API that is not yet supported, please email support@testpad.com.\n\n<br>\n\n---\n**Mostly RESTful**\n<br>\n<br>\n\nThe API mostly conforms to RESTful principles.\n\n- HTTP **GET** requests are used to retrieve information.  \n- HTTP **POST** requests are used for actions like creating new folders, scripts, tests and test runs, where the server will generally be inventing new IDs for these objects and returning them in the response payload.  \n- HTTP **PATCH** requests are used for modifying parts of existing objects, supplying new (replacement) values for a subset of the keys of those objects.  \n- Testpad does not currently support HTTP **PUT** requests – the wholesale replacement of objects with a new object.  \n- Testpad does not currently support HTTP **DELETE** requests – these are likely to be supported in the future, but we are protecting you and ourselves from accidental data loss for the time being.\n\nTo do something with Testpad's API, you need to send a request to one of the API endpoints. These are the URL path that the request is being directed to, and should be thought of as sending a message to the relevant 'owning' object.\n\nFor example, a GET request to `/projects/` is to fetch data on all projects.  \n\nOr, a POST request to `/projects/:p/folders` is a create action on that project’s collection of folders, i.e. to create a new folder in the list of folders.\n\n---\n\n## API Server\n\n<br>\n\n**HTTPS only**\n<br>\n<br>\n\nThe API only responds to requests sent securely using HTTPS.\n\nClients must validate SSL/TLS certificates (including the certificate chain and hostname) to ensure they are communicating with Testpad. Failure to properly validate the certificate exposes your integration to man-in-the-middle attacks, where malicious actors could intercept, modify, or steal your data.\n\n<br>\n\n---\n**Domain**\n<br>\n<br>\n\nFind the API Server at `https://api.testpad.com`.\n\nNote the subdomain is `api` and not your account ID.\n\n<br>\n\n---\n**Paths**\n<br>\n<br>\n\nAll API endpoints are hosted on paths that begin with the common prefix `/api/v1/`.  \n\nAt some point in the future, if backwards incompatible changes are introduced, these will be introduced at a new version number, e.g. `/api/v2/`.  \n\nHowever, in general, every attempt will be made to keep changes and new features backwards compatible, and while this holds true, the API will continue to be hosted at `/api/v1/`.\n\n\n"
  },
  "servers": [
    {
      "url": "https://api.testpad.com/api/v1"
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "All requests to the Testpad API must include the Authorization HTTP Header that supplies an API access token for your account.\n\n<br>\n\n**HTTP Header**\n<br>\n<br>\n\n\nThe `Authorization` header value must have the format: `Authorization: apikey <TOKEN>`\n\nThe token must be preceded by the prefix `apikey` to be considered a valid authorization value. It is envisaged that alternative authorization schemes may be added in the future, and these would be differentiated with a different prefix.\n\n<br>\n\n**API Access Token**\n<br>\n<br>\n\nCreate and manage API Access Tokens on the API settings page in the Testpad app (account Owners only).\n\nThe API settings page is found in the Settings section of the navigation links in the bottom left of the main project view.\n\nIf you cannot see a link to API settings (and you are an account Owner), then your account probably has not yet been enabled for API access – please contact support@testpad.com to request access.\n\n<br>\n\n**Enabling API access**\n\nAPI access is off by default. Two things must both be true for any request to succeed:\n\n1. Your account must have the API feature switched on by Testpad. Until then, every request returns `403 Forbidden` with `{\"error\": \"The API is not available for this account.\"}` — contact support@testpad.com to request access.\n2. An account Owner must turn API Access on for the account (on the API settings page). While it is off, every request returns `403 Forbidden` with `{\"error\": \"API access is turned off for this account.\"}`.\n\nA request with a missing, malformed, expired, deleted or disabled token returns `401 Unauthorized` instead.\n\nSecurity notes:\n\n- Tokens are 64-character case-sensitive strings of letters and digits (a-z, A-Z, 0-9).  \n- Tokens are generated by Testpad using a cryptographically-strong random number generator.\n- An account may generate and make use of multiple tokens, e.g. to give different teams different tokens.\n- For now, the API is secured with tokens that grant access to all API capabilities and across all projects. It is expected that more fine-grained control such as role-specific and project-specific access keys will be supported in the future.\n"
    },
    {
      "name": "◉ Project Endpoints"
    },
    {
      "name": "◉ Folder Endpoints"
    },
    {
      "name": "◉ Script Endpoints"
    },
    {
      "name": "◉ Run Endpoints"
    },
    {
      "name": "◉ Note Endpoints"
    },
    {
      "name": "Common URL Parameters",
      "description": "<br>\n\nMultiple endpoints are available for retrieving folders and scripts, and their responses can contain substantial amounts of data. To help clients request only the information they need, these endpoints support a common set of URL parameters.\n\nIf not supplied in requests, default values for these parameters will be assumed. Note that the defaults used vary depending on whether the request is for a single script object or a folder of several script objects.\n\n<br>\n\n---\n**subfolders** *(enum)*\n<br>\n\nControls whether the nested contents of a retrieved folder should also be retrieved.  \nOnly applicable to endpoints retrieving folder items.\n\n- `none` – the contents of any subfolders in the target folder are not included\n- `all` - *(default)* – the contents, and recursively nested contents, of a retrieved folder are included\n\n<br>\n\n---\n**scripts** *(enum)*\n<br>\n\nControls how much of the script meta data to include (e.g., description, report comments, creation date).\n\n- `terse` *(default for folder endpoints)* – only includes the script’s name and ID  \n- `full` *(default for script endpoints)* – adds all the basic information about the script, including description, creation date, report comments, and whether the script is archived.\n\nThis parameter does **not** affect inclusion of tests, fields, runs, or results.\n\n<br>\n\n---\n**tests** *(enum)*\n<br>\n\nControls whether or not (and in which format) the tests of a script are included in the `tests` field of script objects.\n\n- `none` *(default for folder endpoints)* – excludes the `tests` field entirely  \n- `full` *(default for script endpoints)* – includes tests as a list of objects with fields: `id`, `indent`, `text`, and `tags`  \n- `list` – includes tests as a list of strings, with leading whitespace to show indentation  \n- `text` – includes tests as a single string, with each test separated by newlines and indentation preserved\n\n<br>\n\n---\n**testswith** *(list of enum)*\n<br>\n\nModifies how tests are returned when `tests` are included.\nMost useful when formatting tests as lists or long strings.\n\nCombine multiple values with commas (e.g., `testswith=hashtags,ids`).\n\n- `none` *(default)* – test text is included as is, without decoration\n- `hashtags` – includes tags as Twitter-style hashtags appended to each test text string. If tests are being returned as objects, the `tags` field is omitted.\n- `ids` – prefixes each test text string with the test ID. Ignored if tests are objects.  \n- `_ids` – same as `ids`, but right-aligns ID numbers (4 digits wide). Ignored if tests are objects.\n\n<br>\n\n---\n**fields** *(enum)*\n<br>\n\nControls the contents of the `fields` field of script objects.\n\nThis field defines the set of headers available for each run header (e.g., run number, tag filters, custom fields etc).\n\n- `none` *(default for folder endpoints)* – field information is not returned  \n- `full` *(default for script endpoints)* – fields are defined as a list of objects with:  \n  - `id`  \n  - `label`  \n  - `show` (boolean)  \n- `list` – fields are returned as a simple list of all labels in use  \n- `list_shown` – same as `list`, but only includes fields marked as *shown*\n\n<br>\n\n---\n**runs** *(enum)*\n<br>\n\nControls how test run data is included in the `runs` field of script objects.\n\n- `none` *(default for folder endpoints)* – data on the test runs are not included\n- `full` *(default for script endpoints)* – script objects will contain full run definitions, including state, tester, and header values, but not results (which depend on the `results` parameter below)\n- `terse` – returns each test run as a small object with its `id` and a `label` (a one-line string summarizing the run number and header values)  \n\n<br>\n\n---\n**results** *(enum)*\n<br>\n\nControls whether results data is included in the response (but only if the `runs` field is being included).\n\nResults are included as the `results` field inside run objects (nested in `runs`).\n\n- `none` *(default for folder endpoints)* – the `results` field is excluded from run objects\n- `full` *(default for script endpoints)* – results are included as a list of result objects, with fields:  \n  - `result` *(e.g., `pass`, `fail`, `query`, `blocked`, `skip`)*  \n  - `comment` - string\n  - `issue` - string\n\nOnly non-empty result objects are included.\n\n<br>\n\n---\n**progress** *(enum)*\n<br>\n\nControls how progress statistics are included in the `progress` field of folders, scripts, and runs.\n\n- `none` *(default for folder endpoints)* – `progress` fields are excluded\n- `terse` *(default for script endpoints)* – `progress` fields are included and contain:\n  - `total` - integer - the number of tests that have a result value recorded, i.e. the total tried so far\n  - `pass` - integer - the count of results marked as pass\n  - `fail` - integer - the count of results marked as fail\n  - `block` - integer - the count of results marked as blocked\n  - `query` - integer - the count of results marked as queried\n  - `summary`: a human-readable summary string of these counts\n- `full` – returns progress as an object with individual count fields as well as percentage values. The percentage values are rounded to the nearest integer, set to be at least 1% if >0, and adjusted to all sum to 100% (thus making them useful for e.g. CSS %width values in a dashboard display).\n\n<br>\n\n"
    },
    {
      "name": "Supplying Test Text",
      "description": "Test text can be provided as part of creating new scripts via these endpoints:\n\n- `POST /projects/:p/scripts` — create a script in a project  \n- `POST /projects/:p/folders/:f/scripts` — create a script in a folder\n\n**Choice of format**:\n\n- As a **string**: a block of text with a test per line  \n- As a **list of strings**: a test per item in the list  \n- As a **list of objects**: a test per item in the list, with fields such as `id`, `text`, `indent`, `tags`, `notes`, `results`\n\nKey details:\n\n- When supplying tests as a string or list of strings, **IDs cannot be specified**.  \n- Indentation can be inferred from leading whitespace (when `indent` is not supplied).  \n- Tags can be supplied using hashtags at the end of the test.  \n- Multiline cells are supported in **list of strings** (via `\\n`), but **not** supported when supplying tests as a single string.  \n- Results can be supplied per-test when using the **list of objects** format."
    },
    {
      "name": "Supplying Test Runs",
      "description": "Test runs (columns) can be created using several endpoints:\n\n- `POST /projects/:p/scripts` — create a script in a project, including one or more test runs  \n- `POST /projects/:p/folders/:f/scripts` — create a script in a folder, including one or more test runs  \n- `POST /scripts/:s/runs` — add a single test run to a script\n\nWhere multiple test runs are being supplied (the create new script endpoints), the `runs` field will be a list of the run objects.  \nWhere a single test run is being supplied (the add a single test run endpoint), the payload is a single test run object.\n\n**Test Run object** (fields):\n\n- `id` (optional)  \n- `headers` (object) (optional)  \n- `completed` (boolean or `\"auto\"`) (optional)  \n- `results` (object) (optional)\n- `assignee` (string) (optional) — assign the run to an existing team member by **email address**\n\nNotes:\n\n- The ID can only be supplied when **creating scripts**. Once a script exists, new IDs will be created for you by Testpad.  \n- `assignee` takes the email address of an existing team member. It sets the assignee only and does **not** send a delegation/invite email; an unknown email is rejected. (Assigning to anyone/guest, aliases, and notifications may be offered later.)  \n- `headers` are optional everywhere. A run can have none, only headers, or headers and results. Any header field values that aren't defined for the script are ignored.  \n- Built-in header fields are read-only (`_run`, `_createdDate`, `_createdTime`).  \n- Any header fields that do not receive a value will be left empty.  \n- Any header field values supplied in the test run that do not exist in the script will be ignored.\n"
    },
    {
      "name": "Supplying Test Results",
      "description": "Test results can be provided to Testpad as part of creating Scripts using these endpoints:\n\n- `POST /projects/:p/scripts`  \n- `POST /projects/:p/folders/:f/scripts`\n\nResults can be provided either **together with a test row** (per row), **together with a test run column** (per column), or **individually**.\n\n**Per-row (in test objects)**:\n- `results` as a **list** maps in order to the defined test runs.  \n- `results` as an **object** uses test run IDs as keys.\n\n**Per-column (in run objects)**:\n- `results` is an **object** with test IDs as keys.\n\n**Individual result formats**:\n- **String**: `\"pass\"`, `\"fail\"`, `\"blocked\"`, `\"query\"`, `\"skip\"`  \n- **List**: `[result, comment, issue]` (triple)  \n- **Object**: `{ \"result\": \"...\", \"comment\": \"...\", \"issue\": \"...\" }`"
    },
    {
      "name": "Guidance on Usage",
      "description": "Practical guidance for building and maintaining good scripts through the API — especially when generating them programmatically. Following these keeps scripts readable in the Testpad UI and pleasant to run, retest and share.\n\n**Don't make scripts too short.**\nA script is not a single test case — it's a checklist or outline of many related tests. Put a whole area of testing in one script and use indentation to structure it: a title row, then indented child rows, nested as deep as helps. Avoid the common mistake of one or two tests per script. It wastes what the script editor is built for — progress bars, the outline, collapse-for-overview, fast keyboard entry — and it clogs the project view with hundreds of scripts to wade through. Prefer a handful of rich, collapsible scripts over many tiny ones. (Scripts hold up to 2000 rows; \"too long\" is rarely the problem.)\n\n**Re-run build to build with retests, not new scripts.**\nTo test the next build of the same thing, don't create another script — start a *retest*:\n\n```\nPOST /scripts/{id}/runs?retest={previous_run_id}\n```\n\nThis adds a new run to the same script, linked to the previous one, so each build is a fresh column of results under the one script and the history stays together. Supply `results` to record as you go, or omit them to start blank.\n\n**Copy a script to branch it release to release.**\nThere is no dedicated copy endpoint yet. To fork a script for a new release, read it and write it back: `GET /scripts/{id}` (or `GET /scripts/{id}/tests`) to fetch its tests, then `POST .../scripts` with those tests as the new script's `tests`. Results are not carried over unless you include them.\n\n**Writing test prompts — shorter is usually better.**\nA row is already understood to *be* a test, so you rarely need to say \"Verify…\", \"Check…\" or \"Ensure…\" — just state the thing. Word the prompt so the expected outcome is implied:\n\n- \"Check that paying with a declined card shows an error\" → **\"Declined card shows a clear error\"**\n- \"Verify the user can log in with a correct password\" → **\"Correct password logs in\"**\n\nKeep each row to a single line, and lean on the hierarchy instead of long sentences: a parent row sets the context, child rows carry the specifics.\n\n**Tags — sparingly, and on parents.**\nOnly add tags if you will actually filter on them, or they carry real metadata; tags added for their own sake are just noise. A tag on a title (parent) row applies to all of its child rows — so tag the section once. You never need to tag every line.\n"
    },
    {
      "name": "Progress Objects",
      "description": "\nTest result statistics for test runs, scripts, folders and folders of folders, are represented as **progress objects**.\n\n- **Terse**: `total`, `pass`, `fail`, `block`, `query`, `summary`  \n- **Full**: adds percentages (`passPc`, `failPc`, `blockPc`, `queryPc`), `tried`, `triedPc`, `remain`, and `remainPc`\n\nThe percentages are rounded to the nearest integer, adjusted to be at least 1 when non-zero, and such that they always sum to 100.\n\n"
    },
    {
      "name": "Error Handling",
      "description": "During development of client code (code that makes requests to the Testpad API), it is recommended to catch all errors and to print their details out to the console or a log file for inspection of what went wrong.\n\nOnce in production, client code might scale back on the verbosity of its error reporting, however, it should nevertheless always maintain specific handling for the following error conditions, which, in theory, could happen to any request:\n\n<br>\n\n`401 Unauthorized`\n\nReturned when no token is supplied, the Authorization header is malformed, or the token is invalid, expired, deleted or disabled.\n\n<br>\n\n`403 Forbidden`\n\nReturned when the token is valid but access is not permitted: API access is switched off for the account, the account is not enabled for the API, or the token does not have permission for the project being accessed. The body is `{\"error\": \"...\"}`.\n\n<br>\n\n`429 Too Many Requests`\n\nThe error 429 Too Many Requests will be returned to any request that exceeds Testpad's rate limits. Please see [Rate Limiting](/openapi/rate-limiting) for details.\n\nIf your code receives this error, it must wait for the indicated amount of time before trying again.\n"
    },
    {
      "name": "Rate Limiting",
      "description": "<br>\n\nThe API protects itself from excessive load by limiting the rate at which received requests will be processed.\n\nThere are several types of rate limit applied, and requests must 'pass' all of these to be successful. I.e. if any one of these different rate limiters is triggered, then the request will be rejected.\n\n<br>\n\n**Custom Limits**\n\nThe default rate limits are defined below and are subject to change. For now, all subscription plans have the same limits, but this may well change in the future, with the larger plans obviously allowing for higher API usage requirements.\n\nIf you are finding your rate limits insufficient, please contact support@testpad.com to discuss what you are trying to do and the possibility of increasing these limits for your account.\n\n<br>\n\n---\n**Rejected Requests**\n<br>\n<br>\n\nRequests rejected for exceeding a rate limit will get a `429 TOO MANY REQUESTS` response.\n\nThe response will contain the `Retry-After` HTTP header, specifying how many seconds until a repeat attempt will be successful.\n\n<br>\n\n---\n**Global (Load Balancer) Rate Limits**\n<br>\n<br>\n\nTestpad deploys two types of rate limiter, a global rate limiter that applies to all requests, along with several request-specific limiters that apply different rates for different types of request.\n\nThe global rate limit is **1000 requests per hour**, which is an average of **1 request every 3 seconds**, but allows for bursts of up to **100 requests at a time**.\n\nIf 100 requests arrived back to back, then the client would have to wait 300 seconds before being able to continue (i.e. maintaining the average rate of 1000 requests per hour).\n\n<br>\n\n---\n**GET (Fetch) Rate Limits**\n<br>\n<br>\n\nFor GET (fetch) requests, the API applies two rate limits:\n\n- **1GB** of response data per day, and  \n- a burst rate limit of **50MB every 3 minutes**\n\nWithin any 3 minute period, Testpad will not respond if more\nthan 50MB of data have already been returned.\n\nAnd within any 24 hour period, Testpad will not respond if more than 1GB of data has already been returned.\n\n<br>\n\n---\n**POST (Write) Rate Limits**\n<br>\n<br>\n\nFor POST (write) requests, the API applies limits to how many scripts\nand how many test runs can be created:\n\n- **500 new scripts per month**, and  \n- a burst limit of **100 scripts per hour**\n  \n  <br>\n\n- **1000 new test runs per month**, and  \n- a burst limit of **200 test runs per hour**\n\n<br>\n\n---\n**How the Rate Limits work**\n<br>\n<br>\n\nEach rate limiter has a capacity that it spends as it lets requests through, and that recharges continuously over time. A limiter rejects requests once its capacity drops below zero.\n\nThe `Retry-After` header on a `429` response tells you how many seconds until the relevant limiter has recharged back above zero. So you don't have to wait a whole month after hitting a monthly limit: you only wait until enough capacity has recharged.\n\nOne thing to watch: a request made the instant a limiter turns positive is likely to drive it straight back negative and block what follows. If you're near a limit, back off a little beyond `Retry-After`.\n"
    },
    {
      "name": "Webhooks",
      "description": "Webhooks let Testpad tell your systems about things as they happen, instead of you polling the API to find out.\n\nYou register a URL, choose which events you care about, and Testpad sends an HTTP POST to that URL whenever a matching event occurs. A test gets marked fail, a run is completed, a teammate is invited: your endpoint hears about it within seconds.\n\n<br>\n\n---\n**When to use webhooks (and when not to)**\n<br>\n<br>\n\nReach for webhooks when you want to react to changes: post failures to Slack, open a ticket when a run completes, sync run progress to a dashboard.\n\nKeep using the API directly when you want to read or change data on your own schedule. Webhooks push; the API pulls. Most integrations use both.\n\n<br>\n\n---\n**The flow**\n<br>\n<br>\n\n1. You add a webhook in the Testpad app and pick the topics to subscribe to.\n1. Something happens in your account that matches one of those topics.\n1. Testpad POSTs a signed JSON payload to your URL.\n1. Your endpoint verifies the signature, returns a `2xx` quickly, and does its work.\n\n<br>\n\n---\n**The body is always an array**\n<br>\n<br>\n\nEvery delivery is a JSON **array** of one or more events. A single action can produce more than one event (filling a run with mixed pass and fail results is one POST carrying both), so your handler should loop the array and switch on each event's `_event.topic`.\n\nThat one rule keeps your handler correct whether a delivery holds one event or fifty. See [Webhook Payload](/openapi/webhook-payload) for the shape, and [Webhook Events](/openapi/webhook-events) for the list of topics.\n\n<br>\n\n---\n**Getting set up**\n<br>\n<br>\n\nWebhooks are configured per account by an Owner, on the webhooks settings page in the app. See [Subscribing to Webhooks](/openapi/subscribing-to-webhooks) to get your first one running, then [Verifying Webhooks](/openapi/verifying-webhooks) before you trust anything you receive.\n"
    },
    {
      "name": "Subscribing to Webhooks",
      "description": "Webhooks are set up in the Testpad app, not through the API. You manage them on the webhooks settings page, found in the Settings section of the navigation links in the bottom left of the main project view.\n\nAs with API tokens, only account **Owners** can configure webhooks.\n\nWebhooks require your account to have **API access** — the same access the API needs (contact support@testpad.com if it hasn't been enabled for your account yet). The webhooks settings page then has an account-wide **Enable Webhooks** switch that must be on, and each webhook has its own enable/disable. If your account's API access is withdrawn, or webhooks are switched off, nothing is delivered (including the ping test) and any queued deliveries are dropped.\n\n<br>\n\n---\n**Adding a webhook**\n<br>\n<br>\n\nA webhook has three parts:\n\n- a **URL** to deliver to,\n- a **topic filter** that chooses which events it receives,\n- a **secret**, used to sign deliveries so you can verify they came from Testpad.\n\nYou can also set an optional **token**, a fixed string Testpad sends back in a header. Some setups find that easier to check than a signature. You can use either, both, or just the signature.\n\n<br>\n\n---\n**The URL must be public HTTPS**\n<br>\n<br>\n\nTestpad only delivers to public `https://` URLs on port 443. This is deliberate. The destination is yours to choose, so Testpad treats it as untrusted and refuses anything that could be used to reach inside our network or yours:\n\n- the scheme must be `https`, and the port `443`,\n- the host must resolve to a public address (no loopback, private ranges, link-local, or cloud metadata addresses),\n- credentials in the URL are not allowed,\n- redirects are not followed.\n\nIf you're developing locally, use a tunnel (for example ngrok or Cloudflare Tunnel) to expose a public HTTPS URL that forwards to your machine.\n\n<br>\n\n---\n**Choosing topics**\n<br>\n<br>\n\nA webhook subscribes to one or more topics. Topics name what happened, in `entity.action` form, like `result.fail` or `run.completed`.\n\nFilters can use a wildcard:\n\n- `*` matches everything,\n- `result.*` matches every result topic (`result.pass`, `result.fail`, and so on),\n- `result.fail` matches only that one topic.\n\nSubscribe to the least you need. A delivery only ever contains events your filter matched, so a `result.fail` subscriber never sees passes. The full list is in [Webhook Events](/openapi/webhook-events).\n\n<br>\n\n---\n**Testing it**\n<br>\n<br>\n\nEach webhook has a **ping** button on the settings page. It sends a single `ping` event to that one webhook, regardless of its topic filter, so you can confirm your endpoint is reachable and your signature check works before any real events flow.\n\n<br>\n\n---\n**Disabling, and auto-disable**\n<br>\n<br>\n\nYou can disable a webhook at any time on the settings page.\n\nTestpad will also disable a webhook for you if it fails for a sustained run of deliveries. This stops a dead endpoint from generating retries forever. A webhook disabled this way is flagged so you can spot it, fix the endpoint, and switch it back on. See [Webhook Delivery](/openapi/webhook-delivery) for the exact behavior.\n"
    },
    {
      "name": "Webhook Events",
      "description": "A topic names what happened, in `entity.action` form. Your webhook's filter decides which of these it receives, and each event carries its topic in `_event.topic`. Switch on that.\n\nSubscribe with an exact topic (`result.fail`), a whole family (`result.*`), or everything (`*`):\n\n| Filter | Matches |\n|---|---|\n| `*` | Every event. |\n| `result.*` | Any result event. |\n| `run.*` | Any run event. |\n| `user.*` | Any user event. |\n\n<br>\n\n---\n**Result topics**\n<br>\n<br>\n\nSent as test results are set, changed, or cleared. When one action sets several results at once, each outcome arrives as a single event whose `results` array holds every test of that outcome, so a bulk fill is a few events, not hundreds.\n\n| Topic | Sent when |\n|---|---|\n| `result.pass` | A test was marked pass. |\n| `result.fail` | A test was marked fail. |\n| `result.block` | A test was marked blocked. |\n| `result.query` | A test was marked query. |\n| `result.skip` | A test was excluded from the run. |\n| `result.clear` | A test's result was cleared. |\n\n<br>\n\n---\n**Run topics**\n<br>\n<br>\n\n| Topic | Sent when |\n|---|---|\n| `run.started` | A run got its first result and moved to started. |\n| `run.completed` | A run was completed. |\n| `run.reopened` | A completed run was reopened. |\n| `run.assigned` | A run's assignee changed, including being unassigned. |\n| `run.cleared` | Every result in a run was cleared at once. |\n\n<br>\n\n---\n**User topics**\n<br>\n<br>\n\n| Topic | Sent when |\n|---|---|\n| `user.invited` | Someone was invited to the account. |\n| `user.joined` | An invited person verified their email and joined. |\n| `user.removed` | A member was removed from the account. |\n| `user.role_changed` | A member's role was changed. |\n\n<br>\n\n---\n**Ping**\n<br>\n<br>\n\n| Topic | Sent when |\n|---|---|\n| `ping` | You pressed ping on the webhooks settings page. Sent to that one webhook only, whatever its filter. |\n\n<br>\n\n---\n**Who caused it**\n<br>\n<br>\n\nEvery event names the actor in `context.actor`. A team member shows their real name and email. Work done by Testpad Support on your behalf shows as \"Testpad Support\", and anything done through the API shows as \"API\". Support and API actions carry no email. Events never include details of any other account.\n\n<br>\n\n---\n**What you won't get**\n<br>\n<br>\n\nCopying a script, retesting, moving content between projects, and account-to-account migration are all silent. They re-home existing data rather than re-running it, so they don't fire result or run events. Seeding the example project on a new account is silent too.\n"
    },
    {
      "name": "Webhook Payload",
      "description": "Every delivery is an HTTP POST with a JSON body and a small set of headers. The body is an array of events; each event shares a common envelope and then carries data specific to its topic.\n\n<br>\n\n---\n**Headers**\n<br>\n<br>\n\n| Header | Description |\n|---|---|\n| `Content-Type` | Always `application/json`. |\n| `User-Agent` | `Testpad-Webhooks/1`. |\n| `X-Testpad-Delivery-Id` | A unique ID for this delivery. It stays the same if the delivery is retried, so use it to de-duplicate. |\n| `X-Testpad-Timestamp` | Unix time (seconds) when the delivery was signed. Used in the signature and to reject stale deliveries. |\n| `X-Testpad-Signature` | The HMAC signature, `sha256=` followed by the hex digest. See [Verifying Webhooks](/openapi/verifying-webhooks). |\n| `X-Testpad-Token` | The token you set on the webhook, if you set one. Absent otherwise. |\n\n<br>\n\n---\n**The event envelope**\n<br>\n<br>\n\nEvery event, whatever its topic, has these fields:\n\n- **`_about`** is a constant block naming Testpad and your account. Useful when one endpoint takes webhooks from several sources.\n- **`_api_version`** is the payload schema version, currently `1`. We'll bump it if the shape changes.\n- **`_event`** is this event's metadata: its `topic`, a unique `id` (stable on re-send, so you can de-duplicate per event), the `webhook` subscription ID that matched, and the `ts` it was generated.\n- **`context`** always has `account` and `actor` (who caused the event). `result.*` events also add `project`, `script` and `run` — here the run is *context* for the results, so it's a lean reference (no per-test `results`; those are in the event's own `results`). `run.*` events add `project` and `script` only, because the run itself is the event's subject (see below), carried in full at the top level.\n\nThen, depending on the topic, the event carries its own data at the top level: `results` for `result.*`, `run` for `run.*`, and `member` for `user.*`. Ping events add nothing beyond the envelope.\n\n<br>\n\n---\n**Example**\n<br>\n<br>\n\nA `result.fail` delivery with one failing test:\n\n```json\n[\n  {\n    \"_about\": {\n      \"notice\": \"Webhook event from Testpad (https://testpad.com) for the Acme account.\",\n      \"contact\": \"support@testpad.com\"\n    },\n    \"_api_version\": 1,\n    \"_event\": {\n      \"topic\": \"result.fail\",\n      \"id\": \"9f1c2a7b3d4e5f60\",\n      \"webhook\": \"8cc7a7a9\",\n      \"ts\": \"2026-06-12T16:18:49Z\"\n    },\n    \"context\": {\n      \"account\": { \"id\": \"acme\", \"name\": \"Acme\", \"url\": \"https://acme.testpad.com/\" },\n      \"actor\": { \"name\": \"Jo Tester\", \"email\": \"jo@acme.com\" },\n      \"project\": { \"id\": 4, \"name\": \"Checkout\", \"url\": \"https://acme.testpad.com/4\" },\n      \"script\": { \"id\": 12, \"name\": \"Smoke tests\", \"url\": \"https://acme.testpad.com/script/12\" },\n      \"run\": { \"id\": 3, \"state\": \"started\", \"url\": \"https://acme.testpad.com/script/12/run/3\" }\n    },\n    \"results\": [\n      {\n        \"test\": { \"id\": 2, \"text\": \"Pay with a declined card\", \"tags\": \"billing\", \"url\": \"https://acme.testpad.com/script/12/#2\" },\n        \"result\": { \"result\": \"fail\", \"comment\": \"500 on submit\", \"issue\": \"1234\" }\n      }\n    ]\n  }\n]\n```\n\n<br>\n\n---\n**Switch on the topic**\n<br>\n<br>\n\nRead each event's `_event.topic` and dispatch on it. Don't rely on the array holding only one event or only one topic: a single delivery can batch several events you subscribed to. Treat anything you don't recognize as something to skip, so new topics added later don't break your handler.\n"
    },
    {
      "name": "Verifying Webhooks",
      "description": "Anyone can POST JSON at your URL. Before you act on a delivery, check that it actually came from Testpad and hasn't been tampered with or replayed. Every delivery is signed with the secret you set on the webhook.\n\n<br>\n\n---\n**How the signature works**\n<br>\n<br>\n\nTestpad computes an HMAC-SHA256 over the timestamp and the exact request body, keyed by your webhook secret:\n\n```\nsigned   = \"<X-Testpad-Timestamp>\" + \".\" + <raw request body>\nsignature = \"sha256=\" + hex( HMAC_SHA256(secret, signed) )\n```\n\nand sends the result in `X-Testpad-Signature`. To verify, recompute it and compare.\n\nThree things matter:\n\n- **Use the raw body.** Sign the bytes exactly as received, before any JSON parsing or re-serialization. Re-encoding changes whitespace and key order, and the signature won't match. Most frameworks need to be told to hand you the raw body.\n- **Compare in constant time.** Use a constant-time comparison (`hmac.compare_digest`, `crypto.timingSafeEqual`), not `==`, so you don't leak the answer through timing.\n- **Check the timestamp.** Reject deliveries whose `X-Testpad-Timestamp` is too far from now (five minutes is a sensible window). This stops an old, captured delivery from being replayed.\n\n<br>\n\n---\n**Python (Flask)**\n<br>\n<br>\n\n```python\nimport hashlib\nimport hmac\nimport json\nimport time\n\nfrom flask import Flask, request, abort\n\napp = Flask(__name__)\nSECRET = b\"your-webhook-secret\"\nTOLERANCE = 5 * 60  # seconds\n\ndef verify(secret, raw_body, timestamp, signature):\n    try:\n        if abs(time.time() - int(timestamp)) > TOLERANCE:\n            return False\n    except (TypeError, ValueError):\n        return False\n    if not signature.startswith(\"sha256=\"):\n        return False\n    signed = timestamp.encode() + b\".\" + raw_body\n    expected = hmac.new(secret, signed, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(signature[len(\"sha256=\"):], expected)\n\n@app.post(\"/webhooks/testpad\")\ndef testpad_webhook():\n    raw = request.get_data()  # raw bytes, before parsing\n    ok = verify(\n        SECRET,\n        raw,\n        request.headers.get(\"X-Testpad-Timestamp\", \"\"),\n        request.headers.get(\"X-Testpad-Signature\", \"\"),\n    )\n    if not ok:\n        abort(400)\n\n    for event in json.loads(raw):\n        handle(event[\"_event\"][\"topic\"], event)  # your dispatch\n    return \"\", 200\n```\n\n<br>\n\n---\n**Node (Express)**\n<br>\n<br>\n\n```javascript\nconst crypto = require(\"crypto\");\nconst express = require(\"express\");\n\nconst app = express();\nconst SECRET = process.env.TESTPAD_WEBHOOK_SECRET;\nconst TOLERANCE = 5 * 60; // seconds\n\n// hand us the raw body, not a parsed object\napp.use(\"/webhooks/testpad\", express.raw({ type: \"application/json\" }));\n\nfunction verify(secret, rawBody, timestamp, signature) {\n  const ts = Number(timestamp);\n  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > TOLERANCE) return false;\n  if (!signature.startsWith(\"sha256=\")) return false;\n\n  const expected = crypto\n    .createHmac(\"sha256\", secret)\n    .update(timestamp + \".\")\n    .update(rawBody)\n    .digest(\"hex\");\n\n  const got = Buffer.from(signature.slice(\"sha256=\".length));\n  const want = Buffer.from(expected);\n  return got.length === want.length && crypto.timingSafeEqual(got, want);\n}\n\napp.post(\"/webhooks/testpad\", (req, res) => {\n  const ok = verify(\n    SECRET,\n    req.body, // a Buffer, thanks to express.raw\n    req.get(\"X-Testpad-Timestamp\") || \"\",\n    req.get(\"X-Testpad-Signature\") || \"\"\n  );\n  if (!ok) return res.sendStatus(400);\n\n  for (const event of JSON.parse(req.body)) {\n    handle(event._event.topic, event); // your dispatch\n  }\n  res.sendStatus(200);\n});\n```\n\n<br>\n\n---\n**The token, if you prefer**\n<br>\n<br>\n\nIf you set a token on the webhook, Testpad sends it back in the `X-Testpad-Token` header on every delivery. Comparing that to your expected value is a simpler check than a signature, though weaker: it travels in the clear on each request. The signature is the stronger control, so verify it even if you also check the token. TLS protects both in transit.\n"
    },
    {
      "name": "Webhook Delivery",
      "description": "Testpad delivers each webhook with one HTTP POST, retries it if it looks like a temporary problem, and gives up (or disables the webhook) if it keeps failing. Here's what to expect so you can build a handler that copes.\n\n<br>\n\n---\n**Respond fast, work later**\n<br>\n<br>\n\nReturn a `2xx` as soon as you've verified and stored the delivery, then do the real work asynchronously. Testpad waits only a few seconds for a response (5 seconds to connect, 10 to read). If your handler does slow work inline before responding, deliveries will time out and be retried even though you got them.\n\n<br>\n\n---\n**What counts as success**\n<br>\n<br>\n\nAny `2xx` response means delivered. Anything else is a failure, and Testpad decides whether to retry based on why:\n\n- **Retried:** a network error or timeout, any `5xx`, or `408` and `429`. These look temporary.\n- **Not retried:** other `4xx` responses. These mean the request reached you and you rejected it, so retrying won't help. Fix the endpoint and the next event will go through.\n\nRedirects are not followed, so a `3xx` is treated as a non-success.\n\n<br>\n\n---\n**Retry schedule**\n<br>\n<br>\n\nA retryable failure is retried with exponential backoff and a little random jitter, up to 8 times. The delay starts around a minute and doubles each time, capped at about 15 minutes:\n\n```\n~1m, ~2m, ~4m, ~8m, ~15m, ~15m, ~15m, ~15m\n```\n\nAfter the last retry (a bit over an hour of attempts), the delivery is given up.\n\nIf your `429` or `503` response includes a `Retry-After` header (seconds, or an HTTP-date), Testpad waits at least that long before the next attempt, capped at about 15 minutes.\n\n<br>\n\n---\n**At-least-once, so be idempotent**\n<br>\n<br>\n\nA delivery can arrive more than once: a retry might fire after your endpoint actually succeeded but failed to respond in time. Deduplicate on `X-Testpad-Delivery-Id`, which stays the same across retries of the same delivery. Each event inside also has its own stable `_event.id`.\n\nThere's no ordering guarantee. A `run.completed` can land before a `result.fail` from the same run. Don't assume deliveries arrive in the order the events happened.\n\n<br>\n\n---\n**Auto-disable**\n<br>\n<br>\n\nA delivery only counts as failed once its own retries are exhausted (or it's rejected with a non-retryable error). If 20 such failed deliveries happen in an unbroken row, Testpad disables the webhook and flags it as suspect on the settings page. This keeps a dead endpoint from generating retries forever, and makes it obvious which webhook needs attention. A single successful delivery resets the counter. Once you've fixed the endpoint, switch the webhook back on in the app.\n\nTo protect shared infrastructure, Testpad limits how many webhook-generating events an account can emit in a rolling hour; beyond that, further events that hour are dropped rather than queued. Normal usage stays well under the limit.\n\n<br>\n\n---\n**Seeing what happened**\n<br>\n<br>\n\nThe webhooks settings page keeps a log of recent deliveries for the account, with the URL, the response status (or `blocked` / `error`), and a snippet of the response body. It's the first place to look when a delivery didn't do what you expected.\n"
    },
    {
      "name": "Change Log",
      "description": "**[1.1.0] – 2026-06-24**\n\n[Webhooks](/openapi/webhooks) added. Runs now support `state` and `assignee`.\n\n\n**[1.0.0] – 2025-11-01**\n\nGeneral release of the Testpad API.\n\n\n**[0.9.0] - 2025-08-01**\n\nPre-release for early adopter beta-testing."
    },
    {
      "name": "Unsupported Capabilities",
      "description": "There are many possibilities for the future of Testpad's API. Here is a (obviously non-exhaustive) list of notable features that might be expected of the API but which are **not supported for the time being**.\n\n- **Creating and Modifying Projects**  \nProjects cannot be created by the API and basic project information like names and descriptions cannot be modified. However, the contents of project (scripts and folders) can be modified via the several project and folder endpoints.\n\n- **Folder manipulations**  \nFolder items (scripts, notes and nested folders) cannot be moved around. There's no support for moving folder items between folders or between projects.  \nSimilarly, there's no support yet for archiving or unarchiving scripts, notes and folders.\n\n- **Script field set modifications**  \nThe set of fields in a script cannot be modified by the API. Custom fields can be created as part of the creation of a new script, but once created, the API cannot modify the set of fields defined for a script.\n\n- **Editing or deleting existing tests**  \nTests can be added to a script (at creation, and afterwards via Add tests to a script `POST /scripts/:s/tests`), but existing test rows cannot yet be edited or removed via the API.\n\n- **Deleting content**  \nFor now, there is no provision for deleting content with the API. This is partly because most use-cases for the API will be about creating content to record new testing, but also to protect new API users from themselves until good safeguards against accidental delete operations have been devised. Suggestions welcome!\n"
    },
    {
      "name": "Support",
      "description": "For support, or if you have a scenario or use-case in mind for the API that is not yet supported, please email **support@testpad.com**."
    }
  ],
  "paths": {
    "/projects": {
      "get": {
        "summary": "Get all projects",
        "description": "Retrieves a list of all active projects visible to the authenticating API key. Only basic information (name, id, description) is returned per project.\n\nArchived projects are not included.\n\nLook to these endpoints for retrieving information about the folder and script contents of projects:\n\n- `GET /projects/:p/folders`\n- `GET /projects/:p/folders/:f` ",
        "tags": [
          "◉ Project Endpoints"
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects\" \\\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response, containing the list of projects",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "Simple list of projects found",
                    "value": {
                      "projects": [
                        {
                          "id": 59,
                          "name": "Example Project",
                          "description": "An example project for API queries",
                          "created": "2024-05-12T11:09:52.758000+00:00"
                        },
                        {
                          "id": 80,
                          "name": "Another Project",
                          "description": "Another example project for API querying",
                          "created": "2024-03-12T12:06:20.253000+00:00"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}": {
      "get": {
        "summary": "Get a project",
        "description": "Retrieves the basic information about the project specified by project_id.\nProject IDs can be found in the app by inspecting the URL or via the API endpoint to Get all projects.\n\nDoes not retrieve the script and folder contents of a project.\n\nFor project contents, please see:\n- `GET /projects/:p/folders` – for getting all the script and folder contents of a project\n- `GET /projects/:p/folders/:f` – for getting the contents of a particular folder in a project",
        "tags": [
          "◉ Project Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "The project ID.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/14\" \\\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response object of the project object, containing the following fields:  \n\n- `id` integer — The ID of the project.  \n- `name` string — The name (title) of the project.  \n- `description` string — The project description, which in the app, is only displayed in the project settings page for a project.  \n- `created` timestamp string — The timestamp for when the project was created.\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "Fetch a single project at /projects/14",
                    "value": {
                      "project": {
                        "id": 72,
                        "name": "Example Project 14",
                        "description": "The 14th example project for demonstrating API queries",
                        "created": "2017-09-14T15:41:04.222000+00:00"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/folders": {
      "get": {
        "summary": "Get all folders",
        "description": "Retrieves the complete content tree of folders and scripts contained by the project, project_id.\n\nBy default, the returned tree of folders and scripts consists only of their names and IDs. Folder and script objects can be expanded with more details, such as progress statistics, test details and test runs details by using URL parameters as defined below.\n\nFor large projects, it is recommended to use the more specific endpoint Get a folder `GET /projects/:p/folders/:f` to retrieve the contents of just one folder rather than all folders in the project.\n\nTakes [Common URL Parameters](/openapi/common-url-parameters) for controlling the contents of contained objects.\n\nDefaults for common parameters:\n- subfolders=all\n- scripts=terse\n- tests=none\n- fields=none\n- runs=none\n- results=none\n- progress=none",
        "tags": [
          "◉ Folder Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "The project ID.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/119/folders\" \\\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response payload containing:\n\n- `folders` - the list of items in the requested folder, consisting of script objects, note objects or further (sub) folder objects.\n\nAll items contain the common fields:  \n- `type` - enum — The type of item that this object is representing, one of script, note or folder.\n- `id` - integer or string — The ID of the item. Script IDs are integers. Folder IDs are strings that start with an 'f'. Note IDs are strings that start with an 'n'.  \n- `name` - string — The name (title) of the script, note or folder.\n\nFolder items will also contain the field:\n- `contents` - list of object — The items contained in the sub-folder (recursively).\n\nScript and Folder items may contain progress statistics depending on the `progress` URL parameter:\n- `progress` - object — The progress object for the script or folder, in either full or terse form depending on the progress URL parameter.\n\nScript items may contain any of the usual script object fields, again depending on the URL parameters (`tests`, `fields`, `runs` and `results`).\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "Example: GET /projects/119/folders\n",
                    "value": {
                      "folders": [
                        {
                          "type": "script",
                          "id": 23013,
                          "name": "An example script A"
                        },
                        {
                          "type": "script",
                          "id": 23012,
                          "name": "An example script B"
                        },
                        {
                          "type": "folder",
                          "id": "f34",
                          "name": "new folder",
                          "contents": [
                            {
                              "type": "script",
                              "id": 23018,
                              "name": "A nested script C"
                            },
                            {
                              "type": "script",
                              "id": 23017,
                              "name": "A nested script D"
                            }
                          ]
                        }
                      ],
                      "context": {
                        "folderPath": {
                          "folders": [],
                          "path": "/"
                        },
                        "project": {
                          "id": 72,
                          "name": "An example project",
                          "description": "New project",
                          "created": "2017-09-14T15:41:04.222000+00:00"
                        }
                      }
                    }
                  },
                  "progressTerse": {
                    "summary": "Example: GET /projects/119/folders?progress=terse\n",
                    "value": {
                      "folders": [
                        {
                          "type": "script",
                          "id": 23013,
                          "name": "An example script A",
                          "progress": {
                            "summary": "Pass:15 Fail:4 Block:0 Query:0 Total:19/24"
                          }
                        },
                        {
                          "type": "script",
                          "id": 23012,
                          "name": "An example script B",
                          "progress": {
                            "summary": "Pass:4 Fail:1 Block:0 Query:0 Total:5/15"
                          }
                        },
                        {
                          "type": "folder",
                          "id": "f34",
                          "name": "new folder",
                          "progress": {
                            "summary": "Pass:24 Fail:0 Block:0 Query:1 Total:25/30"
                          },
                          "contents": [
                            {
                              "type": "script",
                              "id": 23018,
                              "name": "A nested script C",
                              "progress": {
                                "summary": "Pass:24 Fail:0 Block:0 Query:1 Total:25/30"
                              }
                            },
                            {
                              "type": "script",
                              "id": 23017,
                              "name": "A nested script D",
                              "progress": {
                                "summary": "Pass:0 Fail:0 Block:0 Query:0 Total:0/0"
                              }
                            }
                          ]
                        }
                      ],
                      "context": {
                        "folderPath": {
                          "folders": [],
                          "path": "/"
                        },
                        "project": {
                          "id": 72,
                          "name": "An example project",
                          "description": "New project",
                          "created": "2017-09-14T15:41:04.222000+00:00"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a folder",
        "description": "Creates a new folder at the top level of the project project_id.\n\nThe new folder is supplied as a JSON object in the request body supplying only the name of the new folder.\n\nNote: it is not possible to create a new folder together with a new script (or sub-folder) in the same request.\nInstead, a first request must be made to create a new folder, and then subsequent requests can be used to populate it with content.",
        "tags": [
          "◉ Folder Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "The project ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Controls where the folder will be created within the top-level items\n\n`first` (default) — the folder is placed at the top of the project.  \n`last` — the folder is placed at the end of the project's contents.  \n`N` - integer — the folder is placed after the Nth item in the project.\n"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The name of the new folder"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/projects/119/folders\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"My new folder\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "JSON response payload containing:\n\n- `folder` - object — A summary of the new folder.\n- `context` - object — Project and folderPath context.\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "Example: POST /projects/119/folders\n",
                    "value": {
                      "folder": {
                        "id": "f10",
                        "name": "My new folder",
                        "contents": []
                      },
                      "context": {
                        "project": {
                          "id": 119,
                          "name": "ApiProj2",
                          "description": "New project",
                          "created": "2020-12-01T11:24:33.360000+00:00"
                        },
                        "folderPath": {
                          "folders": [],
                          "path": "/"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/folders/{folder_id}": {
      "get": {
        "summary": "Get a folder",
        "description": "Retrieves the basic information and contents of the folder specified by folder_id.\n\nFolder IDs are strings that start with an 'f' followed by an integer. To find a folder ID in the app, click on a folder to open it and inspect the URL.\n\nBy default, the returned tree of folders and scripts consists only of their names and IDs. Folder and script objects can be expanded with more details, such as progress statistics, test details and test runs details by using URL parameters as defined below.\n\nIn particular, note that the subfolders URL parameter can be used to limit the response to just the folder requested, without also returning the contents of nested sub-folders.\n\nTakes [Common URL Parameters](/openapi/common-url-parameters) for controlling the contents of contained objects.\n\nDefaults for common parameters:\n- subfolders=all\n- scripts=terse\n- tests=none\n- fields=none\n- runs=none\n- results=none\n- progress=none",
        "tags": [
          "◉ Folder Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "The project ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "description": "The folder ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/119/folders/f1\" \\\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response containing\n\n`folder` - object, containing:\n\n- `id` - string — The ID of the retrieved folder\n- `name` - string — The name (title) of the folder\n- `type` - \"folder\" — Always the string 'folder' \n- `progress` - object (OPTIONAL) — If included by the progress URL parameter\n- `contents` - list of objects — Items contained in the retrieved folder\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f1",
                    "value": {
                      "folder": {
                        "type": "folder",
                        "id": "f1",
                        "name": "An example Folder",
                        "contents": [
                          {
                            "type": "script",
                            "id": 23022,
                            "name": "An example script C"
                          },
                          {
                            "type": "script",
                            "id": 23023,
                            "name": "An example script D"
                          }
                        ]
                      },
                      "context": {
                        "folderPath": {
                          "folders": [],
                          "path": "/"
                        },
                        "project": {
                          "id": 119,
                          "name": "ApiProj2",
                          "description": "New project",
                          "created": "2020-12-01T11:24:33.360000+00:00"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Modify a folder",
        "description": "Modifies the name of the folder folder_id in the project project_id.\n\nTo modify the contents of a folder, please see:\n- Create a sub-folder `POST /projects/:p/folders/:f/folders` – for creating a new folder within a folder\n- Create a script in a folder `POST /projects/:p/folders/:f/scripts` – for creating a script within a folder\n\n### POST data\nname string — The new name of the folder.\n\n### Returns\nJSON response payload containing:\n\nfolder object — Summary of the modified folder.  \ncontext object — Project and folderPath context.\n\n### Defaults for common parameters\n(no common parameters are used).",
        "tags": [
          "◉ Folder Endpoints"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AuthorizationHeader"
          },
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "The project ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "description": "The folder ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The name of the new folder"
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "/projects/119/folders/f11",
                  "value": {
                    "name": "My modified folder name"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X PATCH \"https://api.testpad.com/api/v1/projects/119/folders/f11\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"My modified folder name\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f11",
                    "value": {
                      "folder": {
                        "id": "f11",
                        "name": "My modified folder name",
                        "contents": []
                      },
                      "context": {
                        "project": {
                          "id": 119,
                          "name": "ApiProj2",
                          "description": "New project",
                          "created": "2020-12-01T11:24:33.360000+00:00"
                        },
                        "folderPath": {
                          "folders": [
                            {
                              "id": "f1",
                              "name": "An example parent folder"
                            }
                          ],
                          "path": "An example parent folder/"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/folders/{folder_id}/folders": {
      "post": {
        "summary": "Create a sub-folder",
        "description": "Creates a new sub-folder of the folder folder_id within the project project_id.\n\nThe new folder is supplied as a JSON object in the request body supplying only the name of the new folder.\n\nNote, it is not possible to create a new folder and new script (or sub-folder) contents in the same request. Instead, a first request must be made to create a new folder, and then subsequent requests can be used to populate it with content.",
        "tags": [
          "◉ Folder Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "description": "The project ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "description": "The folder ID.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "Controls where the folder will be created within the parent folder.\n\n`first` (default) — the folder is placed at the top of the parent folder.  \n`last` — the folder is placed at the end of the parent folder.  \n`N' - integer — the folder is placed after the Nth item in the parent folder.\n"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The name of the new folder"
                  }
                }
              },
              "examples": {
                "default": {
                  "summary": "/projects/119/folders/f1/folders",
                  "value": {
                    "name": "My new folder"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/projects/119/folders/f1/folders\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"My new sub-folder\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "JSON response payload containing:\n\n- `folder` - object — A summary of the new folder\n- `context` - object — Project and folderPath context\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f1/folders",
                    "value": {
                      "folder": {
                        "id": "f12",
                        "name": "My new sub-folder",
                        "contents": []
                      },
                      "context": {
                        "project": {
                          "id": 119,
                          "name": "ApiProj2",
                          "description": "New project",
                          "created": "2020-12-01T11:24:33.360000+00:00"
                        },
                        "folderPath": {
                          "folders": [
                            {
                              "id": "f1",
                              "name": "An example parent folder"
                            }
                          ],
                          "path": "An example parent folder/"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/scripts": {
      "post": {
        "summary": "Create a script in a project",
        "description": "Creates a new script in the top level of the project project_id.\n\nIf you want to create a script within a particular folder, please refer to **Create a script in a folder** `POST /projects/:p/folders/:f/script`, a nearly identical endpoint but for the specifying which folder to put the script in. ",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Controls where the script will be created within the project:\n\n`first` (default) — the folder is placed at the top of the parent folder.  \n`last` — the folder is placed at the end of the parent folder.  \n`N` - integer — the folder is placed after the Nth item in the parent folder.\n"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScriptInput"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal (defaults only)",
                  "value": {}
                },
                "named": {
                  "summary": "Named script with a couple of tests",
                  "value": {
                    "name": "Regression Suite",
                    "tests": [
                      "login works",
                      "logout works"
                    ]
                  }
                },
                "full": {
                  "summary": "With fields, tests, runs and results",
                  "value": {
                    "name": "API smoke",
                    "description": "Core endpoints",
                    "fields": [
                      "browser",
                      "(component)",
                      {
                        "id": "platform",
                        "show": false
                      }
                    ],
                    "tests": [
                      {
                        "text": "GET /health is 200",
                        "tags": "HIGH"
                      },
                      {
                        "text": "GET /version returns semver"
                      }
                    ],
                    "runs": [
                      {
                        "headers": {
                          "build": "1.1.0",
                          "browser": "Chrome"
                        },
                        "results": {
                          "1": "pass",
                          "2": "pass"
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/projects/119/scripts\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"a new script\",\n  \"fields\": [\"(weather)\", \"build\", \"date\"],\n  \"runs\": [\n    { \"id\": 1, \"headers\": { \"build\": \"1.1.0\", \"weather\": \"fair\" }, \"results\": { \"2\": \"pass\", \"7\": \"blocked\" } },\n    { \"id\": 2, \"headers\": { \"build\": \"1.1.0\", \"weather\": \"poor\" }, \"results\": { \"2\": { \"result\": \"blocked\", \"comment\": \"cigars not\" } } }\n  ],\n  \"tests\": [\n    { \"text\": \"API-supplied test line one\", \"tags\": \"HIGH\", \"results\": { \"1\": \"pass\", \"2\": \"pass\" } },\n    { \"text\": \"line two\", \"results\": { \"1\": \"pass\", \"2\": { \"result\": \"fail\", \"comment\": \"no cigar\" } } },\n    { \"text\": \"line three\", \"results\": { \"1\": \"pass\", \"2\": \"pass\" } }\n  ]\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "JSON response containing the script object just created:\n\n- `script`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Script"
                },
                "examples": {
                  "default": {
                    "summary": "/projects/119/scripts",
                    "value": {
                      "script": {
                        "id": 8,
                        "name": "scripted script 10",
                        "created": "2025-04-29T15:35:50.516",
                        "description": "",
                        "archived": false,
                        "fields": [
                          "..."
                        ],
                        "tests": [
                          {
                            "id": 1,
                            "indent": 0,
                            "text": "test line A",
                            "tags": "HELLO"
                          },
                          {
                            "id": 2,
                            "indent": 1,
                            "text": "test line B"
                          },
                          {
                            "id": 3,
                            "indent": 1,
                            "text": "test line C"
                          },
                          {
                            "id": 4,
                            "indent": 1,
                            "text": "test line D"
                          },
                          {
                            "id": 5,
                            "indent": 0,
                            "text": "test line E"
                          }
                        ],
                        "progress": {
                          "total": 8,
                          "pass": 3,
                          "fail": 2,
                          "block": 0,
                          "query": 1,
                          "summary": "Pass:3 Fail:2 Block:0 Query:1 Total:6/8"
                        },
                        "type": "script"
                      },
                      "context": {
                        "project": {
                          "id": 119,
                          "name": "Example Project",
                          "description": "Am example project",
                          "created": "2025-04-25T13:28:25.852"
                        },
                        "folderPath": {
                          "folders": [
                            {
                              "id": "f1",
                              "name": "Release 0.1"
                            }
                          ],
                          "path": "Release 0.1/"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/folders/{folder_id}/scripts": {
      "post": {
        "summary": "Create a script in a folder",
        "description": "Creates a new script in the folder folder_id within the project project_id.\n\nThe new script is supplied as a JSON object in the request body and can contain tests,\nruns, run headers, results and custom fields.\n\n### URL Parameters\ninsert enum or integer  \nControls where the script will be created within the specified folder.\n\nfirst (default) — the script is placed at the top of the folder.  \nlast — the script is placed at the end of the folder.  \nas integer, N — the script is placed after the Nth item in the folder.\n\n### POST data\nname string — The name of the new script.  \ndescription string (OPTIONAL) — The description for the new script.  \ncomments string (OPTIONAL) — The report comments for the new script.\n\ntests — See Supplying Test Text.  \nfields — Order/visibility/definition of run header fields.  \nruns — List of test run objects as defined in Supplying Test Runs.\n\n### Returns\nJSON response payload containing:\n\nscript object — Summary of the new script.  \ncontext object — Project and folderPath context.\n\n### Defaults for common parameters\n(no common parameters are used).",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder ID."
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Controls where the script will be created within the target folder:\n\n`first` (default) — the script is placed at the top of the folder.  \n`last` — the script is placed at the end of the folder's contents.  \n`N` - integer — the script is placed after the Nth item in the folder.\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "/projects/119/folders/f1/scripts",
                  "value": "{\n  \"name\": \"scripted script 10\",\n  \"description\": \"created by script\",\n  \"fields\": [\"(weather)\", \"build\", \"date\"],\n  \"runs\": [\n    { \"headers\": { \"build\": \"1.1.0\", \"weather\": \"fair\", \"new\": \"surprise\" }, \"results\": { \"5\": \"query\", \"7\": \"blocked\" } },\n    { \"headers\": { \"build\": \"1.1.0\", \"weather\": \"poor\", \"new\": \"!\" } }\n  ],\n  \"tests\": [\n    { \"text\": \"test line A\", \"tags\": \"HELLo\", \"notes\": \"\" },\n    { \"text\": \"test line B\", \"indent\": 1, \"notes\": \"\", \"results\": { \"1\": \"pass\", \"2\": \"fail\" } },\n    { \"indent\": 1, \"text\": \" test line C\", \"results\": [ \"pass\", \"fail\" ] },\n    { \"indent\": 1, \"text\": \" test line D\", \"results\": [ { \"result\": \"pass\", \"issue\": \"TPT-123\" }, { \"issue\": \"TPT-124\" } ] },\n    { \"text\": \"test line E\" }\n  ]\n}\n"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/projects/119/folders/f1/scripts\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"scripted script 10\",\n  \"description\": \"created by script\",\n  \"fields\": [\"(weather)\", \"build\", \"date\"],\n  \"runs\": [\n    { \"headers\": { \"build\": \"1.1.0\", \"weather\": \"fair\", \"new\": \"surprise\" }, \"results\": { \"5\": \"query\", \"7\": \"blocked\" } },\n    { \"headers\": { \"build\": \"1.1.0\", \"weather\": \"poor\", \"new\": \"!\" } }\n  ],\n  \"tests\": [\n    { \"text\": \"test line A\", \"tags\": \"HELLo\", \"notes\": \"\" },\n    { \"text\": \"test line B\", \"indent\": 1, \"notes\": \"\", \"results\": { \"1\": \"pass\", \"2\": \"fail\" } },\n    { \"indent\": 1, \"text\": \" test line C\", \"results\": [ \"pass\", \"fail\" ] },\n    { \"indent\": 1, \"text\": \" test line D\", \"results\": [ { \"result\": \"pass\", \"issue\": \"TPT-123\" }, { \"issue\": \"TPT-124\" } ] },\n    { \"text\": \"test line E\" }\n  ]\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f1/scripts",
                    "value": "{\n  \"script\": {\n    \"id\": 8,\n    \"name\": \"scripted script 10\",\n    \"created\": \"2025-04-29T15:35:50.516\",\n    \"description\": \"\",\n    \"archived\": false,\n    \"fields\": [ \"...\" ],\n    \"tests\": [\n      { \"id\": 1, \"indent\": 0, \"text\": \"test line A\", \"tags\": \"HELLO\" },\n      { \"id\": 2, \"indent\": 1, \"text\": \"test line B\" },\n      { \"id\": 3, \"indent\": 1, \"text\": \"test line C\" },\n      { \"id\": 4, \"indent\": 1, \"text\": \"test line D\" },\n      { \"id\": 5, \"indent\": 0, \"text\": \"test line E\" }\n    ],\n    \"progress\": { \"total\": 8, \"pass\": 3, \"fail\": 2, \"block\": 0, \"query\": 1, \"summary\": \"Pass:3 Fail:2 Block:0 Query:1 Total:6/8\" },\n    \"type\": \"script\"\n  },\n  \"context\": {\n    \"project\": { \"id\": 119, \"name\": \"Example Project\", \"description\": \"Am example project\", \"created\": \"2025-04-25T13:28:25.852\" },\n    \"folderPath\": { \"folders\": [ { \"id\": \"f1\", \"name\": \"Release 0.1\" } ], \"path\": \"Release 0.1/\" }\n  }\n}\n"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "summary": "Identify the caller",
        "description": "Returns the account and API key associated with the supplied token. Useful as a connectivity and authentication check.",
        "tags": [
          "Authentication"
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/whoami\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response identifying the caller's account and API key.",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/whoami",
                    "value": {
                      "company": {
                        "name": "Example Co"
                      },
                      "apikey": {
                        "number": 1,
                        "label": "CI token",
                        "expires": null
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/scripts/{script_id}": {
      "get": {
        "summary": "Get a script",
        "description": "Retrieves information about the script specified by the script ID, script_id.\n\nScript IDs are integers and are unique across projects. To find the ID of a script via the app, browse to the relevant script and inspect the page URL.\n\nBy default, most information about the script is returned, including its tests, fields, run headers and results. Use the common URL Parameters to modify the contents of the response object.\n\nTakes [Common URL Parameters](/openapi/common-url-parameters) for controlling the contents of contained objects.\n\nDefaults for common parameters:\n- subfolders (not relevant)\n- scripts=full\n- tests=full\n- fields=full\n- runs=full\n- results=full\n- progress=terse",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "description": "The script ID.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/scripts/23\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response containing:\n`script` - object, containing:\n\n- `id` - string — The ID of the retrieved script\n- `name` - string — The name (title) of the script\n- `progress` - object — The progress statistics for all the test runs in the script\n- `comments` - string - Comment text to be included in reports for this script\n- `created` - timestamp - the date/time this script was created\n- `archived` - boolean - whether or not this script has been archived within its project\n- `fields` - array of field objects - the list of header fields this script uses for test runs\n- `tests` - the list of tests in this script, with the format depending on the `tests` URL parameter\n- `runs` - the list of test runs in this script\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23",
                    "value": "{\n  \"script\": {\n    \"id\": 23,\n    \"name\": \"Hello World\",\n    \"description\": \"An example script to fetch from the API\",\n    \"comments\": \"Comments to be included in the report for this script\",\n    \"created\": \"2020-10-22T13:48:58.123000+00:00\",\n    \"archived\": false,\n    \"fields\": [\n      { \"id\": \"_run\", \"label\": \"number\", \"show\": true },\n      { \"id\": \"_tester\", \"label\": \"tester\", \"show\": true },\n      { \"id\": \"_createdDate\", \"label\": \"date\", \"show\": true },\n      { \"id\": \"_createdTime\", \"label\": \"time\", \"show\": false },\n      { \"id\": \"_tags\", \"label\": \"tag filter\", \"show\": false },\n      { \"id\": \"browser\", \"label\": \"Browser\", \"show\": true }\n    ],\n    \"tests\": [\n      { \"id\": 1, \"indent\": 0, \"text\": \"A first title row\" },\n      { \"id\": 2, \"indent\": 1, \"text\": \"child of first row\", \"tags\": \"HIGH\" }\n      { \"id\": 3, \"indent\": 1, \"text\": \"child two of first row\" },\n      { \"id\": 4, \"indent\": 1, \"text\": \"child three of first row\" },\n      { \"id\": 5, \"indent\": 1, \"text\": \"child four of first row\" },\n    ]\n    \"runs\": [\n      {\n        \"id\": \"1\",\n        \"created\": \"2025-06-03T14:28:15.466\",\n        \"state\": \"complete\",\n        \"label\": \"1 / anyone / 03 Jun 2025 / safari \",\n        \"headers\": {\n          \"_run\": \"1\",\n          \"_tester\": \"anyone\",\n          \"_createdDate\": \"03 Jun 2025\",\n          \"_createdTime\": \"14:28:15\",\n          \"_tags\": \"ALL\",\n          \"browser\": \"safari\",\n        },\n        \"assignee\": {\n          \"id\": \"_anyone\",\n          \"name\": \"anyone\",\n          \"email\": \"\"\n        },\n        \"results\": {\n          \"2\": { \"result\": \"blocked\", \"comment\": \"no cigar\" },\n          \"3\":    { \"result\": \"fail\" },\n          \"4\":    { \"result\": \"fail\" },\n          \"5\":    { \"result\": \"\", \"issue\": \"TPT-124\" }\n        },\n        \"progress\": {\n          \"total\": 4,\n          \"pass\": 0,\n          \"fail\": 2,\n          \"block\": 1,\n          \"query\": 0,\n          \"summary\": \"Pass:0 Fail:2 Block:1 Query:0 Total:3/4\"\n        },\n      }\n    ],\n    \"progress\": {\n      \"total\": 4,\n      \"pass\": 0,\n      \"fail\": 2,\n      \"block\": 1,\n      \"query\": 0,\n      \"summary\": \"Pass:0 Fail:2 Block:1 Query:0 Total:3/4\"\n    }\n  },\n  \"context\": {\n    \"project\": { \"id\": 3, \"name\": \"Example Project\", \"description\": \"An example project\", \"created\": \"2020-10-22T13:34:26.571000+00:00\", \"bugPattern\": \"\" },\n    \"folderPath\": { \"folders\": [], \"path\": \"/\" }\n  }\n}\n"
                  },
                  "noTests": {
                    "summary": "/scripts/23?tests=none",
                    "value": "{\n  \"script\": {\n    \"id\": 23,\n    \"name\": \"Hello World\",\n    \"description\": \"An example script to fetch from the API\",\n    \"comments\": \"Comments to be included in the report for this script\",\n    \"created\": \"2020-10-23T10:41:59.834000+00:00\",\n    \"archived\": false,\n    \"fields\": [\n      { \"id\": \"_run\", \"label\": \"number\", \"show\": true },\n      { \"id\": \"_tester\", \"label\": \"tester\", \"show\": true },\n      { \"id\": \"_createdDate\", \"label\": \"date\", \"show\": true },\n      { \"id\": \"_createdTime\", \"label\": \"time\", \"show\": false },\n      { \"id\": \"_tags\", \"label\": \"tag filter\", \"show\": false },\n      { \"id\": \"browser\", \"label\": \"Browser\", \"show\": true }\n    ],\n    \"runs\": [\n      {\n        \"id\": \"1\",\n        \"created\": \"2025-06-03T14:28:15.466\",\n        \"state\": \"complete\",\n        \"label\": \"1 / anyone / 03 Jun 2025 / safari \",\n        \"headers\": {\n          \"_run\": \"1\",\n          \"_tester\": \"anyone\",\n          \"_createdDate\": \"03 Jun 2025\",\n          \"_createdTime\": \"14:28:15\",\n          \"_tags\": \"ALL\",\n          \"browser\": \"safari\",\n        },\n        \"assignee\": {\n          \"id\": \"_anyone\",\n          \"name\": \"anyone\",\n          \"email\": \"\"\n        },\n        \"results\": {\n          \"2\": { \"result\": \"blocked\", \"comment\": \"no cigar\" },\n          \"3\":    { \"result\": \"fail\" },\n          \"4\":    { \"result\": \"fail\" },\n          \"5\":    { \"result\": \"\", \"issue\": \"TPT-124\" }\n        },\n        \"progress\": {\n          \"total\": 4,\n          \"pass\": 0,\n          \"fail\": 2,\n          \"block\": 1,\n          \"query\": 0,\n          \"summary\": \"Pass:0 Fail:2 Block:1 Query:0 Total:3/4\"\n        },\n      }\n    ],\n    \"progress\": {\n      \"total\": 4,\n      \"pass\": 0,\n      \"fail\": 2,\n      \"block\": 1,\n      \"query\": 0,\n      \"summary\": \"Pass:0 Fail:2 Block:1 Query:0 Total:3/4\"\n    }\n  }\n  \"context\": {\n    \"project\": { \"id\": 3, \"name\": \"Example Project\", \"description\": \"An example project\", \"created\": \"2020-10-22T13:34:26.571000+00:00\" },\n    \"folderPath\": { \"folders\": [], \"path\": \"/\" }\n  }\n}\n"
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Modify a script",
        "description": "Modifies the metadata of the script `script_id`. Supply any of the following\nfields to replace their current values:\n\n- `name` string — the script's name (title).\n- `description` string — the script's description.\n- `comments` string — the report comments for the script.\n\nOnly metadata is changed here. To add tests, see Add tests to a script\n`POST /scripts/:s/tests`. To add results, add a test run\n`POST /scripts/:s/runs`.\n\n### Defaults for common parameters\n(no common parameters are used).",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The script ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "Rename a script and set its report comments",
                  "value": {
                    "name": "Renamed script",
                    "description": "updated",
                    "comments": "see release notes"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X PATCH \"https://api.testpad.com/api/v1/scripts/23\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"Renamed script\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The modified script, with project and folderPath context.",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23",
                    "value": {
                      "script": {
                        "id": 23,
                        "name": "Renamed script",
                        "description": "updated",
                        "comments": "see release notes",
                        "archived": false,
                        "type": "script"
                      },
                      "context": {
                        "project": {
                          "id": 3,
                          "name": "Example Project"
                        },
                        "folderPath": {
                          "folders": [],
                          "path": "/"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/scripts/{script_id}/runs": {
      "post": {
        "summary": "Add a test run",
        "description": "Adds a test run to the script script_id.\n\nThe test run will be appended to the set of test runs already present.\n\nBoth `headers` and `results` are optional, so you can add an empty run,\na run with only headers, or a run with results.\n\nTo record a retest of an existing run rather than a fresh run, pass the\n`retest` query parameter (see below).\n\nThis endpoint cannot be used to create new header fields that have not already been defined for the script.\nAny unrecognized header fields will be ignored.\n\n### POST data\nThe test run is defined by supplying a test run object as defined in Supplying Test Runs.\n\n### Including Results\nTo include results with the test run, the test run object can include the results field, which has the choice of format defined in Supplying Test Results.\n\n### Returns\nJSON response payload containing:\n\nrun object — A summary of test run that was created, including its new id field.\n\n### Defaults for common parameters\n(no common parameters are used).",
        "tags": [
          "◉ Run Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The script ID."
          },
          {
            "name": "retest",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The ID of an existing run to retest. When given, the new run is recorded as a\nretest of that run (its ID comes back in the form `N.1`) rather than a fresh run.\nYou can also retest a retest, so this value may itself be a retest id like \"4.1\".\nRetesting a run that has already been superseded returns 400; an unknown run id\nreturns 404.\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "/scripts/23/runs",
                  "value": "{ \"headers\": { \"build\": \"1.1.1\", \"weather\": \"fair\" }, \"results\": { \"2\": \"pass\", \"3\": \"fail\" } }\n"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/scripts/23/runs\"              \n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"headers\": {\"build\": \"1.1.1\", \"weather\": \"fair\"},\n  \"results\": {\"2\": \"pass\", \"3\": \"fail\"}\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23/runs",
                    "value": {
                      "run": {
                        "id": "6",
                        "state": "started",
                        "headers": {
                          "_run": "6",
                          "_tags": "ALL",
                          "build": "1.1.1",
                          "weather": "fair"
                        },
                        "results": {
                          "2": {
                            "result": "pass"
                          },
                          "3": {
                            "result": "fail"
                          }
                        },
                        "assignee": {
                          "id": "_anyone",
                          "name": "",
                          "email": ""
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/scripts/{script_id}/runs/{run_id}": {
      "get": {
        "summary": "Get a test run",
        "description": "Returns a single test run from the script `script_id`, including its headers and\nits results.",
        "tags": [
          "◉ Run Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The script ID."
          },
          {
            "name": "run_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The run ID, e.g. \"6\" or a retest id like \"4.1\"."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/scripts/23/runs/1\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response containing the `run` object.",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23/runs/1",
                    "value": {
                      "run": {
                        "id": "1",
                        "state": "complete",
                        "headers": {
                          "_run": "1",
                          "_tester": "anyone",
                          "_tags": "ALL",
                          "build": "1.2.001"
                        },
                        "results": {
                          "2": {
                            "result": "pass"
                          },
                          "3": {
                            "result": "fail"
                          }
                        },
                        "assignee": {
                          "id": "_anyone",
                          "name": "anyone",
                          "email": ""
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/scripts/{script_id}/tests": {
      "get": {
        "summary": "Get a script's tests",
        "description": "Returns the flat list of tests (test rows) in the script `script_id`, each with\nits `indent` showing where it sits in the outline.\n\nFor the tests formatted as strings or a single block of text, use the script\nendpoint `GET /scripts/:s` with the `tests` URL parameter instead.",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The script ID."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/scripts/23/tests\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response containing:\n\n- `tests` - array of test objects, each with `id`, `indent`, `text`, the\n  script's `script_id`, and `tags` / `notes` when set.\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23/tests",
                    "value": {
                      "tests": [
                        {
                          "id": 1,
                          "indent": 0,
                          "text": "Creating notes",
                          "script_id": 23
                        },
                        {
                          "id": 2,
                          "indent": 1,
                          "text": "Create a note with a title and body",
                          "tags": "smoke",
                          "script_id": 23
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add tests to a script",
        "description": "Appends one or more tests to the end of the script `script_id` (or at a chosen\nposition with the `insert` parameter).\n\nTests can be supplied in the same forms as when creating a script (see\nSupplying Test Text):\n\n1. As **JSON** (`Content-Type: application/json`) — either:\n   - a list of test objects. Each object takes `text` (required), and optionally\n     `tags` (a comma or space separated string), `notes`, and `indent` (an integer\n     outline depth). You may supply your own `id` on every test in the list, or on\n     none of them; or\n   - a list of strings, one test per string, with leading tabs for indentation.\n2. As **plain text** (`Content-Type: text/plain`) — a single string with one test\n   per line, using leading tabs for indentation, exactly as the app's export\n   produces. This is the same format the `tests=text` response returns.\n\nResults cannot be attached when adding tests. A test row has no run to hold a\nresult, so any `results` field in a test object is ignored (and is not reflected\nback in the response). To record results, create the script with runs\n(`POST /projects/:p/scripts`) or add a run (`POST /scripts/:s/runs`).\n\n### Defaults for common parameters\n(no common parameters are used).",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The script ID."
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Where to add the tests within the script:\n\n`first` (default) — at the top.\n`last` — at the end.\n`N` - integer — after the Nth item.\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "listOfObjects": {
                  "summary": "A list of test objects",
                  "value": [
                    {
                      "text": "Account settings",
                      "tags": "smoke"
                    },
                    {
                      "text": "Change email address",
                      "indent": 1,
                      "notes": "needs a verified account"
                    },
                    {
                      "text": "Change password",
                      "indent": 1
                    }
                  ]
                },
                "listOfStrings": {
                  "summary": "A list of strings (tabs for indentation)",
                  "value": [
                    "Account settings",
                    "\tChange email address",
                    "\tChange password"
                  ]
                }
              }
            },
            "text/plain": {
              "examples": {
                "plainText": {
                  "summary": "One test per line, tabs for indentation",
                  "value": "Account settings\n\tChange email address\n\tChange password\n"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/scripts/23/tests\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     -H \"Content-Type: application/json\"\n     --data-binary @- << 'EOF'\n[\n  { \"text\": \"Account settings\", \"tags\": \"smoke\" },\n  { \"text\": \"Change email address\", \"indent\": 1 }\n]\nEOF\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response containing `tests`, the list of tests that were added,\neach with its assigned `id`.\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23/tests",
                    "value": {
                      "tests": [
                        {
                          "id": 14,
                          "text": "Account settings",
                          "tags": "smoke",
                          "script_id": 23
                        },
                        {
                          "id": 15,
                          "text": "Change email address",
                          "script_id": 23
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/scripts/{script_id}/tests/{test_id}": {
      "get": {
        "summary": "Get a single test",
        "description": "Returns one test (test row) from the script `script_id`.",
        "tags": [
          "◉ Script Endpoints"
        ],
        "parameters": [
          {
            "name": "script_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The script ID."
          },
          {
            "name": "test_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The test ID."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/scripts/23/tests/1\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "JSON response containing the `test` object.",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/scripts/23/tests/1",
                    "value": {
                      "test": {
                        "id": 1,
                        "indent": 0,
                        "text": "Creating notes",
                        "script_id": 23
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/notes": {
      "get": {
        "summary": "Get a list of notes at the top-level of a project",
        "description": "Fetches any notes (note items) that sit at the top level of the project.\n\nFor notes in specific folders, see `GET /projects/:p/folders/:f/notes`.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/119/notes\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/notes",
                    "value": {
                      "notes": [
                        {
                          "id": "n1",
                          "name": "A note item in project 119",
                          "type": "note"
                        },
                        {
                          "id": "n2",
                          "name": "Another note item in project 119",
                          "type": "note"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a note in a project",
        "description": "Creates a new note in the top level of the project project_id.\n\nNotes are text comments displayed beside scripts and folders in the project view,\nallowing for additional notes, comments or instructions to be displayed at the\nproject/folder and folder report levels.\n\nThe new note is supplied as a JSON object in the request body with the\ncontents of the note in the name field. This might seem like an odd\nchoice of field name, but is used for consistency with the other types of items\nfound in projects and folders, namely Folders, Scripts and Notes.\nAll project/folder items have a name field, which is the text displayed in the\nproject/folder view. As such, the contents of a Note are in fact its displayed \"name\".",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Controls where the note will be created within the top-level items\n\n`first` (default) — the note is placed at the top of the project.  \n`last` — the note is placed at the end of the project's contents.  \n`N` - integer — the note is placed after the Nth item in the project.\n"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The contents of the new note, effectively its \"name\" in the same way scripts and folders\nhave names that are displayed as items in folders.\n"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/projects/119/notes\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"A note created by the API\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "Successful response. The new note object is returned directly (not wrapped),\nwith its `id`, `name` and `type`.\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/notes",
                    "value": {
                      "id": "n5",
                      "name": "A note created by the API",
                      "type": "note"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/folders/{folder_id}/notes": {
      "get": {
        "summary": "Get the list of notes in a folder",
        "description": "Fetches the list of notes within a folder (but not its subfolders).\n\nFor notes in the root folder of a project, see `GET /projects/:p/notes`.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder ID."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/119/folders/f2/notes\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f2/notes",
                    "value": {
                      "notes": [
                        {
                          "id": "n7",
                          "name": "A note in folder f2 of project 119",
                          "type": "note"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a note in a folder",
        "description": "Creates a new note in the folder folder_id within the project project_id.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder ID."
          },
          {
            "name": "insert",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Controls where the note will be created within the top-level items\n\n`first` (default) — the note is placed at the top of the project.  \n`last` — the note is placed at the end of the project's contents.  \n`N` - integer — the note is placed after the Nth item in the project.\n"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "/projects/119/folders/f2/notes?insert=last",
                  "value": {
                    "name": "A note created by the API"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X POST \"https://api.testpad.com/api/v1/projects/119/folders/f2/notes?insert=last\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"A note created by the API\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "201": {
            "description": "Successful response. The new note object is returned directly (not wrapped).\n",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f2/notes?insert=last",
                    "value": {
                      "id": "n7",
                      "name": "A note created by the API",
                      "type": "note"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/notes/{note_id}": {
      "get": {
        "summary": "Get a note",
        "description": "Returns the project/folder note specified by `note_id`.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "note_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The note ID."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/119/notes/n5\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The note object, returned directly (not wrapped).",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/notes/n5",
                    "value": {
                      "id": "n5",
                      "name": "A note in project 119",
                      "type": "note"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Modify a note",
        "description": "Modifies the project/folder Note specified by note_id.\n\nThe specified note is modified by simply supplying replacement text\ncontents in the name field of the payload JSON object.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "note_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The note ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "/projects/119/notes/n5",
                  "value": {
                    "name": "A note modified by the API"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X PATCH \"https://api.testpad.com/api/v1/projects/119/notes/n5\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"A note modified by the API\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The modified note object, returned directly (not wrapped).",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/notes/n5",
                    "value": {
                      "id": "n5",
                      "name": "A note modified by the API",
                      "type": "note"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/projects/{project_id}/folders/{folder_id}/notes/{note_id}": {
      "get": {
        "summary": "Get a note in a folder",
        "description": "Returns the note `note_id` that sits in folder `folder_id`.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder ID."
          },
          {
            "name": "note_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The note ID."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X GET \"https://api.testpad.com/api/v1/projects/119/folders/f2/notes/n7\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The note object, returned directly (not wrapped).",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f2/notes/n7",
                    "value": {
                      "id": "n7",
                      "name": "A note in folder f2",
                      "type": "note"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Modify a note in a folder",
        "description": "Modifies the note `note_id` in folder `folder_id` by supplying replacement text\nin the `name` field.\n\nFor more description of notes and their representation, see `POST /projects/:p/notes`.",
        "tags": [
          "◉ Note Endpoints"
        ],
        "parameters": [
          {
            "name": "project_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The project ID."
          },
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder ID."
          },
          {
            "name": "note_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The note ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "examples": {
                "default": {
                  "summary": "/projects/119/folders/f2/notes/n7",
                  "value": {
                    "name": "A note modified by the API"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "curl",
            "source": "curl -X PATCH \"https://api.testpad.com/api/v1/projects/119/folders/f2/notes/n7\"\n     -H \"Authorization: apikey $ACCESS_TOKEN\"\n     --data-binary @- << 'EOF'\n{\n  \"name\": \"A note modified by the API\"\n}\nEOF\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The modified note object, returned directly (not wrapped).",
            "content": {
              "application/json": {
                "examples": {
                  "default": {
                    "summary": "/projects/119/folders/f2/notes/n7",
                    "value": {
                      "id": "n7",
                      "name": "A note modified by the API",
                      "type": "note"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "result.pass": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "result.pass",
        "operationId": "webhookResultPass",
        "description": "A test was marked pass.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookResultEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "result.fail": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "result.fail",
        "operationId": "webhookResultFail",
        "description": "A test was marked fail.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookResultEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "result.block": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "result.block",
        "operationId": "webhookResultBlock",
        "description": "A test was marked blocked.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookResultEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "result.query": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "result.query",
        "operationId": "webhookResultQuery",
        "description": "A test was marked query.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookResultEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "result.skip": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "result.skip",
        "operationId": "webhookResultSkip",
        "description": "A test was excluded from the run.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookResultEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "result.clear": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "result.clear",
        "operationId": "webhookResultClear",
        "description": "A test's result was cleared.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookResultEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "run.started": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "run.started",
        "operationId": "webhookRunStarted",
        "description": "A run got its first result and moved to started.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookRunEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "run.completed": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "run.completed",
        "operationId": "webhookRunCompleted",
        "description": "A run was completed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookRunEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "run.reopened": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "run.reopened",
        "operationId": "webhookRunReopened",
        "description": "A completed run was reopened.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookRunEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "run.assigned": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "run.assigned",
        "operationId": "webhookRunAssigned",
        "description": "A run's assignee changed, including being unassigned.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookRunEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "run.cleared": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "run.cleared",
        "operationId": "webhookRunCleared",
        "description": "Every result in a run was cleared at once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookRunEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "user.invited": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "user.invited",
        "operationId": "webhookUserInvited",
        "description": "Someone was invited to the account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookUserEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "user.joined": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "user.joined",
        "operationId": "webhookUserJoined",
        "description": "An invited person verified their email and joined.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookUserEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "user.removed": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "user.removed",
        "operationId": "webhookUserRemoved",
        "description": "A member was removed from the account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookUserEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "user.role_changed": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "user.role_changed",
        "operationId": "webhookUserRoleChanged",
        "description": "A member's role was changed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookUserEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    },
    "ping": {
      "post": {
        "tags": [
          "Webhook Events"
        ],
        "summary": "ping",
        "operationId": "webhookPing",
        "description": "A manual test event sent from the webhooks settings page to one webhook.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/WebhookPingEvent"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge receipt with any 2xx, promptly."
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "AuthorizationHeader": {
        "name": "Authorization",
        "in": "header",
        "description": "Used to supply your API ACCESS TOKEN.",
        "required": true,
        "schema": {
          "type": "string"
        },
        "example": "apikey YOUR_TOKEN_HERE"
      }
    },
    "schemas": {
      "FieldDef": {
        "type": "object",
        "title": "field definitions",
        "description": "A field definition object, consisting of:\n\n- **id** (required) - the ID of the field, can be built-in or user-supplied\n\n- **label** (optional) - not allowed for built-in fields whose ids start with `_`\n\n- **show** (optional) - defaults to `true`\n",
        "properties": {
          "id": {
            "type": "string",
            "description": "The field ID. Built-in field IDs start with `_`. User-supplied field IDs cannot start with `_`. (e.g. `_run`, `_tester`, `_createdDate`, `_createdTime`, `_tags`).\n"
          },
          "label": {
            "type": "string",
            "description": "The text displayed for the field (omit when referencing built-in fields as their label cannot be changed)"
          },
          "show": {
            "type": "boolean",
            "description": "Whether to show this field in summaries (default true)."
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": false
      },
      "FieldsInput": {
        "type": "array",
        "description": "A list of field definitions, with three options for how to supply them:\n- as a **string**, used as the field ID or label, with IDs derived by converting spaces to underscores, and labels derived by converting underscores to spaces\n- as a **parenthesized string**, e.g. `(field)`, which works as for a string-supplied field, except the parantheses imply the field should be show:false (i.e. hidden)\n- as an **object** - a `FieldDef`\n",
        "items": {
          "oneOf": [
            {
              "$ref": "#/components/schemas/FieldDef"
            },
            {
              "type": "string"
            }
          ]
        }
      },
      "TestInput": {
        "type": "object",
        "description": "The text of a single line test prompt, along with optional tags.",
        "properties": {
          "text": {
            "type": "string",
            "description": "The text of the test line."
          },
          "tags": {
            "type": "string",
            "description": "Optional tags for this test line, comma-separated."
          }
        },
        "required": [
          "text"
        ],
        "additionalProperties": false
      },
      "TestsInput": {
        "description": "A collection of tests, each test a separate line of text.\nSupply test lines as one of:\n1. a single multiline string, OR 1. an array of strings, OR 1. an array of objects (TestInput).\n",
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestInput"
            }
          }
        ]
      },
      "HeadersInput": {
        "type": "object",
        "description": "A map of header field IDs to their string values.   Each property name is a header ID (for example `\"browser\"`, `\"platform\"`, or a built-in header such as `\"_tester\"`).   Each property value is the header's value as a string.\n",
        "additionalProperties": {
          "type": "string"
        }
      },
      "Result": {
        "type": "object",
        "description": "A single result value, optionally with comments and issue numbers.",
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "blocked",
              "query",
              "skip"
            ]
          },
          "comment": {
            "type": "string",
            "description": "Optional comment"
          },
          "issue": {
            "type": "string",
            "description": "Optional issue number or comma-separated list of issue numbers."
          }
        },
        "required": [
          "result"
        ],
        "additionalProperties": false
      },
      "ResultValueInput": {
        "oneOf": [
          {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "blocked",
              "query",
              "skip"
            ]
          },
          {
            "$ref": "#/components/schemas/Result"
          }
        ]
      },
      "ResultsInput": {
        "type": "object",
        "description": "Map of **test ID → result**.   Keys are test IDs (test numbers supplied as a string to work as an object key).   Values are either a simple status (\"pass\", \"fail\" etc) or a ResultObject.\n",
        "additionalProperties": {
          "$ref": "#/components/schemas/ResultValueInput"
        }
      },
      "RunInput": {
        "type": "object",
        "description": "A single test run with headers and results.\nDo not include `id`, `retest` or `retestOf` in the body — they return 400. A retest is requested via the `?retest=` query parameter, and the run id is assigned by the server.\n",
        "properties": {
          "headers": {
            "$ref": "#/components/schemas/HeadersInput"
          },
          "results": {
            "$ref": "#/components/schemas/ResultsInput"
          },
          "assignee": {
            "type": "string",
            "description": "Optional. Assign the run to an existing team member by their **email address**. This sets the assignee only — it does **not** send a delegation/invite email. (Assigning to anyone/guest, aliases and notifications may be offered later.)\n"
          }
        },
        "additionalProperties": false
      },
      "RunsInput": {
        "type": "array",
        "description": "List of runs to include with the script.    \n",
        "items": {
          "$ref": "#/components/schemas/RunInput"
        }
      },
      "ScriptInput": {
        "type": "object",
        "description": "Represents a script object when supplied as input, including its metadata, fields, tests, and test runs.\n",
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the script.   Defaults to **\"New Script\"** if not supplied.\n"
          },
          "description": {
            "type": "string",
            "description": "Optional description for the script.   Left empty if not supplied.\n"
          },
          "fields": {
            "$ref": "#/components/schemas/FieldsInput"
          },
          "tests": {
            "$ref": "#/components/schemas/TestsInput"
          },
          "runs": {
            "$ref": "#/components/schemas/RunsInput"
          }
        },
        "additionalProperties": false
      },
      "Fields": {
        "type": "array",
        "description": "A list of fields a script is using\n",
        "items": {
          "$ref": "#/components/schemas/FieldDef"
        }
      },
      "Test": {
        "type": "object",
        "description": "A single test prompt row, including possible tags",
        "properties": {
          "id": {
            "type": "integer",
            "description": "the ID of the test row"
          },
          "text": {
            "type": "string",
            "description": "The text of the test line."
          },
          "tags": {
            "type": "string",
            "description": "Any tags for this test line, comma-separated."
          },
          "indent": {
            "type": "integer",
            "description": "How indented this test row is in the outline hierarchy of the script. Top level title rows are indent=0. Child rows of top level title rows are indent=1. And so on.\n"
          }
        }
      },
      "Tests": {
        "description": "A collection of tests, each test a separate line of text.\nThe format of the collection depends on the `tests` URL parameter (see [Common URL Parameters](/openapi/common-url-parameters)).\n",
        "oneOf": [
          {
            "type": "string"
          },
          {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Test"
            }
          }
        ]
      },
      "Headers": {
        "type": "object",
        "description": "The header values for the header fields of a test run",
        "properties": {
          "_run": {
            "type": "string",
            "description": "Run identifier"
          },
          "_createdDate": {
            "type": "string",
            "description": "The date (day) this test run was created"
          },
          "_createdTime": {
            "type": "string",
            "description": "The time of day that this test run was created"
          },
          "_tags": {
            "type": "string",
            "description": "The value of the Tags Filter field, specifying which tags to use for filtering tests by tag"
          },
          "_tester": {
            "type": "string",
            "description": "The displayable name of the assignee of this test run, might be a user's name, or an alias if using Guest Testing"
          }
        },
        "additionalProperties": {
          "type": "string",
          "description": "Along with any custom header fields"
        }
      },
      "Results": {
        "type": "object",
        "description": "Sparse map of the result values for tests in this script for a test run. The keys are the string version of a test row ID. The values are Result objects.\n",
        "additionalProperties": {
          "$ref": "#/components/schemas/Result"
        }
      },
      "Run": {
        "type": "object",
        "description": "A single test run with headers and results.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The run identifier as a string, e.g. \"6\"; a retest run's id has the form \"N.1\"."
          },
          "state": {
            "type": "string",
            "description": "The run's state.",
            "enum": [
              "new",
              "started",
              "complete"
            ]
          },
          "created": {
            "type": "string",
            "description": "The timestamp of when this test run was created"
          },
          "assignee": {
            "type": "object",
            "description": "An object describing who this test run has been assigned to",
            "properties": {
              "email": {
                "type": "string",
                "description": "the last email address that an invitation to make this test run was sent to (normally used for Guest Testing)"
              },
              "id": {
                "type": "string",
                "description": "the user ID of who this has been assigned to, or `_anyone` if not assigned"
              },
              "name": {
                "type": "string",
                "description": "the name of the assignee of this test run, either the name associated with a known user in the account, or an alias supplied for e.g. when guest testing"
              }
            }
          },
          "headers": {
            "$ref": "#/components/schemas/Headers"
          },
          "results": {
            "$ref": "#/components/schemas/Results"
          }
        },
        "required": [
          "id",
          "state",
          "headers",
          "results",
          "assignee"
        ],
        "additionalProperties": false
      },
      "Runs": {
        "type": "array",
        "description": "The list of test runs in a script.",
        "items": {
          "$ref": "#/components/schemas/Run"
        }
      },
      "ProgressTerse": {
        "type": "object",
        "description": "Terse progress summary: the result counts plus a one-line summary.\n",
        "properties": {
          "total": {
            "type": "integer",
            "description": "The number of tests that have a recorded result so far."
          },
          "pass": {
            "type": "integer",
            "description": "Count of results marked pass."
          },
          "fail": {
            "type": "integer",
            "description": "Count of results marked fail."
          },
          "block": {
            "type": "integer",
            "description": "Count of results marked blocked."
          },
          "query": {
            "type": "integer",
            "description": "Count of results marked query."
          },
          "summary": {
            "type": "string",
            "description": "Readable one-line text summary of the counts."
          }
        }
      },
      "ProgressFull": {
        "type": "object",
        "description": "Full progress summary including numeric totals and percentages, along with textual summaries. Note that percentages are integer, and rounded up to 1 if non-zero, in a way that they all still sum to 100.\n",
        "properties": {
          "pass": {
            "type": "integer",
            "description": "Number of passing tests."
          },
          "passPc": {
            "type": "integer",
            "description": "Percent of total that passed."
          },
          "fail": {
            "type": "integer",
            "description": "Number of failed tests."
          },
          "failPc": {
            "type": "integer",
            "description": "Percent of total that failed."
          },
          "block": {
            "type": "integer",
            "description": "Number of blocked tests."
          },
          "blockPc": {
            "type": "integer",
            "description": "Percent of total that were blocked."
          },
          "query": {
            "type": "integer",
            "description": "Number of tests in query state."
          },
          "queryPc": {
            "type": "integer",
            "description": "Percent of total that are in query state."
          },
          "tried": {
            "type": "integer",
            "description": "Number of tests attempted."
          },
          "triedPc": {
            "type": "integer",
            "description": "Percent of total that were attempted."
          },
          "remain": {
            "type": "integer",
            "description": "Number of remaining tests."
          },
          "remainPc": {
            "type": "integer",
            "description": "Percent of total remaining."
          },
          "total": {
            "type": "integer",
            "description": "Total number of tests."
          },
          "summary": {
            "type": "string",
            "description": "Readable text summary."
          }
        }
      },
      "Progress": {
        "description": "Progress object — may be either **terse** or **full** format depending on the URL parameter `progress`.\n",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ProgressTerse"
          },
          {
            "$ref": "#/components/schemas/ProgressFull"
          }
        ]
      },
      "Script": {
        "type": "object",
        "description": "Represents a script object when returned in responses, including its metadata, fields, tests, and test runs.\n",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The ID of the script, generated by the server.\n"
          },
          "name": {
            "type": "string",
            "description": "The name of the script.  \n"
          },
          "description": {
            "type": "string",
            "description": "Description for the script.\n"
          },
          "comments": {
            "type": "string",
            "description": "Report comments for the script."
          },
          "type": {
            "type": "string",
            "description": "Always \"script\"."
          },
          "created": {
            "type": "string",
            "description": "The timestamp for when the script was created.\n"
          },
          "archived": {
            "type": "boolean",
            "description": "Whether the script has been archived within its project or not. Note that if the project itself has been archived, this property might still say `false` - it only relates to whether the script has been archived within the project.\n"
          },
          "fields": {
            "$ref": "#/components/schemas/Fields"
          },
          "tests": {
            "$ref": "#/components/schemas/Tests"
          },
          "runs": {
            "$ref": "#/components/schemas/Runs"
          },
          "progress": {
            "$ref": "#/components/schemas/Progress"
          }
        },
        "additionalProperties": false
      },
      "WebhookAbout": {
        "type": "object",
        "description": "A constant block identifying the sender. Handy when one endpoint receives webhooks from more than one source.",
        "properties": {
          "notice": {
            "type": "string",
            "description": "A human-readable note naming Testpad and the sending account."
          },
          "contact": {
            "type": "string",
            "description": "Where to get help with these webhooks."
          }
        },
        "additionalProperties": false
      },
      "WebhookEventMeta": {
        "type": "object",
        "title": "_event",
        "description": "Metadata about this one event.",
        "properties": {
          "topic": {
            "type": "string",
            "description": "The event topic, in `entity.action` form (for example `result.pass`). This is the value to switch on in your handler. See [Webhook Events](/openapi/webhook-events).\n"
          },
          "id": {
            "type": "string",
            "description": "A unique ID for this event. Stable if the same event is re-sent, so you can de-duplicate on it."
          },
          "webhook": {
            "type": "string",
            "description": "The ID of the webhook subscription that matched this event."
          },
          "ts": {
            "type": "string",
            "format": "date-time",
            "description": "When the event was generated (UTC)."
          }
        },
        "required": [
          "topic",
          "id",
          "webhook",
          "ts"
        ],
        "additionalProperties": false
      },
      "WebhookAccountRef": {
        "type": "object",
        "description": "The Testpad account the event belongs to.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The account ID (its slug)."
          },
          "name": {
            "type": "string",
            "description": "The account name."
          },
          "url": {
            "type": "string",
            "description": "A link to the account."
          }
        },
        "additionalProperties": false
      },
      "WebhookActor": {
        "type": "object",
        "title": "actor",
        "description": "Who caused the event. A team member shows their name and email. Actions taken by Testpad Support on your behalf show as \"Testpad Support\", and actions taken through the API show as \"API\". The email is empty in those two cases.\n",
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "WebhookProjectRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "WebhookScriptRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "WebhookRunContextRef": {
        "type": "object",
        "description": "A lighter run reference used where the run is only *context* (result.* events, in `context.run`): the same fields as the run, but without its per-test `results` — those would be redundant and verbose, since the event's own `results` carry them.\n",
        "properties": {
          "id": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "new",
              "started",
              "complete"
            ]
          },
          "headers": {
            "$ref": "#/components/schemas/Headers"
          },
          "assignee": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            }
          },
          "url": {
            "type": "string",
            "description": "A link to the run."
          }
        },
        "additionalProperties": false
      },
      "WebhookTestRef": {
        "type": "object",
        "title": "test",
        "description": "A lightweight reference to the test row a result was set against (the test is context for a result, not the subject). Empty optional fields are omitted.\n",
        "properties": {
          "id": {
            "type": "integer"
          },
          "text": {
            "type": "string",
            "description": "The test prompt text."
          },
          "tags": {
            "type": "string",
            "description": "Any tags on the test row, space-separated. Omitted when the test has none."
          },
          "url": {
            "type": "string",
            "description": "A link to the test (webhook-only convenience)."
          }
        },
        "required": [
          "id",
          "text",
          "url"
        ],
        "additionalProperties": false
      },
      "WebhookResultValue": {
        "type": "object",
        "title": "result",
        "description": "The result that was set. For a `result.clear` event the `result` value is an empty string.",
        "properties": {
          "result": {
            "type": "string",
            "description": "One of pass, fail, blocked, query, skip, or empty when the result was cleared. A skip means the test was taken out of the run."
          },
          "comment": {
            "type": "string"
          },
          "issue": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "WebhookResultEvent": {
        "type": "object",
        "description": "A `result.*` event. Sent when test results are set, cleared or changed. When a bulk fill sets several results at once, each outcome is batched into one event whose `results` array carries every test of that outcome.\n",
        "properties": {
          "_about": {
            "$ref": "#/components/schemas/WebhookAbout"
          },
          "_api_version": {
            "type": "integer",
            "description": "The payload schema version. Currently `1`."
          },
          "_event": {
            "$ref": "#/components/schemas/WebhookEventMeta"
          },
          "context": {
            "type": "object",
            "properties": {
              "account": {
                "$ref": "#/components/schemas/WebhookAccountRef"
              },
              "actor": {
                "$ref": "#/components/schemas/WebhookActor"
              },
              "project": {
                "$ref": "#/components/schemas/WebhookProjectRef"
              },
              "script": {
                "$ref": "#/components/schemas/WebhookScriptRef"
              },
              "run": {
                "$ref": "#/components/schemas/WebhookRunContextRef"
              }
            },
            "additionalProperties": false
          },
          "results": {
            "type": "array",
            "description": "One entry per test that took this outcome.",
            "items": {
              "type": "object",
              "properties": {
                "test": {
                  "$ref": "#/components/schemas/WebhookTestRef"
                },
                "result": {
                  "$ref": "#/components/schemas/WebhookResultValue"
                }
              },
              "additionalProperties": false
            }
          }
        },
        "required": [
          "_event",
          "_api_version",
          "context",
          "results"
        ],
        "additionalProperties": false
      },
      "WebhookRunRef": {
        "description": "The test run the event is the *subject* of (run.* events, in `run`) — the same object the API returns (see the `Run` schema, which now includes `state` and `assignee`), plus a webhook-only `url`.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/Run"
          },
          {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "description": "A link to the run."
              }
            }
          }
        ]
      },
      "WebhookRunEvent": {
        "type": "object",
        "description": "A `run.*` event (started, completed, reopened, cleared, or assignee changed). The run is the subject of the event, so it sits at the top level in `run`; the project and script are context.\n",
        "properties": {
          "_about": {
            "$ref": "#/components/schemas/WebhookAbout"
          },
          "_api_version": {
            "type": "integer"
          },
          "_event": {
            "$ref": "#/components/schemas/WebhookEventMeta"
          },
          "context": {
            "type": "object",
            "properties": {
              "account": {
                "$ref": "#/components/schemas/WebhookAccountRef"
              },
              "actor": {
                "$ref": "#/components/schemas/WebhookActor"
              },
              "project": {
                "$ref": "#/components/schemas/WebhookProjectRef"
              },
              "script": {
                "$ref": "#/components/schemas/WebhookScriptRef"
              }
            },
            "additionalProperties": false
          },
          "run": {
            "$ref": "#/components/schemas/WebhookRunRef"
          }
        },
        "required": [
          "_event",
          "_api_version",
          "context",
          "run"
        ],
        "additionalProperties": false
      },
      "WebhookMember": {
        "type": "object",
        "title": "member",
        "description": "The account member the event is about (for `user.*` events).",
        "properties": {
          "id": {
            "type": "integer",
            "description": "The member's ID within the account."
          },
          "type": {
            "type": "string",
            "description": "The member's role, as a human-readable label — one of `owner`, `admin`, `normal`, `guest tester`, `guest report viewer`, `external`.\n"
          },
          "old_type": {
            "type": "string",
            "description": "Only on `user.role_changed`: the member's previous role (same label set as `type`), so you can see the transition.\n"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "name",
          "email"
        ],
        "additionalProperties": false
      },
      "WebhookUserEvent": {
        "type": "object",
        "description": "A `user.*` event. Sent when a member is invited, joins, is removed, or has their role changed.",
        "properties": {
          "_about": {
            "$ref": "#/components/schemas/WebhookAbout"
          },
          "_api_version": {
            "type": "integer"
          },
          "_event": {
            "$ref": "#/components/schemas/WebhookEventMeta"
          },
          "context": {
            "type": "object",
            "properties": {
              "account": {
                "$ref": "#/components/schemas/WebhookAccountRef"
              },
              "actor": {
                "$ref": "#/components/schemas/WebhookActor"
              }
            },
            "additionalProperties": false
          },
          "member": {
            "$ref": "#/components/schemas/WebhookMember"
          }
        },
        "required": [
          "_event",
          "_api_version",
          "context",
          "member"
        ],
        "additionalProperties": false
      },
      "WebhookPingEvent": {
        "type": "object",
        "description": "A `ping` event. Sent from the app to test one webhook, regardless of its topic filter.",
        "properties": {
          "_about": {
            "$ref": "#/components/schemas/WebhookAbout"
          },
          "_api_version": {
            "type": "integer"
          },
          "_event": {
            "$ref": "#/components/schemas/WebhookEventMeta"
          },
          "context": {
            "type": "object",
            "properties": {
              "account": {
                "$ref": "#/components/schemas/WebhookAccountRef"
              },
              "actor": {
                "$ref": "#/components/schemas/WebhookActor"
              }
            },
            "additionalProperties": false
          }
        },
        "required": [
          "_event",
          "_api_version",
          "context"
        ],
        "additionalProperties": false
      }
    }
  }
}