{"openapi":"3.1.0","info":{"title":"QualifyBase Public API","version":"1.0.0","description":"REST API for managing leads and appointments in a clinic workspace. Use a `qbk_live_*` API key as a bearer token. Rate limit: 120 req/min per key."},"servers":[{"url":"{appUrl}/api/v1","variables":{"appUrl":{"default":"https://app.qualifybase.com"}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"qbk_live_*"}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"Lead":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"clinic_id":{"type":"string","format":"uuid"},"full_name":{"type":["string","null"]},"phone":{"type":["string","null"]},"email":{"type":["string","null"]},"country":{"type":["string","null"]},"city":{"type":["string","null"]},"language":{"type":["string","null"]},"status":{"type":"string","enum":["visitor","lead"]},"stage":{"type":"string"},"source":{"type":["string","null"]},"score":{"type":["integer","null"]},"temperature":{"type":["string","null"],"enum":[null,"hot","warm","cold"]},"treatment_interest":{"type":["string","null"]},"notes":{"type":["string","null"]},"do_not_call":{"type":"boolean"},"external_id":{"type":["string","null"],"description":"External system ID (e.g. HubSpot contact ID)."},"external_source":{"type":["string","null"],"description":"Namespace for external_id (e.g. \"hubspot\", \"typeform\")."},"external_metadata":{"type":["object","null"],"additionalProperties":true,"description":"Free-form pass-through JSON, up to 4KB."},"last_contacted_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"LeadCreate":{"type":"object","description":"At least one of phone or email is required. If external_source + external_id match an existing lead, the endpoint upserts (returns 200) instead of creating (201).","properties":{"full_name":{"type":"string"},"phone":{"type":"string","description":"E.164 normalized"},"email":{"type":"string","format":"email"},"country":{"type":"string"},"city":{"type":"string"},"language":{"type":"string"},"treatment_interest":{"type":"string"},"notes":{"type":"string"},"source":{"type":"string","enum":["website","instagram","whatsapp","facebook","tiktok","referral","email","paid_ads","other"]},"external_id":{"type":"string","description":"Your system ID for this lead. Paired with external_source for upsert."},"external_source":{"type":"string","description":"Required when external_id is supplied. Namespaces the ID across integrations."},"external_metadata":{"type":"object","additionalProperties":true,"description":"Free-form pass-through JSON, up to 4KB."}}},"LeadPatch":{"type":"object","properties":{"full_name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"country":{"type":"string"},"city":{"type":"string"},"language":{"type":"string"},"treatment_interest":{"type":"string"},"notes":{"type":"string"},"do_not_call":{"type":"boolean"},"do_not_call_reason":{"type":"string"},"external_id":{"type":"string"},"external_source":{"type":"string"},"external_metadata":{"type":"object","additionalProperties":true,"description":"Replaces the existing metadata object wholesale. Capped at 4KB."}}},"Appointment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"clinic_id":{"type":"string","format":"uuid"},"lead_id":{"type":"string","format":"uuid"},"assigned_to":{"type":["string","null"],"format":"uuid"},"status":{"type":"string","enum":["pending_confirmation","confirmed","rescheduled","cancelled","completed","no_show"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"timezone":{"type":["string","null"]},"scheduled_at":{"type":"string","format":"date-time"},"ends_at":{"type":"string","format":"date-time"},"external_provider":{"type":["string","null"],"enum":[null,"internal","google","outlook","caldav","api"]},"external_event_id":{"type":["string","null"]},"meeting_link":{"type":["string","null"]},"notes":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"AppointmentCreate":{"type":"object","required":["lead_id","scheduled_at"],"properties":{"lead_id":{"type":"string","format":"uuid"},"scheduled_at":{"type":"string","format":"date-time"},"ends_at":{"type":"string","format":"date-time"},"title":{"type":"string"},"description":{"type":"string"},"notes":{"type":"string"},"status":{"type":"string","enum":["pending_confirmation","confirmed"]}}},"AppointmentPatch":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time"},"ends_at":{"type":"string","format":"date-time"},"title":{"type":"string"},"description":{"type":"string"},"notes":{"type":"string"},"status":{"type":"string","enum":["pending_confirmation","confirmed","rescheduled","completed","no_show"]}}},"AvailabilityDay":{"type":"object","properties":{"date":{"type":"string","description":"YYYY-MM-DD"},"slots":{"type":"array","items":{"type":"object","properties":{"starts_at":{"type":"string","format":"date-time"},"ends_at":{"type":"string","format":"date-time"}}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded (120 req/min per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/leads":{"get":{"summary":"List leads","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["visitor","lead"]}},{"name":"stage","in":"query","schema":{"type":"string"}},{"name":"phone","in":"query","schema":{"type":"string"}},{"name":"email","in":"query","schema":{"type":"string"}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Fuzzy search on name/phone/email"},{"name":"external_source","in":"query","schema":{"type":"string"},"description":"Filter by originating external system."},{"name":"external_id","in":"query","schema":{"type":"string"},"description":"Exact match on external system ID."},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"List of leads","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"summary":"Create or upsert a lead","description":"Creates a new lead, or — when external_source + external_id match an existing lead for the clinic — updates it in place and returns 200. Supports Stripe-style idempotency: pass an `Idempotency-Key` header (any opaque string, up to 255 chars) to make retries safe. Replays within 24h return the original response with header `Idempotent-Replayed: true`. Reusing a key with a different body returns 409 `idempotency_conflict`.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Opaque key for safe retries. Same key + same body within 24h replays the cached response. Same key + different body returns 409."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreate"}}}},"responses":{"200":{"description":"Upserted (existing lead matched external_source + external_id)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Idempotency-Key reused with a different body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/leads/{id}":{"get":{"summary":"Get a lead","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Lead","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update a lead","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/availability":{"get":{"summary":"Get available appointment slots","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string"},"description":"ISO date or datetime"},{"name":"to","in":"query","schema":{"type":"string"},"description":"Defaults to from (single day)"},{"name":"preference","in":"query","schema":{"type":"string","enum":["morning","afternoon","evening","any"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":50},"description":"Max slots per day"}],"responses":{"200":{"description":"Availability per day","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityDay"}}}}}}}}}},"/appointments":{"get":{"summary":"List appointments","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}}],"responses":{"200":{"description":"List of appointments","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}}}}}}}}},"post":{"summary":"Book an appointment","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Appointment"}}}}}},"409":{"description":"Slot already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/appointments/{id}":{"get":{"summary":"Get an appointment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Appointment","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Appointment"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Reschedule / update an appointment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Appointment"}}}}}},"409":{"description":"Slot conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Cancel an appointment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Appointment"}}}}}}}}}}}