{"openapi":"3.1.0","info":{"title":"Sparetext API","version":"1.0.0","description":"Send and read SMS through your own phones (Android, iPhone through a Mac relay or Shortcuts). Texts leave from your own number. Phone numbers are E.164, for example +15551234567.\n\n- One SMS holds 160 GSM-7 characters; a longer text goes as a concatenated SMS of 153-character segments. An emoji or any character outside GSM-7 makes the whole text UCS-2: 70 characters, or 67 per segment. Carriers bill each segment.\n- This is person-to-person texting. Carriers filter bulk and marketing (A2P) traffic on personal numbers; in the US that traffic needs a registered 10DLC or toll-free number. Honor STOP replies.\n- Status goes queued, dispatched, then sent or failed. Only Android phones get carrier delivery reports (delivered). An iPhone in tap-to-send mode sends only when its owner taps Send.\n- Webhooks are signed: X-Sparetext-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the webhook secret>."},"servers":[{"url":"https://staging.sparetext.dev/api/v1"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"An API key from the API keys page (spt_live_...)."},"bearer":{"type":"http","scheme":"bearer","description":"The same API key, or an OAuth access token, as Authorization: Bearer."}},"schemas":{"Message":{"type":"object","properties":{"id":{"type":"string","example":"msg_20261027_k3j9x2ab"},"direction":{"type":"string","enum":["outbound","inbound"]},"contact":{"type":"string","description":"The other party's phone number","example":"+15551234567"},"text":{"type":"string"},"status":{"type":"string","enum":["scheduled","queued","dispatched","sent","delivered","failed","received","canceled"]},"deviceId":{"type":"string"},"deviceName":{"type":"string"},"error":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"sentAt":{"type":"string","format":"date-time"},"deliveredAt":{"type":"string","format":"date-time"},"receivedAt":{"type":"string","format":"date-time"}}},"Device":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"platform":{"type":"string","enum":["android","ios","mac"]},"mode":{"type":"string","enum":["native","relay","shortcut","assisted"]},"online":{"type":"boolean"},"enabled":{"type":"boolean"},"isDefault":{"type":"boolean"},"lastSeenAt":{"type":"string","format":"date-time"},"queued":{"type":"integer"}}},"Event":{"type":"object","description":"The webhook body. Verify the signature over these exact bytes.","properties":{"id":{"type":"string","example":"evt_x1…"},"type":{"type":"string","enum":["message.received","message.sent","message.delivered","message.failed","device.paired"]},"createdAt":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}}},"security":[{"apiKey":[]},{"bearer":[]}],"webhooks":{"message.received":{"post":{"summary":"Sparetext calls your URL: message.received","description":"Check X-Sparetext-Signature before you trust the body, and reject a t more than 5 minutes old.","parameters":[{"name":"x-sparetext-signature","in":"header","required":true,"schema":{"type":"string","example":"t=1793125632,v1=5f1c…"}},{"name":"x-sparetext-event","in":"header","required":true,"schema":{"type":"string","example":"message.received"}},{"name":"x-sparetext-delivery","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"responses":{"200":{"description":"Any 2xx means you got it."}}}},"message.sent":{"post":{"summary":"Sparetext calls your URL: message.sent","description":"Check X-Sparetext-Signature before you trust the body, and reject a t more than 5 minutes old.","parameters":[{"name":"x-sparetext-signature","in":"header","required":true,"schema":{"type":"string","example":"t=1793125632,v1=5f1c…"}},{"name":"x-sparetext-event","in":"header","required":true,"schema":{"type":"string","example":"message.sent"}},{"name":"x-sparetext-delivery","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"responses":{"200":{"description":"Any 2xx means you got it."}}}},"message.delivered":{"post":{"summary":"Sparetext calls your URL: message.delivered","description":"Check X-Sparetext-Signature before you trust the body, and reject a t more than 5 minutes old.","parameters":[{"name":"x-sparetext-signature","in":"header","required":true,"schema":{"type":"string","example":"t=1793125632,v1=5f1c…"}},{"name":"x-sparetext-event","in":"header","required":true,"schema":{"type":"string","example":"message.delivered"}},{"name":"x-sparetext-delivery","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"responses":{"200":{"description":"Any 2xx means you got it."}}}},"message.failed":{"post":{"summary":"Sparetext calls your URL: message.failed","description":"Check X-Sparetext-Signature before you trust the body, and reject a t more than 5 minutes old.","parameters":[{"name":"x-sparetext-signature","in":"header","required":true,"schema":{"type":"string","example":"t=1793125632,v1=5f1c…"}},{"name":"x-sparetext-event","in":"header","required":true,"schema":{"type":"string","example":"message.failed"}},{"name":"x-sparetext-delivery","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Event"}}}},"responses":{"200":{"description":"Any 2xx means you got it."}}}}},"paths":{"/messages":{"post":{"operationId":"sendText","summary":"Send a text message from one of your phones","description":"Queues the text and wakes the phone. Check its status afterwards with getText. The text reaches a real person from your number: confirm the recipient and wording first. Up to 1,600 characters: at most 11 GSM-7 segments, or 24 UCS-2 segments.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to","text"],"properties":{"to":{"description":"One phone number, or a list of up to 100","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"text":{"type":"string","maxLength":1600},"deviceId":{"type":"string","description":"Optional. Which phone sends it; defaults to the default phone."},"scheduledAt":{"type":"string","format":"date-time","description":"Optional. Send later, up to 30 days ahead."}}}}}},"responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}},"get":{"operationId":"listTexts","summary":"List sent and received texts, newest first","parameters":[{"name":"direction","in":"query","schema":{"type":"string","enum":["outbound","inbound"]}},{"name":"contact","in":"query","description":"Only texts with this phone number","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","description":"Search the text","schema":{"type":"string"}},{"name":"since","in":"query","description":"Only texts created after this time","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"before","in":"query","description":"Cursor from nextCursor","schema":{"type":"string"}}],"responses":{"200":{"description":"Texts","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"nextCursor":{"type":["string","null"]}}}}}}}}},"/messages/{id}":{"get":{"operationId":"getText","summary":"Get one text and its delivery status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The text","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}},"delete":{"operationId":"cancelText","summary":"Cancel a text that is still scheduled or waiting for the phone","description":"Once the phone has taken it (dispatched or sent), it can't be called back.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Canceled"},"404":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}}},"/messages/{id}/retry":{"post":{"operationId":"retryText","summary":"Send a failed text again","description":"Queues it on the phone that tried it, or on deviceId when given (for example another phone after a Mac relay failed).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","description":"Send it from this phone instead."}}}}}},"responses":{"200":{"description":"Queued again","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}}}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}}}},"/conversations/{contact}/read":{"post":{"operationId":"markRead","summary":"Mark the texts from one number as read","parameters":[{"name":"contact","in":"path","required":true,"description":"E.164 number, URL-encoded (+ is %2B)","schema":{"type":"string"}}],"responses":{"204":{"description":"Marked read"}}}},"/conversations":{"get":{"operationId":"listConversations","summary":"List people you have texted with, with unread counts","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Conversations","content":{"application/json":{"schema":{"type":"object","properties":{"conversations":{"type":"array","items":{"type":"object","properties":{"contact":{"type":"string"},"unread":{"type":"integer"},"count":{"type":"integer"},"lastMessage":{"type":"object","properties":{"text":{"type":"string"},"direction":{"type":"string"},"createdAt":{"type":"string"}}}}}}}}}}}}}},"/devices":{"get":{"operationId":"listDevices","summary":"List your connected phones and whether they are online","responses":{"200":{"description":"Phones","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/Device"}}}}}}}}}},"/stats":{"get":{"operationId":"getStats","summary":"Totals of sent, delivered, failed and received texts","parameters":[{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":90,"default":30}}],"responses":{"200":{"description":"Stats","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}