{"openapi":"3.1.0","info":{"title":"TOTG Platform API","version":"0.1.0","description":"Therapy On The Go Platform. Read-only /v1 behind scoped API keys (Bearer totg_live_... or totg_test_...), signed inbound webhooks and signed outbound webhooks (TOTG-Signature: t=<unix>,v1=<HMAC-SHA256 of \"t.body\">). Aggregates only: booking counts below 10 are withheld. Data is stored and processed in South Africa."},"servers":[{"url":"http://localhost:3131","description":"Local"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key from /developers: totg_live_... or totg_test_... (sandbox)."}},"schemas":{"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"version":{"type":"string"}},"required":["status","version"]},"Ready":{"type":"object","properties":{"status":{"type":"string","enum":["ready"]}},"required":["status"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"request_id":{"type":["string","null"]}},"required":["code","message","request_id"]}},"required":["error"]},"Catalogue":{"type":"object","properties":{"price_list":{"type":["object","null"],"properties":{"code":{"type":"string"},"status":{"type":"string"},"vat_rate":{"type":"number"}},"required":["code","status","vat_rate"]},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CatalogueItem"}}},"required":["price_list","total","limit","offset","data"]},"CatalogueItem":{"type":"object","properties":{"code":{"type":"string","example":"TOTG-001"},"name":{"type":"string"},"company":{"type":"string"},"category":{"type":"string"},"unit":{"type":"string"},"price_ex_vat_cents":{"type":["integer","null"]}},"required":["code","name","company","category","unit","price_ex_vat_cents"]},"LifecyclePlan":{"type":"object","properties":{"code":{"type":"string"},"company_code":{"type":"string"},"name":{"type":"string"},"unit":{"type":"string"},"price_ex_vat_cents":{"type":"integer"},"term_months":{"type":["integer","null"]},"cycle_months":{"type":["integer","null"]},"is_sample":{"type":"boolean"}},"required":["code","company_code","name","unit","price_ex_vat_cents","term_months","cycle_months","is_sample"]},"EventsSummary":{"type":"object","properties":{"min_cell":{"type":"number","enum":[10]},"data":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string","example":"2026-10"},"events":{"type":"integer"},"bookings":{"type":["integer","null"],"description":"null when below 10 (or to protect a hidden cell)"}},"required":["month","events","bookings"]}}},"required":["min_cell","data"]},"InvoicesSummary":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"status":{"type":"string","enum":["approved","sent_to_hoes"]},"count":{"type":"integer"},"ex_vat_cents":{"type":"integer"},"incl_vat_cents":{"type":"integer"}},"required":["month","status","count","ex_vat_cents","incl_vat_cents"]}}},"required":["data"]}},"parameters":{}},"paths":{"/health":{"get":{"tags":["ops"],"summary":"Liveness","responses":{"200":{"description":"The process is up","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/ready":{"get":{"tags":["ops"],"summary":"Readiness (database reachable)","responses":{"200":{"description":"Ready to take traffic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ready"}}}},"503":{"description":"Not ready (database unreachable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/hoes":{"post":{"tags":["webhooks"],"summary":"Receive HOES OS events (X-HOES-Signature)","responses":{"200":{"description":"Duplicate delivery; already stored","content":{"application/json":{"schema":{"type":"object","properties":{"duplicate":{"type":"boolean","enum":[true]}},"required":["duplicate"]}}}},"202":{"description":"Verified and stored; processed asynchronously","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean","enum":[true]}},"required":["received"]}}}},"400":{"description":"Unreadable body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Signature missing, stale or wrong","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Body too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Receiver not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/nutrimove":{"post":{"tags":["webhooks"],"summary":"Receive NutriMove OS events (NutriMove-Signature)","responses":{"200":{"description":"Duplicate delivery; already stored","content":{"application/json":{"schema":{"type":"object","properties":{"duplicate":{"type":"boolean","enum":[true]}},"required":["duplicate"]}}}},"202":{"description":"Verified and stored; processed asynchronously","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean","enum":[true]}},"required":["received"]}}}},"400":{"description":"Unreadable body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Signature missing, stale or wrong","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Body too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Receiver not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/payfast":{"post":{"tags":["webhooks"],"summary":"Receive PayFast ITN (payment and subscription notifications)","description":"Form-encoded. Checks: MD5 signature with the passphrase over every posted field in order, source inside the PayFast ranges, amount against the expected amount (database), server confirmation in sandbox and live only. Applied once per (pf_payment_id, payment_status); only identifiers, status, amounts and the SHA-256 of the body are kept.","responses":{"200":{"description":"Verified; applied, duplicate (replay) or recorded for finance review","content":{"application/json":{"schema":{"type":"object","properties":{"result":{"type":"string"}},"required":["result"]}}}},"400":{"description":"Signature missing or wrong","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Not a PayFast source address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Body too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Receiver not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/catalogue":{"get":{"tags":["v1"],"summary":"Rate card products and prices ex VAT (scope read:catalogue)","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":500},"required":false,"name":"limit","in":"query"},{"schema":{"type":["integer","null"],"minimum":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"A page of the catalogue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Catalogue"}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit for this key exceeded (see Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/lifecycle/plans":{"get":{"tags":["v1"],"summary":"Lifecycle plans (sample prices until confirmed on quote; scope read:lifecycle)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Active plans","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/LifecyclePlan"}}},"required":["data"]}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit for this key exceeded (see Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/events/summary":{"get":{"tags":["v1"],"summary":"Your org's events and bookings per month, bookings suppressed below 10 (scope read:events)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Counts per month","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsSummary"}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit for this key exceeded (see Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/invoices/summary":{"get":{"tags":["v1"],"summary":"Your org's approved invoices per month (scope read:invoices)","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Totals per month and status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicesSummary"}}}},"401":{"description":"Missing, unknown, revoked or expired key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope for this resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit for this key exceeded (see Retry-After)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}