Help Center
API Changelog
Notable changes to the Seedly CRM REST API.
Last updated
All notable changes to the Seedly CRM REST API.
2026-09-17 - A contact's calls and texts are one conversation, and a shared workflow webhook URL is refused#
CRM 5.8.11 and 5.8.13.
Changed#
POST /api/v1/conversationswithchannelset tosmsreturns the contact's conversation for calls and texts. A contact's calls and texts are one conversation, so when the contact already has one, the request returns it with"created": false, reopening it if it was closed, instead of starting a second thread beside it. The response shape is unchanged.- A workflow webhook URL that more than one active workflow holds is refused. Since 5.8.13,
POST /webhooks/workflow/{slug}andPOST /webhooks/event/{slug}answer404and start nothing when two or more active workflows with an Inbound Webhook trigger share that URL. Workflows copied from a snapshot used to share one; the 5.8.13 release notes inside your download carry the command that gives each its own.
2026-09-09 - Contacts become writable for bots, one value at a time#
CRM 5.8.7.
Added#
PATCH /api/v1/contacts/{id}- update a contact. Contacts could be listed, fetched, and created over the API, but never changed, so anything an assistant learned in a conversation had nowhere to go. The update accepts standard fields and custom fields both, under the existingcontacts:writescope. Include only the fields you want to change.PUT /api/v1/contacts/{id}/bot-update- the scalar-only companion. For chatbot custom-tool builders that can only substitute single values into a body and therefore cannot send nested JSON: one instruction per call (set_field,set_custom_field,add_tag,remove_tag), tags merged server-side so the bot never constructs the list,add_tagidempotent and case-insensitive.sourceandassignedToare deliberately not settable by a bot.timezoneon the contact. The field existed internally but was never returned by the contacts API, and was silently discarded on create. It is now carried end to end - returned, settable on create and update, and used for scheduling in place of the sub-account default.
Changed#
- The API reference now lists every endpoint the platform serves. A handful of registered routes were missing from the OpenAPI spec; the spec and the reference now cover the full surface.
2026-09-07 - The reply API meets chatbots where they are#
CRM 5.8.5.
Changed#
POST /api/v1/conversations/{id}/messagesnow also accepts a contact id in the path. The message is delivered to that contact's most recently active conversation, preferring one that matches thechannelbody field when given. This is additive, built for template-driven integrators that only know the contact; existing requests addressed by conversation id behave exactly as before.channelbecame optional on send. When absent (or an empty, unsubstituted{{...}}template value), the message is delivered on the target conversation's own channel. WhentoAddressis omitted, it falls back to the contact's phone for SMS or email for email channels.- A reply aimed at a thread a human has taken over is refused (
404) rather than barging in, so a teammate who answers in a conversation is not talked over by a bot's late replies.
2026-09-02 - A read built for assistants, and multi-record customers found#
CRM 5.8.3 and 5.8.4.
Added#
POST /api/v1/booking/appointments/actionable(5.8.4) - the same verified-customer identification as the full lookup, filtered to the appointments the customer can still cancel or move. Cancelled, past, and recurring occurrences are left out, because those are exactly what the write endpoints refuse: an assistant handed a full history has been observed telling the customer the correct upcoming time and then trying to cancel an old cancelled record. When exactly one appointment is actionable, it is handed back on its own indata.appointmentso there is nothing to choose between; when several are,meta.ambiguousistrueand the assistant should ask the customer by the human-readablelabel. Samebooking-appointments:readscope as the full lookup, because it returns strictly less. The original lookup is untouched.
Fixed#
- A customer with a booking could be told they had none (5.8.3).
POST /api/v1/booking/appointmentsanswered "no match" for people whose CRM held more than one contact record sharing a phone number or email - which is normal when a form fill lands beside an imported row. The lookup checked one record per identifier and gave up when they turned out to be different rows; it now finds the record that matches both, and a person with several records gets their appointments returned together. Nothing about identification changes: two matching identifiers are still required and must still both belong to the same record.
2026-09-01 - Calendar scopes split and renamed (breaking), the conversational booking API, and add-on endpoints#
CRM 5.8.0 through 5.8.2. The scope change is breaking for existing keys and is the reason to read this entry before deploying the update.
Changed - breaking for existing keys#
-
calendars:readandcalendars:writeno longer exist. The oldcalendars:readgranted four things at once: the calendar list, the appointment types, the open time slots, and the booked appointments - and that last one returns each customer's name, email address, and phone number with no date range required. So a key handed to an outside company purely to answer "when can you come out?" could also read the customer list. The scopes are now named after what they reach:Old scope New scope What it reaches now calendars:readappointments:readBooked appointments, with customer names, emails, and phone numbers calendars:writeappointments:writeBooking and cancelling appointments; unchanged in what it grants (new) availability:readOpen times, appointment types, and the calendar list; no customer details No old names are kept alive alongside the new ones: a key still holding an old name is refused by the endpoints it used to reach, and the refusal goes to the integration rather than to you. After deploying, go through every key in Settings > Integrations > API Keys: tick
availability:readon anything that checks open times, tickappointments:writeon anything that books or cancels, and leaveappointments:readoff any key that never needed the roster - keeping it off booking keys is the whole point of the split.
Added#
- A key's scopes can be edited in place, without issuing a new key. Every active key in Settings > Integrations > API Keys has an Edit permissions button next to Revoke. Saving replaces the scope list in one step, changes apply immediately, the key value itself never changes (so nothing is re-installed at the other end), and every edit is recorded in the audit log. Do the whole change in one save, so the key is never mid-way between two permission sets.
- Availability can answer a whole week in one request.
GET /api/v1/calendars/availabilitytakes an optionalendDatebeside thedateit already took and returns every open slot across the range, up to 31 days. Each slot now arrives with the calendar's timezone, its local date, a ready-to-readlabelsuch as "Wednesday, April 1, 2026 at 9:00 AM EDT", and the exactstartTime/endTimepair the booking call wants back. The appointment type can be named withappointmentType(name or slug, any capitalisation) instead of its id; an ambiguous name is refused with the candidates listed rather than one being guessed. A range spends one request's worth of the key's rate-limit allowance per day, and a singledatemust now be a real calendar date writtenYYYY-MM-DDand near today -2026-02-31used to roll over and quietly return another day's times. - The conversational booking family,
/api/v1/booking/*.GET /api/v1/booking/service-types(service-types:read) returns the published price list, withhasVariablePricingflagging services whose real price depends on quantity or options.GET /api/v1/booking/estimates(booking-estimates:read) returns the quotes belonging to the one contact a conversation is already with - totals and validity only, no line items and deliberately no title, addressed only by the CRM contact id.POST /api/v1/booking/appointments,/cancel, and/reschedule(booking-appointments:read/booking-appointments:write, 5.8.2) let a booking assistant handle one verified caller's own appointment: the caller proves who they are with the contact id or two matching details (email, E.164 phone, last name), and every wrong combination gets the same404on purpose, so the surface cannot be used to confirm a customer's details one request at a time. A recurring booking is refused and the sub-account's admins are notified, so a person decides. See the REST API reference for the full family. - Add-on endpoints at
/api/v1/ext. An installed add-on can publish endpoints of its own under/api/v1/ext/, using the same API keys, rate limits, and response envelope as the rest of the API.GET /api/v1/extlists exactly which endpoints your install offers, what each does, and which scope it needs - that listing is the documentation for them, since the base product cannot describe an add-on's endpoints in advance. Contributed scopes appear on the create-key form beside the built-in ones, an add-on can never claim a core scope, and a contributed endpoint that fails does so on its own:502/504against that one endpoint, crash detail to your Convex logs, and never anything the add-on wrote in the error body beyond a capped message on the 4xx statuses it chose to return. With no add-ons installed, the list is empty.
2026-08 - Campaigns API, task cancellation, and repaired scopes#
Added#
- Create, send, and schedule email campaigns via API. You can now push a finished HTML email into the CRM and send it (or schedule it) without opening the builder. The campaign sends through the same provider setup, unsubscribe treatment, suppression checks, and audience selection a UI-built campaign uses. A campaign created this way opens in the app as read-only, so the block canvas is not offered - the HTML is yours and reopening it in the block editor would rewrite it.
Cancelledtask state.PATCH /api/v1/tasks/{id}acceptsstatus: "cancelled". Deleting a contact cancels its open tasks rather than marking them completed, sotask.updatedwebhooks fire with thecancelledstatus. Task-created and task-completed webhook events keep their existing shapes; there is no separatetask.cancelledevent.- Sign-in behind a proxy no longer 404s. Deployments that terminate TLS on Cloudflare, Railway, or a custom platform domain used to fail sign-in on the first request after a redeploy. The auth path now trusts
X-Forwarded-Host, so sign-in works on any proxied hostname.
Fixed#
- Scheduled sends reject impossible times. A timestamp in seconds (not milliseconds) used to be accepted and read as a date in 1970, which the CRM treated as overdue and sent immediately while reporting back that it was scheduled. Times more than a year in the past or ahead are now rejected with an error.
- The task spec matches what the CRM stores. Fields that the CRM never stored have been removed from the public spec. If you were persisting them client-side because the docs promised them, drop those columns.
campaigns:writeis selectable on new API keys and gates the campaign create, send, and schedule endpoints above. There is deliberately nocampaigns:readscope - the campaigns REST surface has no GET endpoint, so a grantable scope that authorises nothing would be a promise the API cannot keep.
Deprecated#
- Nothing this release. The 15-event webhook set plus the 11 events added in the prior release remain the current catalog.
Opportunities, Tasks, and Pipelines are documented#
Documentation#
These endpoints shipped earlier and have been live in the product and in the bundled OpenAPI spec, but this reference only described contacts, conversations, and calendars. That was a documentation gap, not a missing feature. The reference now covers them properly.
Opportunities (opportunities:read, opportunities:write)
GET /api/v1/opportunities- list opportunitiesGET /api/v1/opportunities/{id}- get an opportunityPOST /api/v1/opportunities- create an opportunityPATCH /api/v1/opportunities/{id}- update an opportunityPUT /api/v1/opportunities/{id}/stage- move to a different stagePUT /api/v1/opportunities/{id}/status- set statusDELETE /api/v1/opportunities/{id}- soft-delete an opportunity
Tasks (tasks:read, tasks:write)
GET /api/v1/tasks- list tasks, filter bystatus,assignedTo,contactId,dealId,dueBefore,dueAfterGET /api/v1/tasks/{id}- get a taskPOST /api/v1/tasks- create a taskPATCH /api/v1/tasks/{id}- update a taskPUT /api/v1/tasks/{id}/complete- mark a task completedDELETE /api/v1/tasks/{id}- delete a task
Pipelines (opportunities:read)
GET /api/v1/pipelines- list pipelines and their ordered stages
The scope table in the REST API reference has been corrected to list these. Two further scopes exist but grant no access to /api/v1/* and are intentionally left out. Record timestamps (createdAt, updatedAt) are ISO 8601 here as everywhere else; the scheduling fields (dueDate, completedAt, expectedCloseDate, closedAt) are epoch milliseconds. The reference now states which is which.
Still not exposed#
- Invoices. The
invoice.paidwebhook event exists, but there are no invoice REST endpoints.
Replay-protected webhook signature, and two response fixes#
Added#
X-Webhook-Signature-V2on every outbound webhook delivery. It signs<timestamp>.<body>with your existing per-subscription secret, so a receiver can trustX-Webhook-Timestampand reject a replayed delivery outside a freshness window. This is additive:X-Webhook-Signatureis still sent, unchanged, and integrations that verify it keep working. See Webhook Payloads for the verification snippet.
Fixed#
- By-id and sub-resource routes resolve. Routes addressing a single record or a nested resource under it were not being matched and returned a not-found response. They now behave as documented. If you worked around this by listing and filtering client-side, you can go back to addressing the record directly.
meta.totalis the size of the full result set, not the number of records in the current page. If you were treatingtotalas a page count, or computing a total by summing it across pages, correct that when you upgrade.
Expanded Webhook Events#
Added#
The outbound webhook catalog has grown from the original 15 events to 26. The following events are now available to subscribe to, in addition to the launch set:
opportunity.updated,opportunity.deletedappointment.cancelled,appointment.rescheduled,appointment.confirmed,appointment.completed,appointment.no_showtask.created,task.updated,task.completed,task.deleted
Existing subscriptions are unaffected. See the REST API and Webhook Payloads pages for the full list and payload shapes.
2026-03-27 - Agency-Scoped API Keys#
Added#
- Agency-scoped API keys - Create a single API key that operates across all sub-accounts under an agency. Prefix:
sk_agency_live_(production) andsk_agency_test_(sandbox). RequiresX-Sub-Account-Idheader on every request. GET /api/v1/sub-accounts- Discovery endpoint to list available sub-accounts. Only accessible with agency-scoped keys.- New error codes:
MISSING_SUB_ACCOUNT(400),INVALID_SUB_ACCOUNT(403)
2026-03-27 - v1 Launch#
Initial release of the public REST API.
Added#
Authentication:
- API key authentication with
sk_live_(production) andsk_test_(sandbox) prefixes - Scoped permissions:
contacts:read,contacts:write,conversations:read,conversations:write,calendars:read,calendars:write,webhooks:manage - Test mode keys - messages recorded but not sent to providers
Contacts:
GET /api/v1/contacts- list/search contacts, exact email lookup viaemailparamGET /api/v1/contacts/{id}- get contactPOST /api/v1/contacts- create contactPATCH /api/v1/contacts/{id}- update contactDELETE /api/v1/contacts/{id}- soft-delete contactGET /api/v1/contacts/fields- list custom field definitions
Conversations:
GET /api/v1/conversations- list conversations, filter bycontactIdGET /api/v1/conversations/{id}- get conversationPOST /api/v1/conversations- create or find existing conversationGET /api/v1/conversations/{id}/messages- list messages (cursor-based pagination)POST /api/v1/conversations/{id}/messages- send message (all channels)PATCH /api/v1/conversations/{id}- update status/assignment
Calendars:
GET /api/v1/calendars- list active calendarsGET /api/v1/calendars/types- list appointment typesGET /api/v1/calendars/availability- check available slotsGET /api/v1/calendars/appointments- list appointmentsPOST /api/v1/calendars/appointments- book appointmentDELETE /api/v1/calendars/appointments/{id}- cancel appointment
Webhooks:
GET /api/v1/webhooks- list subscriptionsPOST /api/v1/webhooks- create subscriptionPATCH /api/v1/webhooks/{id}- update subscriptionDELETE /api/v1/webhooks/{id}- delete subscriptionPOST /api/v1/webhooks/{id}/regenerate-secret- regenerate signing secret- 15 webhook events:
contact.created,contact.updated,contact.lifecycle_changed,contact.tag_added,opportunity.created,opportunity.stage_changed,opportunity.won,opportunity.lost,message.received,message.sent,message.delivered,message.failed,appointment.booked,form.submitted,invoice.paid
General:
- Per-key rate limiting with separate limits for read and write operations
- ISO 8601 timestamps in all responses
- Usage logging per API key
- Automated cleanup of expired keys and old usage logs
