{"openapi":"3.1.0","info":{"title":"Cleat API","version":"1.0.0","summary":"Read the texts and transcribed calls that arrive on your US mobile lines.","description":"Cleat rents real US mobile lines that receive texts, and transcribes incoming calls into the same inbox. This API reads them.\n\nIt is read-only. A line cannot send a text or place a call, so there is nothing here that writes. Messages arrive by webhook as they land; this API is how you list what a line has already received, and how you catch up after your endpoint was unreachable.","contact":{"name":"Cleat","email":"contact@cleat.so","url":"https://cleat.so/for/developers"}},"servers":[{"url":"https://cleat.so"}],"security":[{"apiKey":[]}],"tags":[{"name":"Lines","description":"The numbers in a workspace, and the messages they received."}],"paths":{"/api/v1/lines":{"get":{"tags":["Lines"],"summary":"List the lines in this key's workspace","description":"Newest first. Lines that have been fully released are still listed, so an id in your own records still resolves to something.","operationId":"listLines","responses":{"200":{"description":"The workspace's lines.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Line"}}}}}}},"401":{"description":"The key is missing, malformed, revoked, or its workspace owner is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests in a minute on this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/lines/{lineId}/messages":{"get":{"tags":["Lines"],"summary":"List the messages a line received","description":"Pass `after` to poll forwards: results come back oldest first, so you can walk them in order and keep the last `receivedAt` as your cursor. That is also how you catch up on anything a webhook could not deliver.\n\nPass `before` to page backwards through history; results come back newest first. Omit both and you get the newest messages.","operationId":"listMessages","parameters":[{"name":"lineId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"after","in":"query","required":false,"description":"Only messages received strictly after this moment. Switches the order to oldest first.","schema":{"type":"string","format":"date-time"}},{"name":"before","in":"query","required":false,"description":"Only messages received strictly before this moment.","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"The line's messages.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}}}},"400":{"description":"`after` or `before` was not an ISO 8601 timestamp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The key is missing, malformed, revoked, or its workspace owner is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The line is on hold for non-payment. Its texts are kept but cannot be read until it is resubscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The workspace owner hasn't verified their identity yet. The line runs and keeps every text it receives, but none can be read until they do. `code` is `verify_first`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No line with that id in this key's workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 requests in a minute on this key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Line":{"type":"object","required":["id","phone","label","status","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"phone":{"type":"string","example":"13055550100"},"label":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","grace","released"],"description":"active: receiving. grace: unpaid, texts held and not readable. released: the number is gone."},"createdAt":{"type":"string","format":"date-time"}}},"Message":{"type":"object","required":["id","line","from","body","code","receivedAt"],"properties":{"id":{"type":"string","format":"uuid"},"line":{"type":"object","required":["id","phone","label"],"properties":{"id":{"type":"string","format":"uuid"},"phone":{"type":"string","description":"E.164 without the plus, e.g. 13055550100.","example":"13055550100"},"label":{"type":"string","nullable":true}}},"from":{"type":"string","description":"The sender as the network reported it: a number, or an alphanumeric sender id.","example":"22395"},"body":{"type":"string","description":"The full text. For a call, the transcript."},"code":{"type":"string","nullable":true,"description":"The code as extracted, for display. Best effort — always read body when it matters.","example":"704118"},"receivedAt":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"A sentence meant to be shown to a person."},"code":{"type":"string"}}}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key created in workspace settings, sent as `Authorization: Bearer clt_...`. Keys begin with `clt_` and are shown once.\n\nA key is scoped to one workspace and is limited to 120 requests per minute. Keys stop working while the workspace owner's account is disabled."}}},"webhooks":{"message.received":{"post":{"summary":"A text or transcribed call arrived on one of your lines","description":"Sent once, as the message lands. There is no retry: if your endpoint is unreachable or answers anything other than 2xx, the delivery is recorded as failed and not attempted again — catch up with `GET /api/v1/lines/{lineId}/messages?after=...`.\n\nAnswer within 10 seconds. Redirects are not followed, and the endpoint must be an HTTPS URL that resolves to a public address.\n\nVerify `cleat-signature`, which is `t=<unix seconds>,v1=<hex>`, where the hex is HMAC-SHA256 of `<t>.<raw body>` keyed with the endpoint's signing secret. Compare it against the raw body before parsing, and reject a timestamp that is not recent.","parameters":[{"name":"cleat-signature","in":"header","required":true,"schema":{"type":"string"},"example":"t=1789200000,v1=6f1a…"},{"name":"user-agent","in":"header","required":false,"schema":{"type":"string","const":"Cleat-Webhooks/1.0"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","const":"message.received"},"data":{"$ref":"#/components/schemas/Message"}}}}}},"responses":{"200":{"description":"Acknowledged. Any 2xx counts."}}}}},"externalDocs":{"description":"Using a line from code","url":"https://cleat.so/for/developers"}}