Skip to main content
Changes to the REST API that can affect an integration. Each entry says what changed and whether you need to do anything.
Page sizes, validated queries and fixes

A limit above the maximum is treated as the maximum

A list request whose limit is above its maximum now gets the maximum instead of 400: 100 on every list, 200 for sending mailboxes and 25 for enrollment runs. GET /v1/call-log, GET /v1/scored-calls and the contact-list pages had no maximum before and now return at most 100 per page. Every page reports the limit it used; the call-log page without filters, the contact-list and follow-up pages and the snoozed-contact list gained the field. A limit that is not a positive number still fails with 400. What to do: if you ask for more than 100, compare the limit in the response with what you sent and keep paging.

Call-log and scored-call queries are validated

GET /v1/call-log and GET /v1/scored-calls now check and convert their query parameters:
  • A list filter takes a single value without [], such as filters[user_ids]=ID. It used to fail with 500.
  • filters[exclude_negative_dispositions]=false leaves the filter off. It used to turn it on.
  • start_date, end_date, min_score and max_score on scored calls work. They used to return an empty page.
  • A value that does not fit its parameter, such as an unknown outcome, a date that does not parse or a score that is not a number, fails with 400. Unknown parameters are still dropped.
What to do: drop any workaround for these, such as filtering scored calls in your own code.

Fixes

  • PUT /v1/snoozed-contacts/{id} accepts snooze_until, so a snooze’s end date can be changed.
  • Creating, editing and pinning a note, and reading or updating one snoozed contact, return the record in the documented shape. They used to wrap it in internal fields.
  • GET /v1/snoozed-contacts returns each record’s real _id, team and contact_list. They used to be regenerated on every request.
  • An ID that is not valid answers 400, not 500, on the contact-list, snoozed-contact and note endpoints and in analytics user_ids.
  • Phone lookup callbacks no longer include the contact’s email addresses. Use an email lookup for those.
Reference corrections

Errors the API could already return

The Errors page now covers every status the API can answer with, not only the common ones: 413 for a body over the size limit (10 MB on /v1/contact-lists and /v2/sequencer, 1 MB elsewhere), 422 when the Sequencer’s rules refuse a request, 502 and 504 when a service behind the API cannot be reached or does not answer within 55 seconds, and 405 on /v2/sequencer during planned maintenance. It also says which 403s are about the acting user_id rather than the key, and the order the checks run in. Nothing in the API changed. What to do: treat 413 and 422 as not retryable, and 502 and 504 like a 5xx.

Pagination envelopes and IDs

The API Reference now lists the five pagination envelopes the v1 and v2 lists use, and the IDs that are not 24-character hexadecimal strings: disposition and sending-mailbox IDs are integers, CRM sequence IDs are the CRM’s own, and enrollment-run IDs start with enroll_. Nothing in the API changed.

Webhook delivery

Webhooks are now documented in one place, with the events, the payload shape and the delivery rules. Two facts were missing before: a CALL_LOGGED subscription is sent only for calls whose disposition is ticked on it, and the dashboard does not display the signing secret, so x-salesfinity-signature cannot be checked yet. Keys and webhooks are created under Settings → Connections → API & Webhooks.

The OpenAPI description

The OpenAPI description now matches what the API sends, with no operation, parameter or field removed:
  • Every successful response documents the X-Request-Id and X-RateLimit-* headers, and every error response documents X-Request-Id.
  • request_id is a required field of the error body, and every error example carries it.
  • Every /v2/sequencer operation documents 502 and 504.
  • Sequencer events list the fields they already returned: team, sequence_state, task, user, external_id, created_at and updated_at.
  • Request fields carry the length limits the API already enforced, and descriptions state the defaults, units and conditions that were missing, such as primary_reply_pause_duration being in seconds and a schedule needing all five fields.
Nothing in the API changed. What to do: regenerate any client built from the description.

Known issues now documented

Some endpoints do not behave as documented before, and their reference pages now say so with a workaround: a snoozed contact’s snooze_until cannot be changed, the date and score filters on GET /v1/scored-calls return no results, and a few Sequencer responses expand fields that the schema types as IDs. The contact-list, snoozed-contact and note endpoints answer 500, not 400, for an ID that is not 24 hexadecimal characters, and so do the analytics endpoints for such an ID in user_ids. What to do: read the warning on the reference page of any of these endpoints you call, and check IDs before sending them.
Rate limits, safe retries and request IDs

Rate limits for every endpoint

Every endpoint is now rate limited per team, and all of a team’s API keys share the limits. Requests are counted in fixed one-minute windows that start on the minute:Every response to an authenticated request carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 carries Retry-After, the seconds to wait. Before this change only /v2/sequencer/* was limited, at 120 requests per minute. We give notice before lowering any limit. What to do: wait for Retry-After on a 429. See Rate limits.

Idempotency-Key on every Sequencer POST

Every POST /v2/sequencer/* endpoint except the three /preview endpoints now accepts an Idempotency-Key header. A retry with the same key replays the original response; the same key with a different body or path, or while the first request is still running, returns 409. Keys are kept for 24 hours per team. POST endpoints outside the Sequencer are still not idempotent. See Retrying writes safely.

Internal fields removed from Sequencer settings

GET /v2/sequencer/settings no longer returns the three internal fields about the team’s sending account: its account ID and its two provisioning timestamps. What to do: stop reading them if you do. To find out why a sequence with email steps will not enable, read the team’s mailboxes with GET /v2/sequencer/email-accounts: enabling needs at least one mailbox the sequence sends from with is_connected and can_send true and is_paused false.

Request IDs and one error body for every error

Every response carries an X-Request-Id header. Send your own to trace a request, or let us generate one. Every error body now has the same four fields, statusCode, error, message and request_id, including errors raised while reading the request, such as malformed JSON or an oversized body. Status codes and messages are unchanged. What to do: log request_id and include it when you contact support. See Errors.

Webhook event IDs

Every webhook delivery carries an x-salesfinity-event-id header that stays the same across retries of the same event. A delivery that times out, cannot connect, or gets a 429 or a 5xx is attempted up to 3 times in total. The payload is unchanged. What to do: skip events whose ID you have already processed. See Webhooks.

Webhook URLs must be public

Creating a webhook, or changing its URL, is refused unless the URL is a public http or https address: not localhost, and not a private, loopback or link-local IP address. Existing webhooks keep working until their URL is changed. What to do: point webhooks at a publicly reachable address.

Rate-limit and request-ID headers readable in browsers

Responses now list X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After and X-Request-Id in Access-Control-Expose-Headers, so browser code can read them. Nothing changes for server-side clients.

Enrichment callback_url must be public

POST /v1/api/enrichment/email and POST /v1/api/enrichment/phone now reject, with 400, a callback_url that is not a public http or https URL: localhost, and private, loopback or link-local IP addresses. Requests already accepted are unaffected. What to do: point callbacks at a publicly reachable address.

Reference corrections

GET /v1/follow-up is now documented with the shape it has always returned: the page position is under pagination (total, page, next), not at the top level.

Next steps

Handle API errors

Status codes, error bodies, and what to retry.

Stay under the rate limits

Read the limit headers and back off before you hit a 429.