{"openapi":"3.1.0","info":{"title":"Top End Solar backend","version":"0.1.0"},"servers":[{"url":"http://127.0.0.1:8787"}],"paths":{"/api/health":{"get":{"summary":"Liveness and database check","responses":{"200":{"description":"Up"},"503":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/quotes":{"get":{"summary":"List quotes (newest first by default)","security":[{"bearer":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"sort","in":"query","schema":{"type":"string","default":"-created_at","description":"created_at, name, email or source; prefix - for descending"}},{"name":"source","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Substring search over name, email, phone, address, message"}],"responses":{"200":{"description":"meta = {total, limit, offset}","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}},"meta":{"type":"object"}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a quote. urlencoded = public site form (303 redirect); JSON = token + optional Idempotency-Key","security":[{},{"bearer":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteInput"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/QuoteInput"}}}},"responses":{"201":{"description":"Created (JSON)","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"const":true},"data":{"$ref":"#/components/schemas/Quote"},"meta":{"type":"object"}}}}}},"303":{"description":"Form post: redirect to <site>/thank-you/ or <site>/quote-error/"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/quotes/{id}":{"get":{"summary":"Get one quote","security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["ok","data","meta"],"properties":{"ok":{"const":true},"data":{"$ref":"#/components/schemas/Quote"},"meta":{"type":"object"}}}}}},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a quote (DESTRUCTIVE; ?dry_run=true previews)","security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"dry_run","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Deleted, or previewed when dry_run"},"400":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","required":["ok","error"],"properties":{"ok":{"const":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}}}}},"QuoteInput":{"type":"object","required":["name","phone","email"],"properties":{"source":{"type":"string","maxLength":40,"description":"hero-quote | contact | ...; message is required when contact"},"name":{"type":"string","maxLength":120},"phone":{"type":"string","maxLength":40},"email":{"type":"string","maxLength":254},"address":{"type":["string","null"],"maxLength":300},"message":{"type":["string","null"],"maxLength":5000}}},"Quote":{"type":"object","required":["id","created_at","source","name","phone","email","address","message"],"properties":{"id":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"source":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"address":{"type":["string","null"]},"message":{"type":["string","null"]}}}}}}