{"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."},"servers":[{"url":"https://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"}}}},"/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. 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}}}}}},"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"}}}},"/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"}}}}}}