{"openapi":"3.0.3","info":{"title":"CarBuddy Campaign Engine API","version":"1.0.0","description":"API for managing campaign enrollment, API keys, and recipient data.","contact":{"email":"support@carbuddyai.com"}},"servers":[{"url":"https://engine.carbuddyai.com","description":"Production"}],"paths":{"/webhook/{clientSlug}/enroll":{"post":{"summary":"Enroll recipients into a campaign","tags":["Enrollment"],"parameters":[{"name":"clientSlug","in":"path","required":true,"schema":{"type":"string"},"description":"Client slug (e.g., eden-motor)"}],"security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["campaign_type","recipients"],"properties":{"campaign_type":{"type":"string","description":"Campaign type (e.g., lost-sales)","example":"lost-sales"},"label":{"type":"string","description":"Batch label for tracking","example":"March 2026 Import"},"recipients":{"type":"array","minItems":1,"maxItems":10000,"items":{"type":"object","required":["first_name","email"],"properties":{"first_name":{"type":"string","example":"John"},"last_name":{"type":"string","example":"Smith"},"email":{"type":"string","format":"email","example":"john@example.com"},"mobile":{"type":"string","description":"UK mobile in E.164 format","example":"+447700123456"},"vehicle":{"type":"string","example":"Volkswagen Golf"},"vehicle_reg":{"type":"string","example":"DK21 ABC"},"manufacturer":{"type":"string","example":"Volkswagen"},"dealer":{"type":"string","example":"Eden Motor Group"},"assigned_branch":{"type":"string","description":"Branch name for lead routing","example":"Manchester"},"service_date":{"type":"string","description":"Date anchor for countdown-scheduled campaigns (ISO 8601)","example":"2026-08-14"},"salutation":{"type":"string","description":"Title (Mr/Mrs/Ms). Use as first_name when no forename is available.","example":"Mr"},"opted_in_sms":{"type":"boolean","description":"SMS consent (PECR). Three-state: true = send, false = never send, omitted/null = unknown and WE WILL SEND. Omission does not suppress — send an explicit false, or exclude the contact from the payload.","example":true},"opted_in_email":{"type":"boolean","description":"Email consent. Three-state: true = send, false = never send, omitted/null = unknown and WE WILL SEND. Omission does not suppress.","example":true}}}}}}}}},"responses":{"201":{"description":"Batch accepted. NOTE: a 201 does not mean every row enrolled — always read `skipped`.","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"integer","description":"Batch identifier — locates the upload in the portal"},"enrolled":{"type":"integer","description":"Rows that actually entered the campaign"},"duplicates":{"type":"integer","description":"Rows collapsed as duplicates (already-active contacts + repeats within this batch)"},"suppressed":{"type":"integer","description":"Rows dropped because the contact previously opted out or bounced"},"skipped":{"type":"array","description":"Per-row drop list. `idx` is the zero-based index into the recipients array you sent.","items":{"type":"object","properties":{"idx":{"type":"integer","example":12},"reason":{"type":"string","enum":["invalid_email","duplicate_email_active","duplicate_email_within_batch","duplicate_branch_service_date","suppressed","lost_sales_cycle_cap"],"example":"invalid_email"}}}},"warning":{"type":"string","description":"Present when rows enrolled without dealer/assigned_branch — those messages render with a blank dealer name"}}}}}},"400":{"description":"Validation error — `details` names the failing field and row index"},"401":{"description":"Missing, invalid, expired, or wrong-client API key"},"404":{"description":"Client not found"},"409":{"description":"Campaign has ended (its end_date has passed). Body carries campaign_type and end_date."},"422":{"description":"Batch failed a data-quality gate (e.g. surname mapped into both name columns). Nothing was enrolled."},"429":{"description":"Rate limit exceeded","headers":{"Retry-After":{"schema":{"type":"integer"}}}}}}},"/api/v1/{clientSlug}/batches/{batchId}/status":{"get":{"summary":"Get batch enrollment status","tags":["Batches"],"parameters":[{"name":"clientSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"batchId","in":"path","required":true,"schema":{"type":"integer"}}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Batch status","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"integer"},"client_slug":{"type":"string"},"campaign_type":{"type":"string"},"label":{"type":"string"},"total":{"type":"integer"},"active":{"type":"integer"},"replied":{"type":"integer"},"handed_over":{"type":"integer"},"unsubscribed":{"type":"integer"},"completed":{"type":"integer"}}}}}}}}},"/api/v1/{clientSlug}/recipients":{"get":{"summary":"Look up a recipient by email","tags":["Recipients"],"parameters":[{"name":"clientSlug","in":"path","required":true,"schema":{"type":"string"}},{"name":"email","in":"query","required":true,"schema":{"type":"string","format":"email"}}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Recipient details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"status":{"type":"string","enum":["active","paused","completed","unsubscribed"]},"classification":{"type":"string"},"conversation_count":{"type":"integer"},"replied":{"type":"boolean"},"handed_over":{"type":"boolean"},"enrolled_at":{"type":"string","format":"date-time"}}}}}},"404":{"description":"Recipient not found"}}}},"/api/v1/{clientSlug}/api-keys":{"get":{"summary":"List API keys for a client","tags":["API Keys"],"parameters":[{"name":"clientSlug","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"List of API keys"}}},"post":{"summary":"Create a new API key","tags":["API Keys"],"parameters":[{"name":"clientSlug","in":"path","required":true,"schema":{"type":"string"}}],"security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","example":"DMS Integration Key"},"scopes":{"type":"array","items":{"type":"string"},"example":["enroll"]},"rate_limit_rpm":{"type":"integer","default":60},"expires_at":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"API key created (raw key shown only once)"},"404":{"description":"Client not found"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"Per-client API key (starts with cb_) or internal enrollment key"}}}}