### Install and initialize @smileline/browser npm SDK Source: https://docs.smileline.io/guides/lead-capture/website-tracking Install the npm SDK and initialize it with your site key. The install command is shown in the npm SDK tab of the install card, and the import/init snippet is the printed example with your own key filled in. init defaults to the EU cell; US practices must pass apiBase. ```bash npm install @smileline/browser ``` ```javascript import { init, captureLead, consent } from "@smileline/browser"; init({ siteKey: "pk_YOUR_SITE_KEY" }); ``` -------------------------------- ### Cursor pagination response example Source: https://docs.smileline.io/reference/pagination Shows the JSON envelope for high-churn feeds. Pass the nextCursor back to get the next page; a null cursor means the end. Cursors are opaque and must not be parsed or constructed. ```json { "items": [], "nextCursor": "opaque-cursor-string" } ``` -------------------------------- ### DELETE /user/two-factor/setup Source: https://docs.smileline.io/reference/authentication Cancels only the pending setup. An existing active authenticator remains active. ```APIDOC ## DELETE /user/two-factor/setup ### Description Cancels only the pending setup. An existing active authenticator remains active. ### Method DELETE ### Endpoint /user/two-factor/setup ``` -------------------------------- ### POST /user/two-factor/setup Source: https://docs.smileline.io/reference/authentication Initiates two-factor authentication setup. Send password, and when replacing an active authenticator, also send its current code. Returns pending authenticator URI and expiry. ```APIDOC ## POST /user/two-factor/setup ### Description Initiates two-factor authentication setup. Send password, and when replacing an active authenticator, also send its current code. Returns pending authenticator URI and expiry. ### Method POST ### Endpoint /user/two-factor/setup ### Request Body - **password** (string) - Required - Account password. - **currentCode** (string) - Optional - Six-digit code from the current authenticator, required when replacing an active authenticator. ### Response #### Success Response - **authenticatorUri** (string) - Pending authenticator URI. - **expiry** (string) - Expiry of the pending setup. ``` -------------------------------- ### Sign and submit JSON to /capture/{token} with HMAC-SHA256 Source: https://docs.smileline.io/guides/lead-capture/custom-integrations Build the signed bytes as the timestamp, a full stop, the delivery ID, another full stop, and the exact raw request body. The command signs and submits one JSON body; keep the secret in your backend's secret store (the example environment variable is only a placeholder). A successful delivery answers 201 with {"ok":true}; the body must be a JSON object limited to 64 KiB, and if it contains _sl.event_id it must equal X-SmileLine-Delivery-Id. Sign the exact bytes you send; reformatting JSON, changing whitespace, or adding a newline after calculating the HMAC makes the signature invalid. ```text timestamp.deliveryId.rawBody ``` ```bash SMILELINE_CAPTURE_URL="https://api-eu.smileline.io/capture/YOUR_TOKEN" body='{"first_name":"Amelia","email":"amelia@example.co.uk"}' timestamp="$(date +%s)" delivery_id="lead-$(uuidgen)" signature="$(printf '%s' "${timestamp}.${delivery_id}.${body}" | openssl dgst -sha256 -hmac "$SMILELINE_CAPTURE_SECRET" -hex | sed 's/^.* //')" curl -X POST "$SMILELINE_CAPTURE_URL" \ -H "Content-Type: application/json" \ -H "X-SmileLine-Delivery-Id: ${delivery_id}" \ -H "X-SmileLine-Timestamp: ${timestamp}" \ -H "X-SmileLine-Signature: v1=${signature}" \ --data-raw "$body" ``` -------------------------------- ### GET /health Source: https://docs.smileline.io/reference/authentication Coarse-limited app/platform database and production-configuration readiness check. Returns no practice data. ```APIDOC ## GET /health ### Description Coarse-limited app/platform database and production-configuration readiness check. Returns no practice data. ### Method GET ### Endpoint /health ### Response #### Success Response No practice data returned. ``` -------------------------------- ### CSV response body example Source: https://docs.smileline.io/reference/patients/patients/export/get Example of the CSV response body returned by the export endpoint when the response content type is text/csv. ```text "string" ``` -------------------------------- ### Install the SmileLine chat widget script Source: https://docs.smileline.io/guides/chat-widget/setup Paste this snippet before the closing tag on every page of your website, or into your site builder's custom scripts slot. The EU hostname is illustrative; copy the generated snippet exactly, as US organizations use widget-us.smileline.io. The data-key identifies your practice; no login or API key is needed. ```html ``` -------------------------------- ### SmileLine website tracking snippet Source: https://docs.smileline.io/guides/lead-capture/website-tracking Install this snippet on every page of your website, ideally in a site-wide header. Copy yours from SmileLine because it carries your practice's site key and regional hostname. Keep the complete data-site value including pk_; a missing prefix produces a 404 and leaves Snippet installed unchecked. ```html ``` -------------------------------- ### GET /health/runtime Source: https://docs.smileline.io/reference/authentication Storage-free Worker liveness check. Returns 204 and no practice data. ```APIDOC ## GET /health/runtime ### Description Storage-free Worker liveness check. Returns 204 and no practice data. ### Method GET ### Endpoint /health/runtime ### Response #### Success Response (204) No practice data returned. ``` -------------------------------- ### Offset pagination response example Source: https://docs.smileline.io/reference/pagination Shows the JSON envelope returned by list endpoints with limit and offset query parameters. The total field is the full match count for filters, enabling page number rendering. ```json { "items": [], "total": 412, "limit": 50, "offset": 0 } ``` -------------------------------- ### GET /auth/me response body Source: https://docs.smileline.io/reference/authentication Response body of GET /auth/me. Contains the authenticated user, active session, and organization object with member access facts: workspaces, defaultWorkspace, pmsAccess, and accessGeneration. The organization object is derived on every call and never stored. ```json { "user": { "id": "…", "email": "…", "name": "…" }, "session": { "activeOrganizationId": "…" }, "organization": { "id": "…", "name": "…", "memberRole": "owner", "workspaces": ["crm", "pms"], "defaultWorkspace": "crm", "pmsAccess": { "pmsWorkspaceAvailable": true, "nativeWritesEnabled": false, "systemOfRecord": "external", "authorityGeneration": 1 }, "accessGeneration": 3 } } ``` -------------------------------- ### GET /patients/export Source: https://docs.smileline.io/reference/patients/patients/export/get Exports patients as CSV with the same filtering as GET /patients. Supports selecting columns and restricting to specific patient IDs. Caps at 10000 rows; errors with EXPORT_TOO_LARGE if exceeded. ```APIDOC ## GET /patients/export ### Description Exports patients as CSV with the same filtering as GET /patients. Supports selecting and ordering columns, and restricting to specific patient IDs. Caps at 10000 rows; returns EXPORT_TOO_LARGE if exceeded. ### Method GET ### Endpoint `/patients/export` ### Authorization - **bearerAuth** (header) - Required - A practice API key or OAuth access token, presented as `Authorization: Bearer `. The token itself names the practice, so no organization header is needed. - In: `header` ### Query Parameters - **sort** (string) - Optional - Default: `"createdAt"`. Allowed values: `createdAt`, `updatedAt`, `lastName`, `firstName`, `status`, `dateOfBirth`, `city`, `email`, `relevance`. - **direction** (string) - Optional - Default: `"desc"`. Allowed values: `asc`, `desc`. - **q** (string) - Optional - Length: `1 <= length`. - **filters** (string) - Optional - Length: `length <= 4000`. - **status** (string) - Optional - Allowed values: `enquiry`, `lead`, `patient`, `inactive`, `archived`, `deceased`. - **tagId** (string) - Optional - Format: `uuid`, matches UUID pattern. - **leadSourceId** (string) - Optional - Format: `uuid`, matches UUID pattern. - **includeArchived** (string) - Optional. - **columns** (string) - Optional - Selects and orders CSV columns. Matches: `^[a-zA-Z]+(,[a-zA-Z]+)*$`. - **ids** (string) - Optional - Restricts to a selection of patients. Matches: `^[0-9a-fA-F-]{36}(,[0-9a-fA-F-]{36})*$`. ### Request Example ``` curl -X GET "https://example.com/patients/export" ``` ### Response #### Success Response (200) - Content-Type: `text/csv` - Content-Type: `application/json` #### Response Example (application/json) ``` "string" ``` #### Error Response (400) ``` { "error": "string", "code": "string", "details": null} ``` ### Error Handling - If the export exceeds 10000 rows, an error with code `EXPORT_TOO_LARGE` is returned. ``` -------------------------------- ### Create patient 201 response Source: https://docs.smileline.io/reference/patients/patients/post Example response body for a successful patient creation (HTTP 201). Shows the full patient object including id, organizationId, status, timestamps, and optional fields. ```json { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "organizationId": "7bc05553-4b68-44e8-b7bc-37be63c6d9e9", "locationId": "1a5515a3-ba81-4a42-aee7-ad9ffc090a54", "status": "enquiry", "title": "string", "firstName": "string", "lastName": "string", "preferredName": "string", "chartNumber": -9007199254740991, "ethnicityCode": "string", "ethnicityRuleId": "fbb1fed1-4038-443e-9e7c-9f9994a3c864", "ethnicityRuleKind": "string", "dateOfBirth": "2019-08-24", "gender": "male", "fiscalCode": "string", "stsOpposition": true, "email": "string", "phoneMobile": "string", "phoneOther": "string", "addressLine1": "string", "addressLine2": "string", "city": "string", "province": "string", "postalCode": "string", "country": "string", "language": "string", "preferredContactMethod": "phone", "preferredContactTime": "string", "smsOptOut": true, "emailOptOut": true, "whatsappOptOut": true, "phoneOptOut": true, "consentSource": "string", "consentAt": "2019-08-24T14:15:22Z", "leadSourceId": "e6ca3cdd-ccfa-4eb5-b664-896d1a3a2781", "referrerId": "23ef66d0-7f25-4763-b652-182b2f24d19f", "referredByPatientId": "4b657c4d-a1b8-4578-96b1-5efa4df31bed", "mergedIntoPatientId": "8eb8982a-258c-4fe7-8bcb-82ed96c8ba6c", "medicalAlert": "string", "note": "string", "customFields": null, "createdById": "string", "createdByName": "string", "createdBySource": "user", "archivedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z"} ``` -------------------------------- ### List patients 200 response Source: https://docs.smileline.io/reference/patients/patients/get Example 200 response body for the list patients endpoint. Shows the patient object structure including tags, timestamps, and pagination fields. ```json { "items": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "organizationId": "7bc05553-4b68-44e8-b7bc-37be63c6d9e9", "locationId": "1a5515a3-ba81-4a42-aee7-ad9ffc090a54", "status": "enquiry", "title": "string", "firstName": "string", "lastName": "string", "preferredName": "string", "chartNumber": -9007199254740991, "ethnicityCode": "string", "ethnicityRuleId": "fbb1fed1-4038-443e-9e7c-9f9994a3c864", "ethnicityRuleKind": "string", "dateOfBirth": "2019-08-24", "gender": "male", "fiscalCode": "string", "stsOpposition": true, "email": "string", "phoneMobile": "string", "phoneOther": "string", "addressLine1": "string", "addressLine2": "string", "city": "string", "province": "string", "postalCode": "string", "country": "string", "language": "string", "preferredContactMethod": "phone", "preferredContactTime": "string", "smsOptOut": true, "emailOptOut": true, "whatsappOptOut": true, "phoneOptOut": true, "consentSource": "string", "consentAt": "2019-08-24T14:15:22Z", "leadSourceId": "e6ca3cdd-ccfa-4eb5-b664-896d1a3a2781", "referrerId": "23ef66d0-7f25-4763-b652-182b2f24d19f", "referredByPatientId": "4b657c4d-a1b8-4578-96b1-5efa4df31bed", "mergedIntoPatientId": "8eb8982a-258c-4fe7-8bcb-82ed96c8ba6c", "medicalAlert": "string", "note": "string", "customFields": null, "createdById": "string", "createdByName": "string", "createdBySource": "user", "archivedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "tags": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "organizationId": "7bc05553-4b68-44e8-b7bc-37be63c6d9e9", "label": "string", "color": "string", "position": -9007199254740991, "archivedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z" } ] } ], "total": 0, "limit": -9007199254740991, "offset": -9007199254740991} ``` -------------------------------- ### GET /patients Source: https://docs.smileline.io/reference/patients/patients/post Paginated list of patients with search, filtering, sorting, and embedding capabilities. ```APIDOC ## GET /patients ### Description Paginated list. q is a typo-tolerant (trigram) search over name, email, phones, fiscal code and address (street, city, postal code). `filters` is URL-encoded JSON matching the PatientFilters schema; keys are AND-ed. sort=relevance ranks by similarity to q. embed=tags attaches each row's tags. Archived patients are excluded unless includeArchived=true or a status filter asks for archived. ### Method GET ### Endpoint `/patients` ### Parameters #### Query Parameters - **q** (string) - Optional - Search query - **filters** (string) - Optional - URL-encoded JSON matching the PatientFilters schema - **sort** (string) - Optional - Sort order, e.g., relevance - **embed** (string) - Optional - Comma-separated list of related resources to embed, e.g., tags - **includeArchived** (boolean) - Optional - Include archived patients ### Response #### Success Response (200) - **items** (array) - List of patients - **total** (integer) - Total number of patients - **page** (integer) - Current page - **limit** (integer) - Items per page #### Response Example ```json { "items": [], "total": 0, "page": 1, "limit": 20 } ``` ``` -------------------------------- ### Export patients as CSV with cURL Source: https://docs.smileline.io/reference/patients/patients/export/get Exports patients as CSV using the same filtering as GET /patients. The response is capped at 10000 rows; exceeding that returns the error code EXPORT_TOO_LARGE. ```bash curl -X GET "https://example.com/patients/export" ``` -------------------------------- ### cURL request to add/remove tags on many patients Source: https://docs.smileline.io/reference/patients/patients/bulk/tags/post Sends a POST request to /patients/bulk/tags with a JSON body containing patientIds. The example only includes patientIds, but the API requires at least one of addTagIds or removeTagIds. ```bash curl -X POST "https://example.com/patients/bulk/tags" \ -H "Content-Type: application/json" \ -d '{ "patientIds": [ "aa491413-9355-43ea-a48b-efef7edb7812" ] }' ``` -------------------------------- ### Event envelope JSON example Source: https://docs.smileline.io/reference/webhooks The version 1 event envelope that every webhook request body uses. It identifies the changed resource via subject and the actor via actor.type, which can be user, system, patient, or provider. The envelope does not include a before/after snapshot, so side effects should be keyed on the stable logical event id. ```json { "version": "1", "id": "019c1234-5678-7000-8000-000000000001", "type": "patient.updated", "createdAt": "2026-07-19T14:32:11.000Z", "organizationId": "practice_123", "subject": { "type": "patient", "id": "019c1234-5678-7000-8000-000000000002" }, "actor": { "type": "user", "id": "user_123" }, "data": { "action": "update", "changedFields": ["firstName", "email"] } } ``` -------------------------------- ### GET /patients Source: https://docs.smileline.io/reference/patients/patients/get Retrieves a paginated list of patients. Supports search, filtering, sorting, and embedding tags. Archived patients are excluded unless includeArchived=true or a status filter requests archived. ```APIDOC ## GET /patients ### Description Retrieves a paginated list of patients. Supports typo-tolerant search, filtering, sorting, and embedding tags. Archived patients are excluded unless includeArchived=true or a status filter requests archived. ### Method GET ### Endpoint `/patients` ### Authorization `bearerAuth` - Bearer A practice API key or an OAuth access token, both presented as `Authorization: Bearer `. The token itself names the practice, so no organization header is needed. In: header ### Query Parameters - **limit** (integer) - Optional - Range: 1 <= value <= 100, Default: 25 - **offset** (integer) - Optional - Range: 0 <= value <= 9007199254740991, Default: 0 - **sort** (string) - Optional - Default: "createdAt". Allowed values: "createdAt", "updatedAt", "lastName", "firstName", "status", "dateOfBirth", "city", "email", "relevance" - **direction** (string) - Optional - Default: "desc". Allowed values: "asc", "desc" - **q** (string) - Optional - Length: 1 <= length - **filters** (string) - Optional - Length: length <= 4000. URL-encoded JSON matching the PatientFilters schema; keys are AND-ed. - **embed** (string) - Optional - e.g., "tags" to attach each row's tags. - **status** (string) - Optional - Allowed values: "enquiry", "lead", "patient", "inactive", "archived", "deceased" - **tagId** (string) - Optional - UUID format - **leadSourceId** (string) - Optional - UUID format - **includeArchived** (string) - Optional - If true, includes archived patients. ### Request Example ``` curl -X GET "https://api-eu.smileline.io/patients" \ -H "Authorization: Bearer " ``` ### Response #### Success Response (200) - **items** (array of Patient objects) - List of patients. - **total** (integer) - Total number of patients. - **limit** (integer) - Limit used. - **offset** (integer) - Offset used. #### Response Example ``` { "items": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "organizationId": "7bc05553-4b68-44e8-b7bc-37be63c6d9e9", "locationId": "1a5515a3-ba81-4a42-aee7-ad9ffc090a54", "status": "enquiry", "title": "string", "firstName": "string", "lastName": "string", "preferredName": "string", "chartNumber": -9007199254740991, "ethnicityCode": "string", "ethnicityRuleId": "fbb1fed1-4038-443e-9e7c-9f9994a3c864", "ethnicityRuleKind": "string", "dateOfBirth": "2019-08-24", "gender": "male", "fiscalCode": "string", "stsOpposition": true, "email": "string", "phoneMobile": "string", "phoneOther": "string", "addressLine1": "string", "addressLine2": "string", "city": "string", "province": "string", "postalCode": "string", "country": "string", "language": "string", "preferredContactMethod": "phone", "preferredContactTime": "string", "smsOptOut": true, "emailOptOut": true, "whatsappOptOut": true, "phoneOptOut": true, "consentSource": "string", "consentAt": "2019-08-24T14:15:22Z", "leadSourceId": "e6ca3cdd-ccfa-4eb5-b664-896d1a3a2781", "referrerId": "23ef66d0-7f25-4763-b652-182b2f24d19f", "referredByPatientId": "4b657c4d-a1b8-4578-96b1-5efa4df31bed", "mergedIntoPatientId": "8eb8982a-258c-4fe7-8bcb-82ed96c8ba6c", "medicalAlert": "string", "note": "string", "customFields": null, "createdById": "string", "createdByName": "string", "createdBySource": "user", "archivedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z", "tags": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "organizationId": "7bc05553-4b68-44e8-b7bc-37be63c6d9e9", "label": "string", "color": "string", "position": -9007199254740991, "archivedAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z" } ] } ], "total": 0, "limit": -9007199254740991, "offset": -9007199254740991 } ``` #### Error Response (400) - **error** (string) - Error message. - **code** (string) - Error code. - **details** (object) - Additional details. #### Error Example ``` { "error": "string", "code": "string", "details": null } ``` ``` -------------------------------- ### /widget/* Source: https://docs.smileline.io/reference/authentication Practice embed key and visitor identity, with per-IP and per-connection rate limits. ```APIDOC ## /widget/* ### Description Practice embed key and visitor identity, with per-IP and per-connection rate limits. ### Endpoint /widget/* ``` -------------------------------- ### Call sl() global command queue for consent and captureLead Source: https://docs.smileline.io/guides/lead-capture/website-tracking Use the global command queue API, safe to call before the script loads. The first call grants consent, the second captures a lead with a form token and email. This is the same API as the npm SDK but accessed via the sl() global. ```javascript sl("consent", "granted"); sl("captureLead", "YOUR_FORM_TOKEN", { email: "amelia@example.co.uk" }); ``` -------------------------------- ### POST /capture/{token} Source: https://docs.smileline.io/reference/authentication Accepts a practice routing token and request/rate limits. In signed mode, requires HMAC headers. ```APIDOC ## POST /capture/{token} ### Description Accepts a practice routing token and request/rate limits. In signed mode, requires HMAC headers. ### Method POST ### Endpoint /capture/{token} ### Parameters #### Path Parameters - **token** (string) - Required - Unguessable practice routing token. ### Notes - In signed mode, HMAC headers are required (see Custom integrations). ``` -------------------------------- ### POST /settings/webhook-endpoints/subscriptions Source: https://docs.smileline.io/reference/webhooks Creates a webhook endpoint subscription, sends a signed verification probe, and enables the endpoint upon successful echo. This route is idempotent based on subscriptionKey and requires OAuth authentication. ```APIDOC ## POST /settings/webhook-endpoints/subscriptions ### Description Creates a webhook endpoint subscription in one call. It creates the endpoint, sends a signed verification probe with an X-Hook-Secret challenge, and enables the endpoint only when the receiver returns a 2xx response with the same header value. This route is idempotent based on subscriptionKey and requires OAuth authentication. ### Method POST ### Endpoint /settings/webhook-endpoints/subscriptions ### Parameters #### Request Body - **name** (string) - Required - The name of the webhook endpoint. - **url** (string) - Required - The destination URL for the webhook. - **events** (array of strings) - Required - The list of event types to subscribe to (e.g., "journey.won"). - **subscriptionKey** (string) - Required - A stable identity for the subscription, used for idempotent upsert. ### Request Example ``` curl -X POST "$SMILELINE_API_URL/settings/webhook-endpoints/subscriptions" \ -H "Authorization: Bearer sl_oat_EXAMPLE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Orders bridge", "url": "https://hooks.example.com/smileline", "events": ["journey.won"], "subscriptionKey": "orders-bridge-1" }' ``` ### Response #### Success Response (201) - **201** - Created, verified and enabled. #### Success Response (200) - **200** - An existing subscription for this key was returned or repaired. #### Error Response (403) - **403** - OAUTH_CONNECTION_REQUIRED, or OAUTH_CONNECTION_REVOKED if the connection was disconnected mid-call — the endpoint is removed again. #### Error Response (409) - **409** - WEBHOOK_ENDPOINT_LIMIT_REACHED, WEBHOOK_SUBSCRIPTION_CONFLICT (the key belongs to another connection, or names a different url), WEBHOOK_ENDPOINT_PAUSED, WEBHOOK_SUBSCRIPTION_IN_PROGRESS (a concurrent create has not committed yet; retry), or WEBHOOK_VERIFICATION_FAILED. #### Error Details - **WEBHOOK_VERIFICATION_FAILED** carries the probe's own outcome in `details` — `responseStatus` and `error` — so you can distinguish an endpoint that answered `500`, could not be reached, or did not echo the challenge. The challenge is one-time and is never stored as subscription signing material. An endpoint that fails verification this way is not left behind: it is archived, and its slot is returned. ### Notes - The route requires an OAuth connection; an API key gets `403` with `code: "OAUTH_CONNECTION_REQUIRED"`. - `subscriptionKey` is your own stable identity for one subscription, and it is what makes the route an idempotent upsert. Retrying after a lost response returns the **same** endpoint with the **same** `signingSecret` and a `200` instead of a `201`, rather than spending another of the practice's 20 endpoint slots — this works even when the practice is already at the cap, and when two identical calls race, the loser resolves to the winner's subscription rather than erroring. An endpoint left disabled by an interrupted attempt is re-probed and enabled again, consuming no new capacity. - Two things a re-subscribe deliberately will not do. It will not move a subscription to a different `url` — the key identifies the destination, so a changed URL under an existing key is `409` rather than a silent re-point. And it will not resume a **paused** endpoint: pausing is either an operator's decision or the circuit breaker after twenty consecutive failures, and neither should be reversible by a client retry. Resume it from **Settings → Integrations** first. `name` and `events` changes are applied normally. ``` -------------------------------- ### Subscribe in one call with POST /settings/webhook-endpoints/subscriptions Source: https://docs.smileline.io/reference/webhooks Use this route to create, verify, and enable a webhook endpoint in one call. It requires an OAuth connection; an API key returns 403 with code "OAUTH_CONNECTION_REQUIRED". The subscriptionKey makes the call an idempotent upsert, and a retry returns the same endpoint with the same signingSecret and a 200 instead of 201. ```bash curl -X POST "$SMILELINE_API_URL/settings/webhook-endpoints/subscriptions" \ -H "Authorization: Bearer sl_oat_EXAMPLE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Orders bridge", "url": "https://hooks.example.com/smileline", "events": ["journey.won"], "subscriptionKey": "orders-bridge-1" }' ``` -------------------------------- ### Authenticate with API key using curl Source: https://docs.smileline.io/reference/authentication Use this curl command to authenticate with an API key. Set SMILELINE_API_URL to the active organization's EU or US base URL. Send X-Organization-ID when the key owner belongs to more than one practice; without it, the API auto-selects an organization only when exactly one membership exists, otherwise returns 403. API keys have a limit of 300 requests per minute. ```bash curl "$SMILELINE_API_URL/patients?limit=10" \ -H "Authorization: Bearer $SMILELINE_API_KEY" \ -H "X-Organization-ID: $SMILELINE_ORGANIZATION_ID" ``` -------------------------------- ### /site/* and /form-page/{token} Source: https://docs.smileline.io/reference/authentication Public site key or hosted-form token used by tracking script and hosted form pages. Serves per-practice form configuration and accepts form-discovery reports. ```APIDOC ## /site/* and /form-page/{token} ### Description Public site key or hosted-form token used by tracking script and hosted form pages. Serves per-practice form configuration and accepts form-discovery reports. No patient data is readable. ### Endpoints - /site/* - /form-page/{token} ### Parameters #### Path Parameters - **token** (string) - Required - Hosted-form token. ### Notes - Coarse and per-site rate limits apply. ``` -------------------------------- ### /r/* Source: https://docs.smileline.io/reference/authentication Reputation request token sent in a bounded JSON body, with neutral non-enumerating state and per-IP rate limits. ```APIDOC ## /r/* ### Description Reputation request token sent in a bounded JSON body, with neutral non-enumerating state and per-IP rate limits. ### Endpoint /r/* ``` -------------------------------- ### POST /user/two-factor/verify Source: https://docs.smileline.io/reference/authentication Verifies the new authenticator code. Success activates it atomically and returns new backup codes. ```APIDOC ## POST /user/two-factor/verify ### Description Verifies the new authenticator code. Success activates it atomically and returns new backup codes. ### Method POST ### Endpoint /user/two-factor/verify ### Request Body - **code** (string) - Required - Six-digit code from the new authenticator. ### Response #### Success Response - **backupCodes** (array of strings) - New backup codes. ``` -------------------------------- ### Post lead to POST /leads with OAuth token Source: https://docs.smileline.io/guides/lead-capture/direct-posting Posts a lead from a connected integration to POST /leads using an OAuth access token. Requires write scope plus patient:create and journey:create; treatmentSlug is required and must be a slug from Settings → Treatments. The body is capped at 128 KiB and must include an email or mobile number. ```bash curl -X POST "$SMILELINE_API_URL/leads" \ -H "Authorization: Bearer sl_oat_EXAMPLE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Amelia", "lastName": "Hart", "email": "amelia@example.co.uk", "phoneMobile": "+447700900123", "postalCode": "W6 0NB", "preferredContactTime": "after 6pm", "treatmentSlug": "invisalign", "message": "Looking for a consultation", "eventId": "crm-4711", "landingUrl": "https://example.co.uk/invisalign", "attribution": { "utm_source": "google", "gclid": "EXAMPLE" } }' ``` -------------------------------- ### /booking/* Source: https://docs.smileline.io/reference/authentication Public booking-page token or opaque appointment-management UID, with rate limits. ```APIDOC ## /booking/* ### Description Public booking-page token or opaque appointment-management UID, with rate limits. ### Endpoint /booking/* ``` -------------------------------- ### POST /settings/webhook-endpoints Source: https://docs.smileline.io/reference/webhooks Creates a new outbound webhook endpoint with a name, HTTPS URL, and at least one event. The response returns the signingSecret exactly once. ```APIDOC ## POST /settings/webhook-endpoints ### Description Creates a new outbound webhook endpoint. The endpoint must have a name, an HTTPS URL, and at least one event. The response returns the signingSecret exactly once, so save it before continuing. ### Method POST ### Endpoint /settings/webhook-endpoints ### Parameters #### Request Body - **name** (string) - Required - Name of the endpoint. - **url** (string) - Required - HTTPS URL of the endpoint. Must be no more than 2,048 UTF-8 bytes. - **events** (array of strings) - Required - List of event types to subscribe to (e.g., "patient.created", "journey.won"). ### Request Example ``` curl -X POST "$SMILELINE_API_URL/settings/webhook-endpoints" \ -H "Authorization: Bearer $SMILELINE_API_KEY" \ -H "X-Organization-ID: $SMILELINE_ORGANIZATION_ID" \ -H "Content-Type: application/json" \ -d '{ "name": "CRM events", "url": "https://hooks.example.com/smileline", "events": ["patient.created", "journey.won"] }' ``` ### Response #### Success Response (200) - **signingSecret** (string) - The signing secret for the endpoint. Returned exactly once. #### Response Example ``` { "signingSecret": "..." } ``` ``` -------------------------------- ### /webhooks/* Source: https://docs.smileline.io/reference/authentication Provider signatures, callback credentials, or signed single-use OAuth state as appropriate. ```APIDOC ## /webhooks/* ### Description Provider signatures, callback credentials, or signed single-use OAuth state as appropriate. ### Endpoint /webhooks/* ``` -------------------------------- ### POST /user/two-factor/backup-codes Source: https://docs.smileline.io/reference/authentication Replaces every existing backup code. Requires password. ```APIDOC ## POST /user/two-factor/backup-codes ### Description Replaces every existing backup code. Requires password. ### Method POST ### Endpoint /user/two-factor/backup-codes ### Request Body - **password** (string) - Required - Account password. ``` -------------------------------- ### Authenticate with OAuth access token using curl Source: https://docs.smileline.io/reference/authentication Use this curl command to authenticate with an OAuth access token. Send the access token exactly as you would an API key. Access tokens last one hour; refresh tokens last 30 days and rotate on every use. Do not send X-Organization-ID with OAuth tokens because the header is ignored. ```bash curl "$SMILELINE_API_URL/patients?limit=10" \ -H "Authorization: Bearer sl_oat_EXAMPLE_TOKEN" ``` -------------------------------- ### curl test for SmileLine capture endpoint Source: https://docs.smileline.io/guides/lead-capture/direct-posting Test the endpoint from the command line before wiring up the website. A successful capture answers 201 with {"ok":true} and the lead appears on the Today page in real time. ```bash curl -X POST "https://api-eu.smileline.io/capture/YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"first_name":"Amelia","email":"amelia@example.co.uk","phone":"+447700900123"}' ``` -------------------------------- ### Check active organization with curl /auth/me Source: https://docs.smileline.io/reference Check the active organization and user details for the authenticated session or API key. Set SMILELINE_API_URL to the production EU base URL and SMILELINE_API_KEY to your key before running. The response includes your user, active organization, member role, and accessible workspaces. ```bash export SMILELINE_API_URL="https://api-eu.smileline.io" curl "$SMILELINE_API_URL/auth/me" \ -H "Authorization: Bearer $SMILELINE_API_KEY" ``` -------------------------------- ### POST /settings/webhook-endpoints/{id}/enable Source: https://docs.smileline.io/reference/webhooks Enables a verified endpoint. Real events are not queued while the endpoint is disabled or paused. ```APIDOC ## POST /settings/webhook-endpoints/{id}/enable ### Description Enables a verified endpoint. Real events are not queued while the endpoint is disabled or paused. ### Method POST ### Endpoint /settings/webhook-endpoints/{id}/enable ### Parameters #### Path Parameters - **id** (string) - Required - The ID of the webhook endpoint. ### Response #### Success Response (200) No specific response body is documented. ``` -------------------------------- ### Create a patient with cURL Source: https://docs.smileline.io/reference/patients/patients/post Creates a patient by sending a POST request to /patients with a JSON body. The request must include at least one of email or phoneMobile, and the firstName field is required. The response is a 201 status with the created patient object. ```bash curl -X POST "https://example.com/patients" \ -H "Content-Type: application/json" \ -d '{ "firstName": "string" }' ``` -------------------------------- ### POST /leads Source: https://docs.smileline.io/guides/lead-capture/direct-posting Posts a lead from a connected integration using an OAuth connection. It uses the same pipeline as the browser capture endpoint: patient matching, attribution, automation enrolment, and a Deliveries record. ```APIDOC ## POST /leads ### Description Posts a lead from a connected integration (e.g., Zapier or your own server) using an OAuth connection. It uses the same pipeline as the browser capture endpoint: patient matching, attribution, automation enrolment, and a Deliveries record. ### Method POST ### Endpoint /leads ### Parameters #### Request Body - **firstName** (string) - Optional - Patient's first name. - **lastName** (string) - Optional - Patient's last name. - **email** (string) - Optional - Patient's email address. Either email or phoneMobile is required. - **phoneMobile** (string) - Optional - Patient's mobile phone number. Either email or phoneMobile is required. - **postalCode** (string) - Optional - Postal code. - **preferredContactTime** (string) - Optional - Preferred contact time. - **treatmentSlug** (string) - Required - Treatment slug from Settings → Treatments. Must be a valid, non-archived slug. - **message** (string) - Optional - Message from the lead. - **eventId** (string) - Optional - Idempotency key. If omitted, SmileLine generates one from a digest of the body. - **landingUrl** (string) - Optional - Landing page URL. - **attribution** (object) - Optional - Flat map of attribution parameters (e.g., utm_*, gclid, fbclid). At most 40 keys. Keys must not reuse lead field names. - **location** (string) - Optional - Location id or case-insensitive location name. - **phoneOther** (string) - Optional - Additional phone number. - **dateOfBirth** (string) - Optional - Date of birth. - **gender** (string) - Optional - Gender. - **addressLine1** (string) - Optional - Address line 1. - **addressLine2** (string) - Optional - Address line 2. - **city** (string) - Optional - City. - **province** (string) - Optional - Province, county, or state. - **country** (string) - Optional - Country. - **preferredContactMethod** (string) - Optional - Preferred contact method. - **note** (string) - Optional - Additional note. ### Request Example ```json { "firstName": "Amelia", "lastName": "Hart", "email": "amelia@example.co.uk", "phoneMobile": "+447700900123", "postalCode": "W6 0NB", "preferredContactTime": "after 6pm", "treatmentSlug": "invisalign", "message": "Looking for a consultation", "eventId": "crm-4711", "landingUrl": "https://example.co.uk/invisalign", "attribution": { "utm_source": "google", "gclid": "EXAMPLE" } } ``` ### Response #### Success Response (201) - **patientId** (string) - ID of the patient. - **journeyId** (string) - ID of the journey (never null). - **taskId** (string) - ID of the task. - **patientWasCreated** (boolean) - Whether the patient was newly created. - **organizationId** (string) - ID of the organization. - **eventId** (string) - Idempotency key used. #### Response Example ```json { "patientId": "pat_123", "journeyId": "jour_456", "taskId": "task_789", "patientWasCreated": true, "organizationId": "org_101", "eventId": "crm-4711" } ``` #### Success Response (202) Stored durably but not processed yet; SmileLine replays it automatically. Treat as success and do not re-send. #### Error Responses - **400** - Invalid body, or unknown or archived treatmentSlug. - **401** - Access token expired or invalid. - **403** - Connection lacks write scope, or user role cannot create patients/enquiries. - **409** - IDEMPOTENCY_KEY_REUSED, LEAD_INTAKE_DISABLED, or same lead still being processed. - **413** - Body exceeded 128 KiB. ### Notes - Requires `write` scope plus `patient:create` and `journey:create` on the acting user's role. - Body capped at 128 KiB. - Field names are fixed camelCase; no field mapping applies. - A value that cannot be read is left empty rather than rejected. - A submission matching an existing patient fills that record's blanks without overwriting it. - Attribution keys may not reuse lead field names. - Requests are idempotent via `eventId` or body digest. - All request bodies are kept, regardless of outcome. - Leads arrive through a capture form named "Zapier" provisioned on first use; disabling it stops this endpoint with 409 and code LEAD_INTAKE_DISABLED. ```