{"openapi":"3.1.0","info":{"title":"Trailcam API","version":"1","description":"Upload a build of your app and run your flows on it, from CI. The command-line tool (`npx @trailcam/cli`) and the GitHub Action do this for you; this is the API they use."},"servers":[{"url":"https://app.trailcam.dev/api"}],"security":[{"ciKey":[]},{"githubActions":[]}],"tags":[{"name":"Builds","description":"Upload a build: an Android .apk, or a zipped iOS simulator .app."},{"name":"Runs","description":"Run a project's flows on its builds, and follow how they go."}],"paths":{"/projects/{projectId}/builds":{"post":{"tags":["Builds"],"summary":"Start an upload","description":"Starts uploading a build in parts: send each part, then finish. For builds up to 2 GB. Answers how to split the file and which parts have arrived.","operationId":"startUpload","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project's ID, from its CI tab in Trailcam. A CI key works only for the project it was made for.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartUpload"}}}},"responses":{"201":{"description":"The upload has started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadSession"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"413":{"$ref":"#/components/responses/413"},"429":{"$ref":"#/components/responses/429"}}}},"/projects/{projectId}/builds/upload":{"put":{"tags":["Builds"],"summary":"Upload a build in one request","description":"For smaller builds: the request's body is the file.","operationId":"uploadBuild","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project's ID, from its CI tab in Trailcam. A CI key works only for the project it was made for.","schema":{"type":"string","format":"uuid"}},{"name":"fileName","in":"query","required":true,"description":"The build's file name, e.g. `app-release.apk`.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}}},"responses":{"201":{"description":"Uploaded; Trailcam reads it next.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Build"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"413":{"$ref":"#/components/responses/413"},"429":{"$ref":"#/components/responses/429"}}}},"/builds/{buildId}/upload":{"get":{"tags":["Builds"],"summary":"Resume an upload","description":"Which parts have arrived, to pick up an upload that was interrupted.","operationId":"getUpload","parameters":[{"name":"buildId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The upload as it stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadSession"}}}},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/builds/{buildId}/parts/{partNumber}":{"put":{"tags":["Builds"],"summary":"Send a part","description":"Parts are numbered from 1, each `partSize` bytes but the last. Sending a part again replaces it.","operationId":"uploadPart","parameters":[{"name":"buildId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"partNumber","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}}},"responses":{"200":{"description":"The part arrived.","content":{"application/json":{"schema":{"type":"object","properties":{"partNumber":{"type":"integer"},"etag":{"type":"string"}},"required":["partNumber","etag"]}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"409":{"$ref":"#/components/responses/409"},"429":{"$ref":"#/components/responses/429"}}}},"/builds/{buildId}/complete":{"post":{"tags":["Builds"],"summary":"Finish an upload","description":"Once every part has arrived. Trailcam then reads the build: it's `ready` to run in a few seconds, or `failed` with why.","operationId":"completeUpload","parameters":[{"name":"buildId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The build.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Build"}}}},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"409":{"$ref":"#/components/responses/409"},"429":{"$ref":"#/components/responses/429"}}}},"/projects/{projectId}/batches":{"post":{"tags":["Runs"],"summary":"Run flows","description":"Runs the project's flows on the given builds (one per platform), one run per flow and device. Without `flows`, every flow in the project; flows with errors don't run, and are listed as skipped. Without `devices`, each build runs on its platform's default device; with them, on those (each a model from `GET /devices`, with `os` for a system other than its default), and a build no device is named for runs on its platform's default.","operationId":"startBatch","parameters":[{"name":"projectId","in":"path","required":true,"description":"The project's ID, from its CI tab in Trailcam. A CI key works only for the project it was made for.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartBatch"}}}},"responses":{"201":{"description":"The runs have started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Batch"}}}},"400":{"$ref":"#/components/responses/400"},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"409":{"$ref":"#/components/responses/409"},"429":{"$ref":"#/components/responses/429"}}}},"/devices":{"get":{"tags":["Runs"],"summary":"List devices","description":"The devices flows run on: each model with the systems it's offered on, newest first, and the one a run gets when it names none. The same for everyone, so it needs no sign-in.","operationId":"listDevices","security":[],"responses":{"200":{"description":"The devices.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Devices"}}}}}}},"/batches/{batchId}":{"get":{"tags":["Runs"],"summary":"See how runs are going","description":"`running` while any run is waiting or running. Then `failed` if a flow failed, was skipped for its errors or its build couldn't run; otherwise `couldnt_run` if Trailcam couldn't run one, `stopped` if one was stopped, or `passed`.","operationId":"getBatch","parameters":[{"name":"batchId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The runs, as they stand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Batch"}}}},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}},"/batches/{batchId}/stop":{"post":{"tags":["Runs"],"summary":"Stop runs","description":"Stops the runs that are still waiting or running.","operationId":"stopBatch","parameters":[{"name":"batchId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The runs, stopping.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Batch"}}}},"401":{"$ref":"#/components/responses/401"},"403":{"$ref":"#/components/responses/403"},"404":{"$ref":"#/components/responses/404"},"429":{"$ref":"#/components/responses/429"}}}}},"components":{"securitySchemes":{"ciKey":{"type":"http","scheme":"bearer","description":"A CI key (`tck_…`), made on the project's CI tab in Trailcam. It acts for that one project: it uploads builds, runs the project's flows on the builds it uploaded, and reads the results of the runs it started."},"githubActions":{"type":"http","scheme":"bearer","description":"From GitHub Actions in a repository linked to the project: the workflow's own token, requested with `id-token: write` and Trailcam's address (`https://app.trailcam.dev`) as its audience, with the project named in the `X-Trailcam-Project` header. No secret to store."}},"responses":{"400":{"description":"The request isn't valid; the message says what to change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No valid CI key or token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key or token can't do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not this key's project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Not possible as things stand: the upload already finished, a build hasn't finished uploading or couldn't be read, the project has no flows yet, or the workspace's plan doesn't allow it (its runs are used up, for one). The message says which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The build is larger than Trailcam takes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests: wait a moment, then try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Build":{"type":"object","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"status":{"type":"string","enum":["uploading","processing","ready","failed"]},"failureCode":{"type":["string","null"]},"fileName":{"type":"string"},"size":{"type":"number"},"platform":{"anyOf":[{"type":"string","enum":["ios","android"]},{"type":"null"}]},"appName":{"type":["string","null"]},"bundleId":{"type":["string","null"]},"version":{"type":["string","null"]},"buildNumber":{"type":["string","null"]},"minOsVersion":{"type":["string","null"]},"uploadedBy":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"user"},"userId":{"type":"string"}},"required":["type","userId"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"api_key"},"name":{"type":["string","null"]}},"required":["type","name"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"github"},"repository":{"type":["string","null"]}},"required":["type","repository"],"additionalProperties":false}]},"isProduction":{"type":"boolean"},"createdAt":{"type":"string"},"readyAt":{"type":["string","null"]}},"required":["id","projectId","status","failureCode","fileName","size","platform","appName","bundleId","version","buildNumber","minOsVersion","uploadedBy","isProduction","createdAt","readyAt"],"additionalProperties":false},"UploadSession":{"type":"object","properties":{"build":{"type":"object","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"status":{"type":"string","enum":["uploading","processing","ready","failed"]},"failureCode":{"type":["string","null"]},"fileName":{"type":"string"},"size":{"type":"number"},"platform":{"anyOf":[{"type":"string","enum":["ios","android"]},{"type":"null"}]},"appName":{"type":["string","null"]},"bundleId":{"type":["string","null"]},"version":{"type":["string","null"]},"buildNumber":{"type":["string","null"]},"minOsVersion":{"type":["string","null"]},"uploadedBy":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"user"},"userId":{"type":"string"}},"required":["type","userId"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"api_key"},"name":{"type":["string","null"]}},"required":["type","name"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"github"},"repository":{"type":["string","null"]}},"required":["type","repository"],"additionalProperties":false}]},"isProduction":{"type":"boolean"},"createdAt":{"type":"string"},"readyAt":{"type":["string","null"]}},"required":["id","projectId","status","failureCode","fileName","size","platform","appName","bundleId","version","buildNumber","minOsVersion","uploadedBy","isProduction","createdAt","readyAt"],"additionalProperties":false},"partSize":{"type":"number"},"partCount":{"type":"number"},"uploadedParts":{"type":"array","items":{"type":"number"}}},"required":["build","partSize","partCount","uploadedParts"],"additionalProperties":false},"StartUpload":{"type":"object","properties":{"fileName":{"type":"string","minLength":1,"maxLength":255},"size":{"type":"integer","exclusiveMinimum":0,"maximum":2147483648}},"required":["fileName","size"],"additionalProperties":false},"StartBatch":{"type":"object","properties":{"builds":{"minItems":1,"maxItems":2,"type":"array","items":{"type":"string"}},"flows":{"minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":120}},"devices":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"object","properties":{"model":{"type":"string","minLength":1,"maxLength":40},"os":{"type":"string","minLength":1,"maxLength":20}},"required":["model"],"additionalProperties":false}},"context":{"type":"object","properties":{"provider":{"type":"string","enum":["github_actions","gitlab","bitrise","circleci","codemagic","xcode_cloud","eas","other"]},"repository":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9_.-]+(?:\\/[A-Za-z0-9_.-]+)+$"},"branch":{"type":"string","minLength":1,"maxLength":255},"commit":{"type":"string","pattern":"^(?:[0-9a-f]{40}|[0-9a-f]{64})$"},"title":{"type":"string","minLength":1,"maxLength":200},"pullRequest":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647},"url":{"type":"string","maxLength":2000,"pattern":"^https:\\/\\/[^\\s]+$"}},"required":["provider"],"additionalProperties":false}},"required":["builds"],"additionalProperties":false},"Batch":{"type":"object","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"status":{"type":"string","enum":["running","passed","failed","couldnt_run","stopped"]},"counts":{"type":"object","properties":{"passed":{"type":"number"},"failed":{"type":"number"},"couldntRun":{"type":"number"},"stopped":{"type":"number"},"active":{"type":"number"},"skipped":{"type":"number"},"brokenBuilds":{"type":"number"}},"required":["passed","failed","couldntRun","stopped","active","skipped"],"additionalProperties":false},"context":{"type":"object","properties":{"provider":{"type":"string","enum":["github_actions","gitlab","bitrise","circleci","codemagic","xcode_cloud","eas","other"]},"repository":{"type":["string","null"]},"branch":{"type":["string","null"]},"commit":{"type":["string","null"]},"title":{"type":["string","null"]},"pullRequest":{"type":["number","null"]},"url":{"type":["string","null"]}},"required":["provider","repository","branch","commit","title","pullRequest","url"],"additionalProperties":false},"startedBy":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","const":"api_key"},"name":{"type":["string","null"]}},"required":["type","name"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"github"},"repository":{"type":"string"}},"required":["type","repository"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","const":"user"},"userId":{"type":"string"}},"required":["type","userId"],"additionalProperties":false}]},"builds":{"type":"array","items":{"type":"object","properties":{"id":{"type":["string","null"]},"label":{"type":"string"},"platform":{"type":"string","enum":["ios","android"]}},"required":["id","label","platform"],"additionalProperties":false}},"devices":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string","enum":["ios","android"]},"model":{"type":"string"},"osVersion":{"type":"string"}},"required":["platform","model","osVersion"],"additionalProperties":false}},"github":{"anyOf":[{"type":"object","properties":{"repository":{"type":"string"},"checkUrl":{"type":["string","null"]}},"required":["repository","checkUrl"],"additionalProperties":false},{"type":"null"}]},"createdAt":{"type":"string"},"finishedAt":{"type":["string","null"]},"runs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"flow":{"type":"object","properties":{"id":{"type":["string","null"]},"name":{"type":"string"},"revision":{"type":"number"}},"required":["id","name","revision"],"additionalProperties":false},"build":{"type":"object","properties":{"id":{"type":["string","null"]},"label":{"type":"string"}},"required":["id","label"],"additionalProperties":false},"platform":{"type":"string","enum":["ios","android"]},"device":{"type":"object","properties":{"model":{"type":"string"},"osVersion":{"type":"string"}},"required":["model","osVersion"],"additionalProperties":false},"trigger":{"type":"string","enum":["manual","ci","schedule"]},"triggeredBy":{"type":["string","null"]},"checkId":{"type":["string","null"]},"recheckOf":{"type":["string","null"]},"batchId":{"type":["string","null"]},"status":{"type":"string","enum":["queued","running","passed","failed","errored","cancelled"]},"attempt":{"type":"number"},"failure":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["assertion_failed","element_not_found","app_crashed","flow_error","no_email","timed_out","build_invalid","stuck","plan_limit","device_unavailable","install_failed","agent_error","upload_failed","no_device","ai_unavailable"]},"message":{"type":["string","null"]},"stepIndex":{"type":["number","null"]}},"required":["kind","message","stepIndex"],"additionalProperties":false},{"type":"null"}]},"steps":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","minimum":0,"maximum":10000},"command":{"type":"string","maxLength":2000},"status":{"type":"string","enum":["passed","failed","skipped"]},"startMs":{"type":"integer","minimum":0,"maximum":2147483647},"durationMs":{"type":"integer","minimum":0,"maximum":2147483647}},"required":["index","command","status","durationMs"],"additionalProperties":false}},"artifacts":{"type":"array","items":{"type":"string","enum":["video","screenshot","hierarchy","log","report","network","accessibility"]}},"stopping":{"type":"boolean"},"stopReason":{"anyOf":[{"type":"string","enum":["requested","build_deleted","check_removed","project_deleted"]},{"type":"null"}]},"deviceSeconds":{"type":["number","null"]},"flowMs":{"type":["number","null"]},"countedRuns":{"type":"number"},"queuedAt":{"type":"string"},"startedAt":{"type":["string","null"]},"finishedAt":{"type":["string","null"]},"timelineStartedAt":{"type":"string"},"draft":{"type":"boolean"},"proofOf":{"type":["string","null"]},"kind":{"type":"string","const":"explore"},"likely":{"anyOf":[{"type":"object","properties":{"cause":{"type":"string","enum":["app_bug","app_crash","server_error","test_outdated","test_data","in_the_way","too_slow","flaky"]},"confidence":{"type":"number","minimum":0,"maximum":1}},"required":["cause","confidence"],"additionalProperties":false},{"type":"null"}]},"slower":{"anyOf":[{"type":"object","properties":{"flowMs":{"type":"number"},"usualMs":{"type":"number"},"step":{"anyOf":[{"type":"object","properties":{"index":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"command":{"type":"string"},"ms":{"type":"number"},"usualMs":{"type":"number"}},"required":["index","command","ms","usualMs"],"additionalProperties":false},{"type":"null"}]}},"required":["flowMs","usualMs","step"],"additionalProperties":false},{"type":"null"}]},"looksWrong":{"type":"boolean"},"accessibility":{"type":"object","properties":{"screens":{"type":"number"},"findings":{"type":"number"}},"required":["screens","findings"],"additionalProperties":false}},"required":["id","projectId","flow","build","platform","device","trigger","triggeredBy","checkId","recheckOf","batchId","status","attempt","failure","steps","artifacts","stopping","deviceSeconds","flowMs","queuedAt","startedAt","finishedAt"],"additionalProperties":false}},"skipped":{"type":"array","items":{"type":"object","properties":{"flow":{"type":"object","properties":{"id":{"type":["string","null"]},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"reason":{"type":"string","const":"flow_has_errors"}},"required":["flow","reason"],"additionalProperties":false}},"url":{"type":"string"}},"required":["id","projectId","status","counts","context","startedBy","builds","devices","github","createdAt","finishedAt","runs","skipped","url"],"additionalProperties":false},"Devices":{"type":"object","properties":{"devices":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string","enum":["ios","android"]},"model":{"type":"string"},"name":{"type":"string"},"form":{"type":"string","enum":["phone","tablet","foldable"]},"os":{"type":"array","items":{"type":"object","properties":{"version":{"type":"string"},"name":{"type":"string"}},"required":["version","name"],"additionalProperties":false}},"defaultOs":{"type":"string"},"isDefault":{"type":"boolean"}},"required":["platform","model","name","form","os","defaultOs","isDefault"],"additionalProperties":false}}},"required":["devices"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"}},"required":["code","message","requestId"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}}