{"openapi":"3.1.0","info":{"title":"Grouthna CRM API","version":"1","description":"Authorization: Bearer gr_live_… — create a key (with only the scopes it needs) in Settings → API. 120 requests a minute per key; errors are { \"error\": { \"code\", \"message\" } }.\n\nWebhooks: add an HTTPS address in Settings → API and pick events. Each POST carries X-Grouthna-Event, X-Grouthna-Timestamp and X-Grouthna-Signature = sha256=HMAC_SHA256(secret, `${timestamp}.${body}`) — check it, and reject old timestamps. lead.created, lead.stage_changed and payment.paid carry `ad`: the ad the client came from (platform, campaign, adset, ad, ad_id, campaign_id, adset_id, form, form_id, click_id, click_type = fbclid|gclid|gbraid|wbraid|ttclid|sccid|twclid|ctwa, platform_lead_id, utm_*), or null."},"servers":[{"url":"https://staging.timdad.online/api/v1"}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"}}},"paths":{"/leads":{"post":{"summary":"Add or update a lead","description":"A website form, landing page or another system. Upserts by phone: 201 when created, 200 when the number already exists. Ad fields (platform, campaign, ad, click ids, utm_*) credit the exact ad.\n\nScope: `leads:write`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"phone":"0551234567","name":"Noura","service_code":"LASER","source":"website","campaign":"Summer offer","utm_source":"snapchat","gclid":"…"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"id":"lead-uuid","created":true}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}},"get":{"summary":"List leads","description":"Newest first.\n\nScope: `leads:read`","security":[{"bearer":[]}],"parameters":[{"name":"updated_since","in":"query","required":false,"description":"ISO date","schema":{"type":"string"}},{"name":"stage","in":"query","required":false,"description":"stage key","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1–200 (50)","schema":{"type":"string"}},{"name":"before","in":"query","required":false,"description":"ISO date, for paging","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"lead-uuid","name":"Noura","phone":"+966551234567","stage":"new","source":"website","campaign":"Summer offer","opted_out":false,"created_at":"2026-10-09T09:00:00Z"}],"next_before":null}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/leads/{id}":{"get":{"summary":"One lead","description":"The lead with its appointments.\n\nScope: `leads:read`","security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"id":"lead-uuid","name":"Noura","phone":"+966551234567","stage":"booked","appointments":[{"id":"appt-uuid","start_at":"2026-10-12T07:00:00Z","status":"booked","doctor":"Dr. Sara","service":"Cleaning"}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/messages":{"post":{"summary":"Send a WhatsApp message","description":"Three shapes. A template goes any time (creates the lead if new). Text and files go into the client's open conversation: on the official number only if the client wrote in the last 24 hours; QR numbers, Telegram and website chat any time — otherwise 409 window_closed (send a template). Files are fetched once from a public HTTPS link (no redirects, 20 MB).\n\nScope: `messages:write`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"phone":"0551234567","template":"invoice_ready","params":["Noura","INV-1001"],"header":{"link":"https://example.com/inv-1001.pdf","filename":"invoice.pdf"},"buttons":["ORD-1001"]}},"alt1":{"summary":"Plain text","value":{"phone":"0551234567","text":"Your order is on its way"}},"alt2":{"summary":"A file","value":{"phone":"0551234567","media_url":"https://example.com/menu.pdf","caption":"Our menu"}}}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"example":{"queued":true,"type":"text","message_id":"msg-uuid","conversation_id":"conv-uuid","lead_id":"lead-uuid"}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"404":{"description":"not_found — no approved template with this name"},"409":{"description":"window_closed / opted_out / blocked"},"422":{"description":"params count, media type or size"},"429":{"description":"Rate limited"}}}},"/whatsapp/numbers":{"get":{"summary":"Your QR WhatsApp numbers","description":"The business's QR-linked WhatsApp numbers and whether each is connected now — the number_id to send from.\n\nScope: `messages:read`","security":[{"bearer":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"number-uuid","name":"Sales","phone":"966551234567","connected":true}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/whatsapp/groups":{"get":{"summary":"Groups of a QR number","description":"The WhatsApp groups the number is in (connected numbers only).\n\nScope: `messages:read`","security":[{"bearer":[]}],"parameters":[{"name":"number_id","in":"query","required":true,"description":"required","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"120363000000000001@g.us","name":"VIP clients","size":48}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/whatsapp/send":{"post":{"summary":"Send from a chosen QR number","description":"Send from one of your QR numbers to any number (no earlier conversation needed — it shows in the inbox) or to a group the number is in. One of text (optionally with up to 3 buttons, or a list), media_url (+caption, filename) or poll. Buttons and lists aren't officially allowed from QR numbers: they may not show and can get the number restricted — at your own risk. Paced by the number's own sending queue. 409 when the number isn't connected or the client is blocked.\n\nScope: `messages:write`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"number_id":"number-uuid","to":"0551234567","text":"Your order is ready ✅"}},"alt1":{"summary":"To a group, with a file","value":{"number_id":"number-uuid","to":"120363000000000001@g.us","media_url":"https://example.com/offer.jpg","caption":"This week's offer"}},"alt2":{"summary":"A poll","value":{"number_id":"number-uuid","to":"0551234567","poll":{"question":"Which time suits you?","options":["Morning","Evening"],"multi":false}}},"alt3":{"summary":"Buttons (QR — WhatsApp may block them)","value":{"number_id":"number-uuid","to":"0551234567","text":"Book your visit?","buttons":[{"kind":"reply","text":"Book now"},{"kind":"url","text":"Our site","url":"https://example.com"}]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"queued":true,"to":"966551234567","message_id":"msg-uuid","conversation_id":"conv-uuid"}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"404":{"description":"not_found — no QR number with this id"},"409":{"description":"not_connected / blocked"},"422":{"description":"media type or size, poll options"},"429":{"description":"Rate limited"}}}},"/conversations":{"get":{"summary":"A client's conversations","description":"Up to 10 conversations (newest first) with their latest messages, oldest → newest. Team notes are left out.\n\nScope: `messages:read`","security":[{"bearer":[]}],"parameters":[{"name":"phone","in":"query","required":true,"description":"required","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"messages per conversation, 1–200 (50)","schema":{"type":"string"}},{"name":"before","in":"query","required":false,"description":"ISO date, to page back","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"contact":{"id":"contact-uuid","name":"Noura","phone":"+966551234567","opted_out":false,"blocked":false},"conversations":[{"id":"conv-uuid","channel":{"type":"whatsapp","name":"Main number"},"window_open":true,"messages":[{"id":"msg-uuid","direction":"in","kind":"text","text":"Hello","status":"received","at":"2026-10-09T09:00:00Z"}]}]}}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/templates":{"get":{"summary":"List templates","description":"WhatsApp templates and their status.\n\nScope: `templates:read`","security":[{"bearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"APPROVED | PENDING | REJECTED | DRAFT | PAUSED","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"name":"invoice_ready","category":"UTILITY","language":"ar","status":"APPROVED","body":"Hi {{1}}, your invoice {{2}} is ready.","variables":2,"header":{"type":"DOCUMENT"},"buttons":[{"type":"URL","text":"Pay","url":"https://pay.example.com/{{1}}","dynamic":true}]}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}},"post":{"summary":"Create a template","description":"Created and sent to WhatsApp for approval (submit: false keeps it a draft). Variables are {{1}}, {{2}}… with one example each. A media header is fetched from header.link (JPG/PNG 5 MB, MP4 16 MB, PDF 20 MB).\n\nScope: `templates:write`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"name":"invoice_ready","category":"UTILITY","language":"ar","body":"Hi {{1}}, your invoice {{2}} is ready.","examples":["Noura","INV-1001"],"footer":"Thank you","header":{"type":"DOCUMENT","link":"https://example.com/sample.pdf","filename":"invoice.pdf"},"buttons":[{"type":"URL","text":"Pay","url":"https://pay.example.com/{{1}}"}]}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"example":{"data":{"name":"invoice_ready","status":"PENDING"}}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"409":{"description":"conflict — same name and language"},"422":{"description":"invalid_template — the builder's checks"},"429":{"description":"Rate limited"}}}},"/templates/{name}":{"get":{"summary":"Template status","description":"One template, with the rejection reason when WhatsApp rejected it.\n\nScope: `templates:read`","security":[{"bearer":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":{"name":"invoice_ready","status":"PENDING","rejected_reason":null}}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/services":{"get":{"summary":"Services","description":"Active services with price, duration and doctors (for booking widgets).\n\nScope: `appointments:read`","security":[{"bearer":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"svc-uuid","code":"CLEAN","name":"Cleaning","price_sar":250,"duration_min":30,"doctors":[{"id":"doc-uuid","name":"Dr. Sara","accepts_new":true}]}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/slots":{"get":{"summary":"Free times","description":"Free times per doctor (Riyadh dates).\n\nScope: `appointments:read`","security":[{"bearer":[]}],"parameters":[{"name":"service_id","in":"query","required":true,"description":"required","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"YYYY-MM-DD","schema":{"type":"string"}},{"name":"days","in":"query","required":false,"description":"1–14 (7)","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"doctor_id":"doc-uuid","doctor":"Dr. Sara","accepts_new":true,"slots":[{"start_at":"2026-10-12T07:00:00Z","date":"2026-10-12","label":"10:00 ص"}]}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/appointments":{"get":{"summary":"Appointments","description":"Appointments between two dates (62 days max).\n\nScope: `appointments:read`","security":[{"bearer":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"ISO date","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"ISO date","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"data":[{"id":"appt-uuid","lead_id":"lead-uuid","client":"Noura","phone":"+966551234567","status":"confirmed"}]}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/otp":{"post":{"summary":"Send a verification code","description":"A code on WhatsApp through the business's approved verification template (the default «verify_code», or one of its own authentication templates built in Settings → Templates, by name). Grouthna makes the code unless you send one.\n\nScope: `otp:send`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"phone":"0551234567","ttl_minutes":5}},"alt1":{"summary":"Your own authentication template","value":{"phone":"0551234567","template":"login_code","code":"482913"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"sent":true,"otp_id":"otp-uuid","expires_at":"2026-10-09T09:05:00Z"}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/campaigns":{"post":{"summary":"Launch a WhatsApp campaign","description":"An approved template to a list of numbers (up to 5000), from your official number or a QR number (channel_id, else the campaign's default). Unknown numbers become leads. Each row may carry its own values for the template's variables (params per row) — else the campaign's params go to everyone ({name} = the client's first name). Optional fallbacks: an SMS text id when WhatsApp fails for good, then an email. Runs at once, or at schedule_at. 202 with the campaign id.\n\nScope: `campaigns:write`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"name":"October offer","template":"monthly_offer","channel_id":"channel-uuid","params":["{name}"],"phones":[{"phone":"0551234567","name":"Noura"},{"phone":"0559876543","params":["Sara"]}],"schedule_at":"2026-10-20T07:00:00Z","email_fallback":{"subject":"October offer","text":"Hi {{name}}, …"}}}}}}},"responses":{"202":{"description":"OK","content":{"application/json":{"example":{"id":"campaign-uuid","status":"sending","recipients":2,"skipped":0}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"404":{"description":"not_found — no approved template with this name / no such channel"},"422":{"description":"unprocessable — params count, empty list, plan limit"},"429":{"description":"Rate limited"}}}},"/campaigns/{id}":{"get":{"summary":"Campaign status","description":"Where it stands: counts per state and the delivery numbers.\n\nScope: `campaigns:write`","security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"id":"campaign-uuid","name":"October offer","status":"done","started_at":"2026-10-20T07:00:00Z","finished_at":"2026-10-20T07:03:00Z","recipients":2,"sent":2,"delivered":2,"read":1,"replied":0,"failed":0,"pending":0,"sms_fallback":0,"email_fallback":0}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/campaigns/{id}/cancel":{"post":{"summary":"Cancel a campaign","description":"What wasn't sent yet is skipped; what went stays.\n\nScope: `campaigns:write`","security":[{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"cancelled":true}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/otp/verify":{"post":{"summary":"Check a code","description":"Valid once, until it expires; 5 tries per code. A wrong code: { valid: false, reason, attempts_left }.\n\nScope: `otp:send`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"phone":"0551234567","code":"4821"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"valid":true}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}},"/calls":{"post":{"summary":"Log a call","description":"Any phone system reports a finished call. It lands on the client's card; a missed incoming call alerts the owner. Idempotent per (source, external_id).\n\nScope: `calls:write`","security":[{"bearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"examples":{"main":{"value":{"phone":"0551234567","direction":"in","status":"answered","duration_sec":134,"extension":"101","source":"grandstream","external_id":"1696512345.12"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"example":{"id":"call-uuid","lead_id":"lead-uuid","duplicate":false}}}},"401":{"description":"Missing, invalid or expired key"},"403":{"description":"Scope or plan"},"429":{"description":"Rate limited"}}}}}}