{"openapi":"3.1.0","info":{"title":"Vylo OTP API","version":"1.0.0","description":"WhatsApp OTP delivery as a service.\n\n**Authentication** — every request needs your API key in the `Authorization` header:\n```\nAuthorization: Bearer otpsk_...\n```\nThe key is a **server-side secret**: call this API from your backend only — never from a browser or mobile app.\n\n**Flow** — `POST /otp/send` generates a one-time code and delivers it to the phone on WhatsApp; your app then calls `POST /otp/verify` with the code the user typed. Codes expire after `ttlSeconds`; re-sending invalidates the previous code; a verified code cannot be replayed.\n\n**Billing** — each successfully delivered message is billed at your per-message price. `GET /usage` shows your own counts, cost, and balance."},"servers":[{"url":"https://otp.vylo-tech.com"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Your API key (starts with `otpsk_`). Keep it server-side."}},"schemas":{"Problem":{"type":"object","description":"RFC 7807 problem+json. The OTP-specific error code is in `cause.code`.","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"code":{"type":"string","description":"Generic HTTP-level code (e.g. `unprocessable`, `rate_limited`)."},"cause":{"type":"object","description":"Carries the specific OTP error code.","properties":{"code":{"type":"string","example":"otp_service.invalid_code"}}}}}}},"security":[{"apiKey":[]}],"paths":{"/":{"get":{"security":[],"summary":"Landing page (HTML)","responses":{"200":{"description":"Human-readable overview of the service."}}}},"/postman.json":{"get":{"security":[],"summary":"Download a Postman collection","responses":{"200":{"description":"Postman Collection v2.1 (send / verify / usage)."}}}},"/register":{"post":{"security":[],"summary":"Self-service registration","description":"Creates an account and returns an API key ONCE. Sending is disabled until the operator connects a WhatsApp sender.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","example":"Acme Inc."},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"company":{"type":"string"}}}}}},"responses":{"201":{"description":"Account created; API key revealed once.","content":{"application/json":{"schema":{"type":"object","properties":{"apiKey":{"type":"string"},"keyPrefix":{"type":"string"},"name":{"type":"string"}}}}}},"409":{"description":"`otp_service.name_taken` — that account name is already in use.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many sign-ups from this IP.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/me":{"get":{"summary":"Your account (identity + config)","responses":{"200":{"description":"Account identity.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"keyPrefix":{"type":"string"},"configured":{"type":"boolean","description":"True once a WhatsApp sender is connected (sends work)."},"pricePerMessage":{"type":"string"}}}}}},"401":{"description":"`otp_service.invalid_key`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`otp_service.client_disabled`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/otp/send":{"post":{"summary":"Send a one-time code","description":"Generates a fresh code and delivers it to the phone over WhatsApp.\nRe-sending to the same phone invalidates the previous code (only the newest is valid).\n\n**Rate limits**: 45s cooldown per phone · 6 sends/hour per phone · 120 sends/minute per key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","description":"Destination phone, international digits. `+`, spaces and dashes are stripped.","example":"9647501234567"}}},"example":{"phone":"9647501234567"}}}},"responses":{"200":{"description":"Code generated and delivered.","content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean","const":true},"ttlSeconds":{"type":"integer","description":"Seconds until the code expires.","example":300}}}}}},"401":{"description":"`otp_service.invalid_key` — missing or unknown API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`otp_service.client_disabled` — your account is deactivated.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`otp_service.not_configured` — no WhatsApp sender is bound to your account yet. `otp_service.delivery_failed` — the WhatsApp gateway could not deliver (sender offline / number unreachable).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"`otp_service.cooldown` — a code was sent to this phone under 45 seconds ago. Otherwise: rate limit exceeded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/otp/verify":{"post":{"summary":"Verify a code","description":"Checks the code the user typed against the newest unexpired code for that phone.\nOn success the code is consumed — it cannot be verified twice.\nWrong guesses are counted; after 5 the code is locked (send a new one).\n\n**Rate limits**: 15 verifies/15min per phone · 240 verifies/minute per key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone","code"],"properties":{"phone":{"type":"string","example":"9647501234567"},"code":{"type":"string","description":"4-8 digits, as delivered.","example":"482913"}}},"example":{"phone":"9647501234567","code":"482913"}}}},"responses":{"200":{"description":"Code is correct (and now consumed).","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","const":true}}}}}},"401":{"description":"`otp_service.invalid_key`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`otp_service.client_disabled`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`otp_service.invalid_code` — wrong code (or none pending). `otp_service.code_expired` — the code timed out; send a new one. `otp_service.too_many_attempts` — 5 wrong guesses; send a new code.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/usage":{"get":{"summary":"Your usage & balance","description":"Self-service metering: per-day send counts inside the window (default: last 30 days), window cost, and your all-time payments + balance.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD (UTC), inclusive."},{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD (UTC), inclusive."}],"responses":{"200":{"description":"Usage summary.","content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"totalSent":{"type":"integer"},"totalFailed":{"type":"integer"},"cost":{"type":"string","description":"USD cost of billable sends in the window.","example":"1.2300"},"paymentsTotal":{"type":"string","description":"USD payments recorded, all time."},"balance":{"type":"string","description":"USD owed (all-time cost − payments); negative = credit."},"pricePerMessage":{"type":"string","example":"0.0100"},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"sent":{"type":"integer"},"failed":{"type":"integer"}}}}}}}}},"401":{"description":"`otp_service.invalid_key`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`otp_service.client_disabled`","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}