Skip to main content

REST API

Everything in the CRM can be read and changed over WordPress's REST API, under the namespace fw-crm/v1 (for example https://example.com/wp-json/fw-crm/v1/contacts).

Authentication and permissions​

Use any WordPress REST authentication: application passwords (Users → Profile → Application Passwords) for scripts and other services, or the logged-in cookie with a nonce from inside WordPress.

The API follows the same rules as the screens:

  • The user needs CRM access (the CRM Manager or Sales Rep role, or an administrator).
  • A sales rep only reaches the records they own; lists are filtered to theirs, other records answer 403 fw_crm_forbidden, and owner fields they send are ignored.
  • Manager-only actions (custom fields, segments, pipelines, products, deleting for good, the audit log) answer 403 for reps.
  • Lists send X-WP-Total and X-WP-TotalPages headers; use per_page (up to 100) and page.
  • Errors are WordPress REST errors: 400 bad input, 403 not allowed, 404 not found.

Contacts​

Method and routeDoes
GET /contactsList. Filters: search, stage, status, owner, company, list, tag, segment, trashed (exclude, only, include), orderby, order
POST /contactsCreate. Any contact field, plus company_id, fields (custom fields), lists and tags (slugs). Never subscribes anyone
GET /contacts/{id}One contact, with lists, per-list status, tags and custom fields
PATCH /contacts/{id}Update
DELETE /contacts/{id}Move to the bin; ?force=true deletes for good (managers)
POST /contacts/{id}/restoreOut of the bin
POST /contacts/{id}/merge{ with, prefer }: merge another contact into this one
GET /contacts/duplicatesGroups of likely duplicates
GET /contacts/{id}/timeline · POSTThe timeline; post { note } to add a note
GET /contacts/{id}/consentThe consent log
POST /contacts/{id}/companies · DELETE /contacts/{id}/companies/{cid}Link or unlink a company
POST /contacts/{id}/email{ subject, body, cc?, deal_id? }: send a one-to-one email (502 if it could not be sent)
GET /contacts/{id}/files · POST · DELETE /contacts/{id}/files/{aid}Files: post { attachment_id } or a multipart file

POST /subscribers ({ email, name, list, tags }) is the way to subscribe someone, recording their consent; creating or updating a contact never changes their email status.

Companies​

GET /companies, POST /companies, GET|PATCH|DELETE /companies/{id}, POST /companies/{id}/restore, GET /companies/{id}/contacts, GET|POST /companies/{id}/timeline, and files under /companies/{id}/files.

Custom fields, segments, audit​

RouteDoes
GET /fields?object=contact · POST /fields · DELETE /fields/{object}/{key}Field types and definitions (managers write)
GET /stagesLifecycle stages
GET /segments · POST · `GETPATCH
GET /segments/{id}/contactsThe contacts in a segment
POST /segments/preview{ rules } → { count, describe }
GET /segments/fieldsWhat rules can use
GET /auditThe audit log (managers)

Deals and pipelines​

Available while the sales pipeline module is on.

RouteDoes
GET /pipelines · POSTPipelines with their stages
GET /pipelines/{id} · PATCH · DELETEOne pipeline with totals per stage. A PATCH without stages keeps them; 409 when deleting one that has deals
GET /dealsFilters: pipeline, stage, status (open, won, lost), owner, contact, company, search, trashed, closing_before, orderby, order
POST /deals · `GETPATCH
POST /deals/{id}/move{ stage_id } or { status: "won" } / { status: "lost", lost_reason }
POST /deals/{id}/restoreOut of the bin
POST /deals/{id}/contacts · DELETE /deals/{id}/contacts/{cid}Add or remove a contact
`GETPOST /deals/{id}/timeline`
GET /deals/{id}/items · POST · `PATCHDELETE /deals/{id}/items/{iid}`
GET /products · POST · `GETPATCH
GET /lost-reasonsThe reasons offered when a deal is lost
GET /reports/salespipeline, owner, from, to → summary, by stage, forecast, won and lost, lost reasons, by owner

Example: move a deal to Won.

curl -X POST https://example.com/wp-json/fw-crm/v1/deals/42/move \
-u "alex:abcd efgh ijkl mnop qrst uvwx" \
-H "Content-Type: application/json" \
-d '{"status":"won"}'

Tasks, notes, calendar​

RouteDoes
GET /tasksstatus (open, done, overdue, today, upcoming), owner, object_type, object_id
POST /tasks{ title, type, object_type, object_id, due_at, duration, location, attendee_ids, owner_id }; add done: true and outcome to log a call that happened
`GETPATCH
POST /tasks/{id}/complete · POST /tasks/{id}/reopenComplete with { outcome, duration }, or reopen
GET /task-typesto-do, call, meeting, email
`PATCHDELETE /notes/{id}`
GET /calendarfrom, to (up to 62 days), owner → events by day

Webhooks​

Managers can create and manage webhooks under /webhooks (Webhooks module). A webhook pushes events out as they happen; the routes above are for reading and changing records.

Modules and routes​

A route that belongs to a module exists only while that module is on: /deals, /pipelines, /products and /reports (Sales pipeline), /calendar (Calendar), /contacts/{id}/email (Email conversations), /audit (Audit log) and /webhooks (Webhooks). A switched-off module's routes answer 404 rest_no_route. Contacts, companies, fields, segments, tasks, notes and files are always there.

The inbound email address​

POST /inbound/{token} is public on purpose: the token is the secret. Email services post incoming email to it, either a raw message (in mime, body-mime, email or raw, or as the body with content type message/rfc822) or parsed fields (from/sender, to/recipient, subject, text/body-plain/ stripped-text, html, Message-Id, In-Reply-To). It answers 200 with what it did (received, sent, unknown, duplicate). See Email and replies.