{"openapi":"3.0.3","info":{"title":"Strelve Public API","version":"1.0.0","description":"REST API for managing leads and receiving webhook events from your Strelve account.","contact":{"email":"support@strelve.co.nz"}},"servers":[{"url":"https://www.strelve.co.nz/api/v1","description":"Production"}],"security":[{"BearerAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key in the format: sk_live_..."}},"schemas":{"Lead":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string","nullable":true},"preferredContactTime":{"type":"string","nullable":true,"enum":["anytime","morning","afternoon","after_5pm"],"description":"Lead's preferred time to be rung back."},"address":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"zipCode":{"type":"string","nullable":true},"status":{"type":"string","enum":["new","contacted","proposal","won","lost"]},"categoryId":{"type":"string"},"categoryName":{"type":"string"},"source":{"type":"string"},"proposalValue":{"type":"integer","nullable":true},"actualValue":{"type":"integer","nullable":true},"priceMin":{"type":"integer","nullable":true},"notes":{"type":"string","nullable":true},"answers":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},"isDuplicate":{"type":"boolean"},"unsubscribed":{"type":"boolean","description":"True when the lead has opted out of automated emails."},"unsubscribedAt":{"type":"string","format":"date-time","nullable":true},"unsubscribeReason":{"type":"string","nullable":true,"enum":["too_expensive","went_with_someone_else","just_researching","timing_not_right","no_longer_needed","already_sorted","other"]},"unsubscribeReasonLabel":{"type":"string","nullable":true,"description":"Operator-facing opt-out reason label captured when the lead gave the reason."},"unsubscribeReasonNote":{"type":"string","nullable":true},"unsubscribeStage":{"type":"string","nullable":true,"enum":["new","contacted","proposal","won","lost"],"description":"Pipeline status when the lead first opted out."},"unsubscribeReasonUpdatedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"contactedAt":{"type":"string","format":"date-time","nullable":true}}},"Error":{"type":"object","required":["error","code","message","resolution"],"properties":{"error":{"type":"string","description":"Human-readable error string retained for API compatibility."},"code":{"type":"string","enum":["missing_api_key","invalid_api_key","invalid_api_key_format","account_inactive","professional_plan_required","rate_limit_exceeded","invalid_pagination","invalid_status_filter","invalid_from_date","invalid_to_date","lead_not_found","invalid_json","invalid_input","demo_api_read_only","status_transition_not_allowed","route_not_found"]},"message":{"type":"string","description":"Human-readable explanation."},"resolution":{"type":"string","description":"Suggested action for resolving the error."},"details":{"type":"object","description":"Optional validation details for invalid requests.","additionalProperties":true}}}}},"paths":{"/leads":{"get":{"operationId":"listLeads","summary":"List leads","description":"Returns a paginated list of leads for the authenticated client.","tags":["Leads"],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["new","contacted","proposal","won","lost"]},"description":"Filter by canonical lead status."},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter leads created on or after this ISO 8601 date-time."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Filter leads created on or before this ISO 8601 date-time."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Number of leads per page. Values above 100 are capped at 100."},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Number of leads to skip. Must be a whole number of 0 or greater."}],"responses":{"200":{"description":"A paginated list of leads","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"400":{"description":"Invalid status, date, limit, or offset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API access is not available for this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the supplied interval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/leads/{id}":{"get":{"operationId":"getLead","summary":"Get a lead","description":"Returns a single lead by ID when it belongs to the account authenticated by the API key.","tags":["Leads"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The lead","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"401":{"description":"Missing, invalid, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API access is not available for this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Lead not found for this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the supplied interval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateLeadStatus","summary":"Update lead status","description":"Updates the status of a lead belonging to the account authenticated by the API key. Only the status field can be changed.","tags":["Leads"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["new","contacted","proposal","won","lost"]}}}}}},"responses":{"200":{"description":"The updated lead","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}}}}},"400":{"description":"Request body is invalid JSON or contains an unsupported status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"API access is not available for this account or the demo key is read-only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Lead not found for this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The requested status transition is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the supplied interval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}