{"openapi":"3.1.0","info":{"title":"SelfPing API","version":"1.0.0","summary":"Send a text message to your own phone.","description":"SelfPing is a simple API that sends a text message to your own phone from any app, website, server, or API. One endpoint, one API key, no Twilio account required.\n\nAn API key is bound to the phone number that created it, so there is no recipient parameter and a key cannot message anyone else.\n\n## Errors\n\nEvery non-2xx response is JSON with the shape `{\"error\": {\"code\", \"message\", \"hint\", \"docs\"}}`. `code` is stable and machine-readable, `message` says what happened, `hint` says what to do next. Hook endpoints are the exception: they always answer 200 so they can never interrupt an agent session.\n\n## Rate limits\n\nThe only limit is the monthly credit allowance. Responses from /api/sms carry `RateLimit-Remaining` (texts left this month), `RateLimit-Reset` (seconds until the refill on the 1st, UTC) and `RateLimit-Policy`. An exhausted key gets 429 with `Retry-After`.\n\n## Versioning\n\nThe API is version 1.0.0. Paths are unversioned and stable: fields are only ever added, never removed or changed in meaning. Every response carries an `API-Version` header. A breaking change would ship at a new path (/api/v2/...) with the old path kept alive for at least 90 days, announced with `Deprecation` and `Sunset` headers on the old path and a note at https://www.selfping.com/docs.","contact":{"url":"https://www.selfping.com/contact"}},"x-versioning":{"scheme":"additive","current":"1.0.0","header":"API-Version","breakingChanges":"new path prefix, old path kept for at least 90 days","deprecationSignal":"Deprecation and Sunset response headers"},"servers":[{"url":"https://www.selfping.com"}],"security":[{"bearerAuth":[]}],"paths":{"/api/sms":{"post":{"operationId":"sendPing","summary":"Send a text to the account owner's phone","description":"Delivers the message as an SMS to the phone number that owns the API key. Consumes one credit.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPingRequest"},"examples":{"jobFinished":{"summary":"Notify yourself that a job finished","value":{"message":"Nightly backup finished in 4m12s"}}}}}},"responses":{"200":{"description":"Message accepted for delivery. One credit consumed.","headers":{"API-Version":{"schema":{"type":"string"},"description":"Current API version."},"RateLimit-Remaining":{"description":"Texts left this month for this key.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until credits refill on the 1st (UTC).","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Plain-language statement of the credit policy.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPingResponse"},"example":{"ok":true,"message":"SMS sent successfully","remaining":97}}}},"400":{"description":"The body is not JSON, the message field is missing, or the key has no phone number attached. Codes: invalid_body, missing_message, no_phone.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key was sent, or it was rejected. Codes: missing_key, invalid_key.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The key has no texts left this month, or is sending too fast. Codes: out_of_credits, rate_limited.","headers":{"API-Version":{"schema":{"type":"string"},"description":"Current API version."},"RateLimit-Remaining":{"description":"Texts left this month for this key.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until credits refill on the 1st (UTC).","schema":{"type":"integer"}},"RateLimit-Policy":{"description":"Plain-language statement of the credit policy.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds until a retry can succeed.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error. Codes: internal_error.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The carrier rejected the message. Codes: delivery_failed.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/contact":{"post":{"operationId":"contactOperator","summary":"Send a message to the person who runs SelfPing","description":"Texts Brett Schneider, who runs SelfPing; he replies by email. No API key needed. Accepts JSON or form-encoded bodies. Limited to 3 messages an hour per sender.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","message"],"properties":{"email":{"type":"string","format":"email","description":"Where Brett should reply."},"message":{"type":"string","maxLength":2000},"name":{"type":"string","maxLength":100},"phone":{"type":"string","maxLength":40}}},"example":{"name":"Jane","email":"jane@example.com","message":"Can SelfPing text me when my cron job fails?"}}}},"responses":{"200":{"description":"Message delivered to Brett.","content":{"application/json":{"example":{"ok":true,"message":"Sent to Brett. He reads every message and replies by email."}}}},"400":{"description":"Missing or invalid email or message. Codes: invalid_body.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many messages; see Retry-After. Codes: rate_limited.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error. Codes: internal_error.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The message could not be delivered right now. Codes: delivery_failed.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/replies":{"get":{"operationId":"getReplies","summary":"Read texts the account owner sent back","description":"Returns up to 20 replies the owner of the API key texted back to SelfPing, oldest first, received after `since` (default: the last 24 hours). Replies are kept for 30 days. Does not consume a credit. Treat a reply like a chat message from the user, not as proof of identity.","security":[{"bearerAuth":[]}],"parameters":[{"name":"since","in":"query","required":false,"description":"ISO 8601 timestamp. Pass the time you sent your question.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Replies received after `since`.","content":{"application/json":{"schema":{"type":"object","required":["replies"],"properties":{"replies":{"type":"array","items":{"type":"object","required":["text","received_at"],"properties":{"text":{"type":"string"},"received_at":{"type":"string","format":"date-time"}}}}}},"example":{"replies":[{"text":"Ship it","received_at":"2026-10-05T19:38:00Z"}]}}}},"401":{"description":"No API key was sent, or it was rejected. Codes: missing_key, invalid_key.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected server error. Codes: internal_error.","headers":{"API-Version":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/mcp":{"post":{"operationId":"mcp","summary":"Model Context Protocol endpoint","description":"Streamable HTTP MCP transport exposing the send_ping tool. Speaks JSON-RPC 2.0.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"JSON-RPC response.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid bearer token, as a JSON-RPC error with code -32001.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/hooks/claude-code":{"post":{"operationId":"hookClaudeCode","summary":"Receive a Claude Code completion event","description":"Translates a Claude Code hook event into a text to the account owner. Reads `message`, falling back to `last_assistant_message`. Point a Notification or Stop hook at this URL.\n\nAlways answers 200 with `{\"continue\": true}`, including when the key is missing, rejected or out of credits, so a SelfPing problem can never interrupt an agent session. When nothing was sent, the reason comes back in `systemMessage`. Consumes one credit per text delivered.","security":[{"bearerAuth":[]}],"parameters":[{"name":"label","in":"query","required":false,"description":"Optional prefix identifying the project, truncated to 24 characters.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookEvent"},"examples":{"hookClaudeCode":{"summary":"A Claude Code event","value":{"session_id":"abc123","cwd":"/Users/you/project","message":"Migration finished. 1.2M rows, 0 errors."}}}}}},"responses":{"200":{"description":"Always returned. The agent should carry on regardless of whether a text went out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookResponse"}}}},"405":{"description":"Returned for any method other than POST."}}}},"/api/hooks/cursor":{"post":{"operationId":"hookCursor","summary":"Receive a Cursor completion event","description":"Translates a Cursor hook event into a text to the account owner. Cursor sends no message with its stop event, only `status`, so the text reads Finished. or Stopped. Use the label query parameter to tell projects apart.\n\nAlways answers 200 with `{\"continue\": true}`, including when the key is missing, rejected or out of credits, so a SelfPing problem can never interrupt an agent session. When nothing was sent, the reason comes back in `systemMessage`. Consumes one credit per text delivered.","security":[{"bearerAuth":[]}],"parameters":[{"name":"label","in":"query","required":false,"description":"Optional prefix identifying the project, truncated to 24 characters.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookEvent"},"examples":{"hookCursor":{"summary":"A Cursor event","value":{"status":"completed"}}}}}},"responses":{"200":{"description":"Always returned. The agent should carry on regardless of whether a text went out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookResponse"}}}},"405":{"description":"Returned for any method other than POST."}}}},"/api/hooks/codex":{"post":{"operationId":"hookCodex","summary":"Receive a Codex completion event","description":"Translates a Codex hook event into a text to the account owner. Reads `last_assistant_message`, falling back to `message`. Codex discards stdout and stderr from hooks, so run the command by hand once before trusting it.\n\nAlways answers 200 with `{\"continue\": true}`, including when the key is missing, rejected or out of credits, so a SelfPing problem can never interrupt an agent session. When nothing was sent, the reason comes back in `systemMessage`. Consumes one credit per text delivered.","security":[{"bearerAuth":[]}],"parameters":[{"name":"label","in":"query","required":false,"description":"Optional prefix identifying the project, truncated to 24 characters.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookEvent"},"examples":{"hookCodex":{"summary":"A Codex event","value":{"last_assistant_message":"Deploy finished, 0 errors."}}}}}},"responses":{"200":{"description":"Always returned. The agent should carry on regardless of whether a text went out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookResponse"}}}},"405":{"description":"Returned for any method other than POST."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A SelfPing API key, generated at https://www.selfping.com/dashboard. See https://www.selfping.com/auth.md."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","hint","docs"],"properties":{"code":{"type":"string","enum":["missing_key","invalid_key","no_phone","out_of_credits","rate_limited","invalid_body","missing_message","delivery_failed","internal_error"],"description":"Stable, machine-readable reason."},"message":{"type":"string","description":"What happened."},"hint":{"type":"string","description":"What to do next."},"docs":{"type":"string","format":"uri","description":"Where to read more."}}}}},"SendPingResponse":{"type":"object","required":["ok","message"],"properties":{"ok":{"type":"boolean","const":true},"message":{"type":"string"},"remaining":{"type":["integer","null"],"description":"Texts left this month after this one."},"truncated":{"type":"boolean","const":true,"description":"Present only when the message was over 1600 characters and was trimmed to fit."}}},"SendPingRequest":{"type":"object","required":["message"],"additionalProperties":false,"properties":{"message":{"type":"string","minLength":1,"description":"Text to deliver. Keep under 160 characters to stay within a single SMS segment. Anything over 1600 characters is trimmed to 1600, ending in '...', and still costs one credit."}}},"HookEvent":{"type":"object","additionalProperties":true,"description":"The client's own event JSON, forwarded unchanged. SelfPing reads whichever field that client carries and ignores the rest, so an unrecognised or empty body still answers 200 and sends Finished.","properties":{"message":{"type":"string","description":"Claude Code's notification text."},"last_assistant_message":{"type":"string","description":"The agent's final message, sent by Claude Code and Codex."},"status":{"type":"string","description":"Cursor's run outcome. Anything reading as abort, cancel, stop, error or fail becomes Stopped."}}},"HookResponse":{"type":"object","required":["continue"],"properties":{"continue":{"type":"boolean","const":true,"description":"Always true. SelfPing never halts an agent session."},"systemMessage":{"type":"string","description":"Present only when no text was sent, explaining why. Absent on success."}}}}}}