{"openapi":"3.1.0","info":{"title":"Semaloop API","version":"1.1.0","summary":"Push mobile builds to Semaloop, and drive tests, runs and issues over MCP.","description":"Semaloop is a continuous testing platform for mobile apps. AI agents test apps like real users, catching crashes, regressions, and UI bugs before every release. Teams describe their app in natural language; Semaloop validates every build — on real iPhones, and on Android emulators — surfaces failures with full context, and replaces brittle scripted UI tests.\n\nThis specification covers the endpoints an agent can reach without a browser: the public pricing and demo-booking endpoints on semaloop.com, the build-upload endpoints on api.semaloop.com that take a Semaloop API key, and the Streamable HTTP MCP server on mcp.semaloop.com that exposes tests, runs, issues, secrets and custom tools as tools.\n\nCreate an API key in https://app.semaloop.com under Settings → API keys. Full documentation is at https://docs.semaloop.com.","termsOfService":"https://semaloop.com/terms","contact":{"name":"Semaloop","url":"https://semaloop.com/demo"}},"externalDocs":{"description":"Semaloop documentation","url":"https://docs.semaloop.com"},"servers":[{"url":"https://semaloop.com","description":"Public endpoints, no authentication"},{"url":"https://api.semaloop.com","description":"REST API, authenticated with an API key"},{"url":"https://mcp.semaloop.com","description":"MCP server"}],"tags":[{"name":"Pricing","description":"Published pricing, computed on demand."},{"name":"Demo","description":"Find and book a 30-minute Semaloop demo on someone's behalf. Walkthrough with live slots: https://semaloop.com/demo.md."},{"name":"Uploads","description":"Push a build to Semaloop and trigger its test runs."},{"name":"MCP","description":"Model Context Protocol server over Streamable HTTP."}],"paths":{"/api/pricing/quote":{"get":{"tags":["Pricing"],"operationId":"quotePricing","summary":"Quote a monthly Semaloop bill","description":"Computes a bill from the published pricing grid. An execution is one test run once, so monthly executions are `tests × runsPerMonth × platforms`. Tests, runsPerMonth and agents are all per platform. Past 4,000 executions a month pricing is bespoke and the fees come back null.","security":[],"servers":[{"url":"https://semaloop.com"}],"parameters":[{"name":"tests","in":"query","required":true,"description":"Number of tests in the suite.","schema":{"type":"integer","minimum":1,"maximum":2000}},{"name":"runsPerMonth","in":"query","required":true,"description":"How many times the full suite runs each month.","schema":{"type":"integer","minimum":1,"maximum":1000}},{"name":"agents","in":"query","required":false,"description":"Devices running tests in parallel, per platform. Defaults to the number that keeps the suite under an hour.","schema":{"type":"integer","minimum":2,"maximum":20}},{"name":"platforms","in":"query","required":false,"description":"Which platforms the suite runs on. `both` doubles executions and buys an agent pool on each platform; runtime per build is unchanged.","schema":{"type":"string","enum":["both","ios","android"],"default":"ios"}},{"name":"billing","in":"query","required":false,"description":"Annual billing takes 10% off both fees.","schema":{"type":"string","enum":["annual","monthly"],"default":"annual"}}],"responses":{"200":{"description":"A quote in USD","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingQuote"}}}},"400":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/booking/availability":{"get":{"tags":["Demo"],"operationId":"listDemoAvailability","summary":"List bookable demo slots","description":"Live availability for a 30-minute Semaloop demo, soonest first. Each slot carries UTC milliseconds, ISO 8601 instants, and a label in the requested time zone. Pass a slot's `startIso` to `bookDemo`.","security":[],"servers":[{"url":"https://semaloop.com"}],"parameters":[{"name":"timezone","in":"query","required":false,"description":"IANA time zone for the `local` labels. Unknown zones fall back to Europe/London.","schema":{"type":"string","default":"Europe/London"}}],"responses":{"200":{"description":"Bookable slots","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoAvailability"}}}},"502":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/booking":{"post":{"tags":["Demo"],"operationId":"bookDemo","summary":"Book a demo slot","description":"Books a slot currently on offer from `listDemoAvailability` and sends a calendar invite to `email`. Only book for a person who asked you to, with their own work email.","security":[],"servers":[{"url":"https://semaloop.com"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoBookingRequest"}}}},"responses":{"200":{"description":"Booked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoBooking"}}}},"400":{"description":"A field is missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoBookingError"}}}},"409":{"description":"The slot is no longer on offer; `alternatives` lists the nearest open ones","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoBookingError"}}}},"502":{"description":"The calendar provider failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoBookingError"}}}}}}},"/api/v1/uploads":{"post":{"tags":["Uploads"],"operationId":"createUpload","summary":"Create a build upload","description":"Returns an upload id and a pre-signed URL, valid for one hour. PUT the zipped `.app` or `.ipa` to that URL, then call the finalize endpoint.","security":[{"semaloopApiKey":[]}],"servers":[{"url":"https://api.semaloop.com"}],"responses":{"200":{"description":"Upload id and pre-signed PUT URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUploadResponse"}}}},"401":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/uploads/finalize":{"post":{"tags":["Uploads"],"operationId":"finalizeUpload","summary":"Finalize a build upload","description":"Registers the uploaded artefact as a build and queues a run of every active test against it.","security":[{"semaloopApiKey":[]}],"servers":[{"url":"https://api.semaloop.com"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FinalizeUploadRequest"}}}},"responses":{"200":{"description":"Build registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FinalizeUploadResponse"}}}},"400":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/mcp":{"post":{"tags":["MCP"],"operationId":"callMcp","summary":"Semaloop MCP server (Streamable HTTP)","description":"JSON-RPC 2.0 over Streamable HTTP. Tools cover apps, builds, tests, runs, run artefacts, issues, signals, secrets, custom tools and feedback; call `tools/list` for the live set. Authenticate interactively with OAuth 2.1 (dynamic client registration is supported, and `/.well-known/oauth-protected-resource` declares the scopes), or from CI with a Semaloop API key as a bearer token against https://mcp.semaloop.com/mcp/key.","security":[{"semaloopMcpOAuth":["semaloop:read","semaloop:write"]},{"semaloopApiKey":[]}],"servers":[{"url":"https://mcp.semaloop.com"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"}}}},"responses":{"200":{"description":"JSON-RPC response, or an SSE stream of them","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}},"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Request rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"securitySchemes":{"semaloopApiKey":{"type":"http","scheme":"bearer","description":"A Semaloop API key, created in https://app.semaloop.com under Settings → API keys. A key is scoped to a single app and cannot read or act on any other app in the organisation; it carries `semaloop:write` and `builds:write` for that app alone."},"semaloopMcpOAuth":{"type":"oauth2","description":"OAuth 2.1 with PKCE against the Semaloop MCP server, which supports dynamic client registration (RFC 7591) and publishes RFC 9728 protected-resource metadata at https://mcp.semaloop.com/.well-known/oauth-protected-resource. Granted scopes follow the user's organisation role: a read-only member's session holds semaloop:read only.","flows":{"authorizationCode":{"authorizationUrl":"https://mcp.semaloop.com/authorize","tokenUrl":"https://mcp.semaloop.com/token","refreshUrl":"https://mcp.semaloop.com/token","scopes":{"semaloop:read":"Read apps, builds, tests, test runs, issues, signals, secret definitions, and run artefacts (video, network logs, system logs). Held by every member of an organisation.","semaloop:write":"Create and update tests, custom tools, and secrets, and trigger or cancel runs. Held only by members whose organisation role is write or admin."}}}}},"schemas":{"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","const":false},"errors":{"type":"array","items":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}},"CreateUploadResponse":{"type":"object","required":["success","result"],"properties":{"success":{"type":"boolean"},"result":{"type":"object","required":["uploadId","uploadUrl"],"properties":{"uploadId":{"type":"string","description":"Server-generated upload identifier."},"uploadUrl":{"type":"string","format":"uri","description":"Pre-signed PUT URL, expires in one hour."}}}}},"FinalizeUploadRequest":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Upload id returned by POST /api/v1/uploads."},"gitRef":{"type":"object","description":"Optional git context for a CI build. All three fields are required together.","required":["repo","commitSha","ref"],"properties":{"repo":{"type":"string","examples":["owner/name"]},"commitSha":{"type":"string"},"ref":{"type":"string","examples":["refs/heads/main"]}}},"allowDuplicateVersion":{"type":"boolean","default":false,"description":"Accept an upload whose bundle id and version already exist, recording it as a distinct build."}}},"FinalizeUploadResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"},"appId":{"type":"string"},"bundleId":{"type":"string"},"versionLabel":{"type":"string"},"versionName":{"type":"string"},"message":{"type":"string","description":"Present when success is false."}}},"DemoSlot":{"type":"object","required":["start","end","startIso","endIso","local"],"properties":{"start":{"type":"integer","description":"UTC milliseconds."},"end":{"type":"integer","description":"UTC milliseconds."},"startIso":{"type":"string","format":"date-time"},"endIso":{"type":"string","format":"date-time"},"local":{"type":"string","description":"Start time in the requested time zone.","examples":["Tue 6 Oct, 14:00 GMT+1"]}}},"DemoAvailability":{"type":"object","required":["durationMillis","timezone","slots"],"properties":{"durationMillis":{"type":"integer"},"timezone":{"type":"string"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/DemoSlot"}},"book":{"type":"object","description":"How to book one of these slots."}}},"DemoBookingRequest":{"type":"object","required":["startTime","email","company"],"properties":{"startTime":{"oneOf":[{"type":"string","format":"date-time"},{"type":"integer","description":"UTC milliseconds."}],"description":"A slot's `startIso` or `start`."},"email":{"type":"string","format":"email","description":"The attendee's work email; the invite goes here."},"company":{"type":"string","maxLength":100},"name":{"type":"string","maxLength":100,"description":"Full name. Defaults to the part of the email before the @."},"timezone":{"type":"string","default":"Europe/London","description":"IANA time zone the invite is written in."},"surface":{"type":"string","enum":["agent"],"description":"Send `agent` when booking on someone's behalf."}}},"DemoBooking":{"type":"object","required":["start","end","startIso","endIso"],"properties":{"start":{"type":"integer"},"end":{"type":"integer"},"startIso":{"type":"string","format":"date-time"},"endIso":{"type":"string","format":"date-time"}}},"DemoBookingError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"alternatives":{"type":"array","description":"Present on 409: the nearest open slots.","items":{"$ref":"#/components/schemas/DemoSlot"}}}},"PricingQuote":{"type":"object","required":["tests","runsPerMonth","agents","platforms","billing","executions","currency"],"properties":{"tests":{"type":"integer","description":"Per platform."},"runsPerMonth":{"type":"integer","description":"Per platform."},"agents":{"type":"integer","description":"Per platform."},"platforms":{"type":"string","enum":["both","ios","android"]},"billing":{"type":"string","enum":["annual","monthly"]},"executions":{"type":"integer","description":"Across every platform tested."},"currency":{"type":"string","const":"USD"},"platformFee":{"type":["number","null"],"description":"Null when the bill is bespoke."},"agentFee":{"type":["number","null"]},"total":{"type":["number","null"]},"annualSaving":{"type":["number","null"],"description":"What a year of annual billing saves."},"custom":{"type":["string","null"],"enum":["executions",null],"description":"Set when the inputs fall outside the published grid and need a bespoke quote."},"suiteMinutes":{"type":"integer","description":"Approximate wall-clock time for one full run."},"speedBand":{"type":"string"},"pricingPage":{"type":"string","format":"uri"}}},"JsonRpcRequest":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["string","integer"]},"method":{"type":"string","examples":["initialize","tools/list","tools/call"]},"params":{"type":"object"}}},"JsonRpcResponse":{"type":"object","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"type":["string","integer"]},"result":{"type":"object"},"error":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}}}