Skip to content

Join the Seedly owners community →

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/conversations with channel set to sms returns 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} and POST /webhooks/event/{slug} answer 404 and 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 existing contacts:write scope. 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_tag idempotent and case-insensitive. source and assignedTo are deliberately not settable by a bot.
  • timezone on 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}/messages now also accepts a contact id in the path. The message is delivered to that contact's most recently active conversation, preferring one that matches the channel body 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.
  • channel became optional on send. When absent (or an empty, unsubstituted {{...}} template value), the message is delivered on the target conversation's own channel. When toAddress is 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 in data.appointment so there is nothing to choose between; when several are, meta.ambiguous is true and the assistant should ask the customer by the human-readable label. Same booking-appointments:read scope 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/appointments answered "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:read and calendars:write no longer exist. The old calendars:read granted 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 scopeNew scopeWhat 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:read on anything that checks open times, tick appointments:write on anything that books or cancels, and leave appointments:read off 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/availability takes an optional endDate beside the date it 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-read label such as "Wednesday, April 1, 2026 at 9:00 AM EDT", and the exact startTime / endTime pair the booking call wants back. The appointment type can be named with appointmentType (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 single date must now be a real calendar date written YYYY-MM-DD and near today - 2026-02-31 used 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, with hasVariablePricing flagging 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 same 404 on 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/ext lists 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/504 against 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.
  • Cancelled task state. PATCH /api/v1/tasks/{id} accepts status: "cancelled". Deleting a contact cancels its open tasks rather than marking them completed, so task.updated webhooks fire with the cancelled status. Task-created and task-completed webhook events keep their existing shapes; there is no separate task.cancelled event.
  • 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:write is selectable on new API keys and gates the campaign create, send, and schedule endpoints above. There is deliberately no campaigns:read scope - 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 opportunities
  • GET /api/v1/opportunities/{id} - get an opportunity
  • POST /api/v1/opportunities - create an opportunity
  • PATCH /api/v1/opportunities/{id} - update an opportunity
  • PUT /api/v1/opportunities/{id}/stage - move to a different stage
  • PUT /api/v1/opportunities/{id}/status - set status
  • DELETE /api/v1/opportunities/{id} - soft-delete an opportunity

Tasks (tasks:read, tasks:write)

  • GET /api/v1/tasks - list tasks, filter by status, assignedTo, contactId, dealId, dueBefore, dueAfter
  • GET /api/v1/tasks/{id} - get a task
  • POST /api/v1/tasks - create a task
  • PATCH /api/v1/tasks/{id} - update a task
  • PUT /api/v1/tasks/{id}/complete - mark a task completed
  • DELETE /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.paid webhook event exists, but there are no invoice REST endpoints.

Replay-protected webhook signature, and two response fixes#

Added#

  • X-Webhook-Signature-V2 on every outbound webhook delivery. It signs <timestamp>.<body> with your existing per-subscription secret, so a receiver can trust X-Webhook-Timestamp and reject a replayed delivery outside a freshness window. This is additive: X-Webhook-Signature is 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.total is the size of the full result set, not the number of records in the current page. If you were treating total as 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.deleted
  • appointment.cancelled, appointment.rescheduled, appointment.confirmed, appointment.completed, appointment.no_show
  • task.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) and sk_agency_test_ (sandbox). Requires X-Sub-Account-Id header 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) and sk_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 via email param
  • GET /api/v1/contacts/{id} - get contact
  • POST /api/v1/contacts - create contact
  • PATCH /api/v1/contacts/{id} - update contact
  • DELETE /api/v1/contacts/{id} - soft-delete contact
  • GET /api/v1/contacts/fields - list custom field definitions

Conversations:

  • GET /api/v1/conversations - list conversations, filter by contactId
  • GET /api/v1/conversations/{id} - get conversation
  • POST /api/v1/conversations - create or find existing conversation
  • GET /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 calendars
  • GET /api/v1/calendars/types - list appointment types
  • GET /api/v1/calendars/availability - check available slots
  • GET /api/v1/calendars/appointments - list appointments
  • POST /api/v1/calendars/appointments - book appointment
  • DELETE /api/v1/calendars/appointments/{id} - cancel appointment

Webhooks:

  • GET /api/v1/webhooks - list subscriptions
  • POST /api/v1/webhooks - create subscription
  • PATCH /api/v1/webhooks/{id} - update subscription
  • DELETE /api/v1/webhooks/{id} - delete subscription
  • POST /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
Was this page helpful?