API

One REST surface, generated from the schema

The database tables, the endpoints, this document and every page of the application come from one set of entity definitions. The useful consequence for you is that there are no endpoints that exist for one object and not another: whatever you learn about contacts is true of templates, sends and suppressions.

Before the first call

Base URL

https://crm-merge.mycompanylist.com/api/v1

Authentication

Send an API key as the X-API-Key header. A browser that has signed in carries a session cookie instead, and either is accepted. A request with neither is refused with 401 and a message telling you which two things it would have taken.

The plan catalogue lists API access against Pro and Scale -- see pricing. Keys are issued by the operator; there is no screen in the account that creates one, so ask for yours rather than going looking.

A first request

curl -H "X-API-Key: $EMAIL_APP_KEY" \
  "https://crm-merge.mycompanylist.com/api/v1/contacts?limit=5&sort=-created_at"

Conventions that hold everywhere

Listing

Every collection accepts limit, offset, page (a 1-based alternative to offset), sort (a comma-separated list of fields, a leading - for descending), q (substring search across the fields the schema marks searchable), fields (a comma-separated column subset), expand (reference fields to inline), and include_deleted.

Any other query parameter is a filter on the field of that name, which is why there is no separate filter syntax to learn: ?email_status=active&opted_out=false means what it looks like. Repeat a parameter to pass several values.

The page envelope

{
  "items":    [ ... ],
  "total":    1842,
  "limit":    50,
  "offset":   0,
  "has_more": true
}

count=false skips the counting query on a large table and returns total: null. has_more is still correct.

Anything that is not CRUD is an action

Operations that are not create, read, update or delete are declared on the entity and reached the same way everywhere: POST /api/v1/{entity}/{id}/actions/{name} for one record, POST /api/v1/{entity}/actions/{name} for the collection, and POST /api/v1/{entity}/bulk/{operation} for many at once. A record's related collections are GET /api/v1/{entity}/{id}/{relation}.

Errors say which kind of wrong it was

StatusWhat it means
400The value was not usable.
401No API key and no session.
403Signed in, and not allowed to do this.
404No such record -- or no such entity, which is also the answer when an entity exists and is not yours to see. A 403 there would confirm it exists.
405The entity is read-only through the API.
409It conflicts with something already stored.
422Validation. The body carries detail: [{loc, msg}], one entry per field.
502A relay or CRM refused us, and its own message is passed through rather than hidden behind a 500.

Secrets go in and never out

Credential fields accept a value and never return one, not even truncated or masked into something that looks like the original. Read a connector back and the credential is simply absent. Set one through the action the schema declares for it.

Reference

This is a curated document, not the whole API. Entities are published by name rather than excluded by name, so an entity added to the schema -- including one you add yourself at run time -- is not published here until somebody decides it should be. Your own account's full document is at /api/v1/meta/openapi.json once you are signed in.