Download OpenAPI specification:
The RLM v1 API lets an external system read lead data out of a Retail Lead Management pipeline, and create new leads in it.
This is the long-standing public API. It is stable and will not be removed, but it is limited: you can list leads, fetch one lead, and read your organization and pipeline configuration. There is no filtering beyond lead status and a created/updated date, and a list request returns at most 500 leads per page.
If you need richer querying - per-field filters, sorting, pagination, or access to tasks, companies, contacts, journals, attachments and samples - use the v3 read-only API instead. v3 requires access to be enabled on your account; contact RLM support to request it.
This page is generated from an OpenAPI 3.0.3 document, served alongside it:
| Format | URL |
|---|---|
| JSON | /docs/rlm-v1.json |
| YAML | /docs/rlm-v1.yaml |
Point a client generator, a request validator, or a coding assistant at either one rather than at this page - the two files are the same document this page was built from, and the JSON is a straight parse of the YAML.
Two conventions in them are worth knowing, because they are the difference between "these are the only values" and "these are the values we know":
enum means the API constrains the value. On a response field, it is
the complete set of values the API will ever send.x-recognised-values means the value set is known and fixed but not
enforced - RLM understands these values and stores anything else as sent.
LeadCreate.state is the one that matters. A validator must not treat it
as an enum, because a request outside the list is still accepted.Values that are configured per pipeline - lead sources, product lines,
stage names - cannot appear as either. Read those from
GET /api/v1/pipeline/{apiKey} at runtime; each field that works this way
says so and names where in that response to look.
Every endpoint on this page is prefixed /api/v1/, with one exception.
Read the path printed on each operation as the complete path: nothing is
added to it for you.
| Exception | Path | Why |
|---|---|---|
| Create a lead | POST /api/{apiKey}/leads |
Predates the versioned API. Left where it is because moving it would break every integration posting to it. |
So a read is https://app.retailleadmanagement.com/api/v1/organization,
while creating a lead is
https://app.retailleadmanagement.com/api/0123.../leads - no v1.
That one endpoint differs in more than its path: it takes no authentication
headers, and its {apiKey} is the pipeline key rather than an
organization key. Both differences are covered below and on the operation
itself.
Requests to the four read endpoints under /api/v1/ must send two
headers:
| Header | Value |
|---|---|
X-Api-Key |
Your organization API key |
X-User-Email |
The email address of an RLM user in that organization |
POST /api/{apiKey}/leads - the unprefixed endpoint from the table above -
is the exception. It takes no headers at all: the pipeline API key in
its URL is the only credential it reads, and a request that omits both
headers is accepted and returns 201. Do not hand an organization API key
to a form vendor or any other third party writing leads for you - see that
endpoint for the reasoning.
These are two different keys and mixing them up is the single most common integration mistake:
X-Api-Key header. It
authenticates the request. Ask RLM support for it.{apiKey} segment.
It selects which pipeline you are reading from. An organization can have
several pipelines, each with its own key.X-User-Email determines what you can see. Every lead endpoint is scoped
to the pipeline named by {apiKey} and narrowed to what that user may
view in the RLM web app, so the same request run as two different users can
return different sets.
RLM has six roles. For the lead endpoints they fall into three groups:
| Role | What the lead endpoints return |
|---|---|
| System Admin, Business Admin, Business Manager, Full View | The pipeline's leads, narrowed to the pipelines, divisions and locations that user may see |
| Single View | Only the leads in that pipeline assigned to them |
| Assistant | Only the leads they created that are still pending and unassigned |
Full View is not a manager role. It shares the top row above because it
sees the whole pipeline, but that is breadth of visibility, not seniority.
RLM's manager roles are System Admin, Business Admin and Business Manager;
Full View and Single View are salesperson roles, and their older names in
the database still say so - Sales Person Full View and Sales Person Single View.
The distinction has teeth: deleted on List leads is the one thing that
needs an actual manager, and a Full View user gets an empty list from it
despite seeing every live lead in the pipeline.
Two more consequences worth planning around:
{status} segment entirely. Pending and
unassigned is the whole of what they may see, so closed, all and
deleted all return what open returns.Pick the user whose visibility matches the data set you intend to sync. If a sync returns fewer leads than you expect, check the user's role first, then their pipeline, division and location permissions, before suspecting the request.
The two headers must agree. The user named in X-User-Email must belong
to the organization that X-Api-Key authenticates as. Pairing your own
organization key with someone else's email returns 422, exactly like an
unrecognised key - v1 does not distinguish "wrong credentials" from "not
allowed".
Pipeline visibility is a separate matter and is not an authentication check: a user who cannot see the pipeline still authenticates successfully and simply gets an empty result set. If a request suddenly returns no leads rather than an error, check what that user can see in the web app before suspecting your credentials.
Successful reads on this API return 201 Created, not 200 OK. This is
true for the GET endpoints as well. It is a long-standing quirk of the v1
implementation and changing it now would break existing integrations, so it
is documented rather than fixed. Treat any 2xx as success rather than
checking for 200.
Almost every failure - unknown organization key, unknown user email, wrong
pipeline key, a user who may not see the pipeline - returns 422 Unprocessable Entity with a {"result": "Bad Request"} body. v1 does not
distinguish authentication failures from authorization failures, and never
returns 401 or 403.
There is one exception: GET /api/v1/pipeline/{apiKey}/lead/{leadId}
answers 404 when the lead id does not exist in that pipeline, and a
client that handles only 2xx and 422 will fall through on it. Treat it
as "no lead was returned".
That 404 body is an HTML page, not JSON - Content-Type: text/html,
roughly 15 KB of markup. Calling JSON.parse on it raises. Do not assume
a non-2xx response parses as JSON: branch on the status code first, and
parse the body only when the response declares application/json. The same
is true of any unexpected 500.
Every endpoint on this page - the /api/v1/* reads and
POST /api/{apiKey}/leads alike - is limited to 20 requests per
second, measured per source IP over a 5 second window. The /api/v1/*
reads are measured per set of credentials as well, at the same rate, so
spreading a job across several machines does not raise its allowance. It is
one shared budget, not 20 per endpoint: a busy lead feed and a polling sync
running from the same IP spend the same allowance. Exceeding it returns
429 Too Many Requests with a Retry-After header.
Returns every lead in the pipeline matching {status} that the user in
X-User-Email is allowed to see, most recently updated first.
A response carries at most 500 leads. This is a page, not a truncation: send no paging parameters and you get the first 500, exactly as if you had asked for page 1. A different limit may be configured for your account.
⚠️ If you previously received more than 500 leads from one call, you
now receive 500 and must follow the pages to get the rest. Nothing
errors, so an integration that ignores the Link header below will
quietly process only the first page. This is the one thing to check
before this change reaches you.
Read the rest with page[number] and page[size]. Asking for a
page[size] above the limit - or below 1 - is refused with 422 and the
standard error envelope rather than quietly given a different number, so
you always know what you asked for. Nothing else about the size of the
result set can produce an error.
Paging is advertised in a Link header (RFC 8288) on every response,
including the 422 and including requests that asked for no page, with
rel="first", rel="prev" and rel="next" as applicable. The
response body is identical whether or not you paged - always a single
data array, never a wrapper - so adding paging to an existing
integration cannot change what it parses. Walk until no rel="next" is
present.
There is no rel="last" and no total count: producing either would cost
a full scan of the matching set on every request.
⚠️ Pages are cut from a list ordered by most recently updated first,
and that ordering moves as leads are updated - so a lead can shift
between pages while you walk them. For an unattended sync, page within a
fixed by_datetime_since window rather than paging the whole history,
or use the v3 API, whose ordering can be pinned to the immutable id.
For routine syncing, prefer open and narrow with the date parameters
below rather than pulling all repeatedly.
Leads with no location or no division are dropped. Restricting this
list to the pipelines, divisions and locations the user may see is an
inner join on all three, so a lead whose location or division is
empty matches nothing and never appears here - even though Lead
documents both as nullable, and even though
GET /api/v1/pipeline/{apiKey}/lead/{leadId} returns that same lead
without complaint. If a lead is visible in the web app but never
reaches your integration, check those two fields first.
To poll for changes, send by_date=updated together with
by_datetime_since set to the timestamp of your last successful sync:
?by_date=updated&by_datetime_since=2026-08-05%2014:30:00
The two date parameters do not use the same clock.
by_datetime_since is interpreted in your organization's configured
time zone; by_date_at is a UTC calendar day and ignores your time
zone entirely. For a Pacific dealer that means every lead taken after
5 p.m. local counts as the following UTC day.
Two more traps worth wiring around before you go live:
by_datetime_since=2026-08-05
is read as noon on that day, so it silently skips the morning.by_datetime_since value that cannot be parsed - or a missing one -
is not an error. It becomes a comparison against nothing, and the
response is 201 with an empty data array. A wrong timestamp
format therefore looks identical to "no new leads", forever. If a
sync goes permanently quiet, re-check the value you are sending.| apiKey required | string Example: 0123456789abcdef0123456789abcdef The pipeline API key, identifying which pipeline to read from or
write to. Ask RLM support for it: no endpoint hands out the key of a
pipeline you do not already have, and neither
It is not a secret kept from the read endpoints, though. A lead created
through |
| status required | string Enum: "open" "closed" "all" "deleted" Example: open Which leads to return.
Any unrecognised value is treated as |
| by_date | string Enum: "created" "updated" Example: by_date=updated Restrict to leads by date. Choose which date to filter on, then
supply exactly one of Omit Sending |
| by_date_at | string <date> Example: by_date_at=2026-08-05 A single calendar day, as The day is a UTC day, not a day in your organization's time
zone: the comparison is made against the stored UTC timestamp. A
Pacific dealer asking for |
| by_datetime_since | string Example: by_datetime_since=2026-08-05 14:30:00 Return leads created or updated at or after this point in time,
read in your organization's configured time zone. Used only when
Parsed leniently, so
|
| page[number] | integer >= 1 Example: page[number]=1 Which page to return, counting from 1. Defaults to 1, so omitting it gives you the first page. A value below 1, or one that is not a number, is treated as absent
and you get page 1. A page past the end returns |
| page[size] | integer [ 1 .. 500 ] Example: page[size]=200 How many leads a page may carry. Defaults to the limit that applies to your account - 500 unless yours is set otherwise. A value above that limit, or below 1, is refused with |
{- "data": [
- {
- "id": 0,
- "status": "Open",
- "name": "string",
- "pipeline": "string",
- "location_name": "string",
- "location": "string",
- "division_name": "string",
- "division": "string",
- "salesperson": "string",
- "stage_name": "string",
- "stage_class": "string",
- "status_color": "red",
- "next_task_type": "string",
- "next_task_regarding": "string",
- "next_task_name": "string",
- "task_start_date": "string",
- "lead_task_start_date": "string",
- "next_task_overdue": true,
- "next_task_start_time": "string",
- "next_task_end_time": "string",
- "samples_out": "Yes",
- "external_lead_source": "string",
- "external_lead_id": "string",
- "external_lead_key": "string",
- "pipeline_api_key": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "closed_at": "2019-08-24T14:15:22Z",
- "sold_at": "2019-08-24T14:15:22Z",
- "lead_created_at": "2019-08-24T14:15:22Z",
- "lead_updated_at": "2019-08-24T14:15:22Z",
- "lead_closed_at": "2019-08-24T14:15:22Z",
- "created_by": "string",
- "updated_by": "string",
- "lead_created_by": "string",
- "lead_updated_by": "string",
- "time_to_sale": "string",
- "time_to_contact": "string",
- "organization_id": 0,
- "pipeline_id": 0,
- "location_id": 0,
- "division_id": 0,
- "salesperson_id": 0,
- "stage_id": 0,
- "company_id": 0,
- "contact_id": 0,
- "open": true,
- "pending": true,
- "unassigned": true,
- "deleted": true
}
]
}Returns one lead by its RLM id, in the same shape as an entry in the list response.
This endpoint does not use the 422 JSON envelope for every failure. It
also returns 404 when the lead id does not exist in this pipeline,
with an HTML body rather than JSON. Branch on the status before parsing.
| apiKey required | string Example: 0123456789abcdef0123456789abcdef The pipeline API key, identifying which pipeline to read from or
write to. Ask RLM support for it: no endpoint hands out the key of a
pipeline you do not already have, and neither
It is not a secret kept from the read endpoints, though. A lead created
through |
| leadId required | integer Example: 1234567 The RLM lead id, as returned in the |
{- "data": {
- "id": 0,
- "status": "Open",
- "name": "string",
- "pipeline": "string",
- "location_name": "string",
- "location": "string",
- "division_name": "string",
- "division": "string",
- "salesperson": "string",
- "stage_name": "string",
- "stage_class": "string",
- "status_color": "red",
- "next_task_type": "string",
- "next_task_regarding": "string",
- "next_task_name": "string",
- "task_start_date": "string",
- "lead_task_start_date": "string",
- "next_task_overdue": true,
- "next_task_start_time": "string",
- "next_task_end_time": "string",
- "samples_out": "Yes",
- "external_lead_source": "string",
- "external_lead_id": "string",
- "external_lead_key": "string",
- "pipeline_api_key": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "closed_at": "2019-08-24T14:15:22Z",
- "sold_at": "2019-08-24T14:15:22Z",
- "lead_created_at": "2019-08-24T14:15:22Z",
- "lead_updated_at": "2019-08-24T14:15:22Z",
- "lead_closed_at": "2019-08-24T14:15:22Z",
- "created_by": "string",
- "updated_by": "string",
- "lead_created_by": "string",
- "lead_updated_by": "string",
- "time_to_sale": "string",
- "time_to_contact": "string",
- "organization_id": 0,
- "pipeline_id": 0,
- "location_id": 0,
- "division_id": 0,
- "salesperson_id": 0,
- "stage_id": 0,
- "company_id": 0,
- "contact_id": 0,
- "open": true,
- "pending": true,
- "unassigned": true,
- "deleted": true
}
}Creates a lead in the pipeline identified by {apiKey}.
Note that this endpoint is not under /api/v1/ - its path is
/api/{apiKey}/leads.
The pipeline API key in the URL is the entire credential. This
operation never reads X-Api-Key or X-User-Email; a request that
sends neither is accepted and returns 201.
That has one important consequence. When a web-form vendor, chat
widget or marketing platform posts leads on your behalf, give them the
pipeline key and nothing else. Do not give a third party your
organization API key for this endpoint - it is not used here, and it
is the credential that reads every lead in your organization through
/api/v1/. A pipeline key only writes into the one pipeline it names.
Before anything else, RLM looks at the top level of the body for lead,
contact, rawRequest or api, in that order. If one is present,
its contents replace the entire payload and every other top-level key
is discarded. This is how form vendors post: a JotForm webhook sends
rawRequest, and lead and contact wrap a lead the same way.
| Key | What it must hold | Anything else |
|---|---|---|
lead, contact, api |
An object of the lead's fields | A string, number or array answers 500 |
rawRequest |
A string of JSON | An object, or a string that is not JSON, answers 500 |
Treat all four names as reserved. {"api": "web"} is not a lead
with an api field - it is a malformed envelope, and it answers 500
rather than the 201 an unrecognised key would get. If you need to send
a field called lead, contact, api or rawRequest, ask RLM support
to configure an api_field_name for it and send that name instead.
If none of the four is present, the body itself is the payload. That is the ordinary case, and everything below describes it.
Post a single lead object. For each key, RLM looks first for a pipeline
field whose api_field_name matches, and failing that for a column on
the lead with that name - which in practice is the field's
column_name. Keys are lower-cased before either lookup.
api_field_name is an optional alias that somebody has to configure
deliberately, and most pipelines have none set on any field, so
column_name - as listed in pipeline_fields[] by
GET /api/v1/pipeline/{apiKey} - is the normal answer to "what do I
call this field?". Use api_field_name when the pipeline configuration
shows one.
Neither is the label you see on a lead in RLM. A field shown as
Lead Source is source_name on the wire, and the two are edited
independently - see PipelineField for how each key maps to the
pipeline's field settings. Give RLM support the display name when
asking which key a field uses; give your own code the column_name.
A key matching neither is appended to the new lead as a note rather
than being rejected, so a misspelled key name fails quietly. The note
line uses the humanized key, so a stray first_name reads
First name: Jane - not first_name: Jane. A note key is written
into the note verbatim, with no prefix.
Some keys reach neither a field nor the note: formid, uniqueid,
handshakekey, controller, action, api_key,
salesperson_assignment, id, location_id, division_id,
location_name, division_name, and any key whose value is blank.
Several of those are acted on elsewhere in the request - the location
and division keys file the lead - but none of them survive as a note,
so do not rely on the note as a full record of what you posted.
The four reserved keys above are not in that list because they never get this far: they are read before the payload is walked, and a malformed one fails the request outright.
The new lead is placed in the pipeline's initial status, and filed under the pipeline's default location and division unless the request supplies its own.
It is attributed to the pipeline's default salesperson, who is
recorded as its creator - but it is not assigned to anyone. A lead
created here comes back with salesperson and salesperson_id null
and unassigned true, and stays that way until someone takes it in the
web app. Do not read salesperson on a fresh lead to decide who owns
it.
An empty or unrecognised payload does not fail. No key is required.
Post an empty body and you get 201 and a blank lead sitting in the
pipeline; post a body whose keys match nothing and you get 201 and a
lead that is only a note. Validate the payload before you send it -
nothing on this side will do it for you, and there is no response you
can check for "that was not a real lead".
The one exception is a malformed envelope: a top-level lead,
contact, api or rawRequest of the wrong type answers 500. That
is the only shape of body this endpoint rejects.
| apiKey required | string Example: 0123456789abcdef0123456789abcdef The pipeline API key, identifying which pipeline to read from or
write to. Ask RLM support for it: no endpoint hands out the key of a
pipeline you do not already have, and neither
It is not a secret kept from the read endpoints, though. A lead created
through |
| location_name | string Dealer store location, looked up by name among the pipeline's locations. An unrecognised name is not an error. The lead is filed under
the pipeline's default location instead, or under the
alphabetically first location if the pipeline has no default, and
the response is still Sending a numeric Both ids are listed in |
| division_name | string Dealer sales division, looked up by name among the pipeline's divisions. An unrecognised name is not an error. The lead is filed under
the pipeline's default division instead, or under the
alphabetically first division if the pipeline has no default, and
the response is still Sending a numeric Both ids are listed in |
| source_name | string Lead source - labelled Lead Source in RLM. Defaults to A |
| channel_name | string A channel within the lead source - labelled Lead Channel in RLM. A Not validated either way - |
| company_name | string |
| first_name | string |
| last_name | string |
| title | string |
string | |
| website | string |
| phone | string |
| mobile_phone | string |
| work_phone | string |
| work_phone_ext | string |
| address1 | string |
| address2 | string |
| city | string |
| state | string State or territory. Send the two-letter code. Unlike
That is 60 values in total. It is the whole of what RLM's own state
pickers offer and the whole of what the name-to-code conversion
below will match; Canadian provinces are not among them, so a
value like Sending a full state name usually does not work. A name such as
Nothing rejects an unrecognised value; it is stored as sent. Read the field back from the created lead if it matters. |
| zip | string |
| deal_name | string |
| how_heard | string How the customer heard about the dealer, and the current standard field for it - every pipeline RLM sets up now gets this one. A
"How Heard" on screen does not reliably mean this field. See
|
| referral_type | string The older how-heard field, and the reason to map these two by
RLM no longer configures Where the field is configured it is a Sending it where it is not configured fails quietly rather than
loudly. |
| referral_source | string |
| products_interested_in | Array of strings Product lines - labelled Products Interested In in RLM. A The example below is illustrative only - the real list is whatever
that pipeline has configured. As with |
| opt_in | boolean Email marketing permission. |
| promo_type | string Which promotion the lead came in under - labelled Promo Type in RLM. A Nothing validates this against the pipeline's configured options -
|
| note | string Initial comments or request detail. |
| property name* additional property | any |
{- "location_name": "string",
- "division_name": "string",
- "source_name": "Website",
- "channel_name": "string",
- "company_name": "string",
- "first_name": "string",
- "last_name": "string",
- "title": "string",
- "email": "string",
- "website": "string",
- "phone": "string",
- "mobile_phone": "string",
- "work_phone": "string",
- "work_phone_ext": "string",
- "address1": "string",
- "address2": "string",
- "city": "string",
- "state": "string",
- "zip": "string",
- "deal_name": "string",
- "how_heard": "string",
- "referral_type": "string",
- "referral_source": "string",
- "products_interested_in": [
- "carpet",
- "hardwood",
- "tile"
], - "opt_in": true,
- "promo_type": "string",
- "note": "string"
}{- "result": "Success",
- "lead_id": 1234567,
- "lead_uuid": "4f0a1b2c-3d4e-5f60-7182-93a4b5c6d7e8",
- "messages": null
}Read how your organization and pipelines are set up - which pipelines, locations and divisions exist, and which field names appear on lead objects. Pipeline API keys are not discoverable here; ask RLM support.
Returns the organization identified by the X-Api-Key header, including
its locations, divisions and pipelines.
Call this first: the pipelines[].id and pipelines[].name values tell
you which pipelines exist and which one you mean.
This response does not contain pipeline API keys. Ask RLM support
for the key of the pipeline you want to read; it is the {apiKey} path
segment on every other endpoint.
{- "result": "Success",
- "id": 0,
- "name": "string",
- "daily_agenda_delivery": "2019-08-24T14:15:22Z",
- "daily_stalled_delivery": "2019-08-24T14:15:22Z",
- "locations": [
- {
- "id": 0,
- "name": "string",
- "location_number": "string"
}
], - "divisions": [
- {
- "id": 0,
- "name": "string"
}
], - "pipelines": [
- {
- "id": 0,
- "name": "string",
- "has_samples": true,
- "sample_fields": [
- "string"
], - "default_division_id": 0,
- "default_location_id": 0,
- "default_salesperson_id": 0
}
]
}Returns the configuration of a single pipeline: its fields, its lead statuses, and the locations and divisions available to it.
This is how you discover field names. A lead object carries the custom
fields configured for its pipeline, so the pipeline_fields[] entries
here tell you which keys to expect on a lead and what type each holds.
| apiKey required | string Example: 0123456789abcdef0123456789abcdef The pipeline API key, identifying which pipeline to read from or
write to. Ask RLM support for it: no endpoint hands out the key of a
pipeline you do not already have, and neither
It is not a secret kept from the read endpoints, though. A lead created
through |
{- "result": "Success",
- "name": "string",
- "api_key": "string",
- "has_samples": true,
- "sample_fields": [
- "string"
], - "default_division_id": 0,
- "default_location_id": 0,
- "default_salesperson_id": 0,
- "pipeline_fields": [
- {
- "id": 0,
- "active": true,
- "required": true,
- "display_name": "string",
- "column_name": "string",
- "api_field_name": "string",
- "field_type": "address",
- "field_options": [
- "string"
], - "helper_text": "string",
- "visiblity": {
- "new": true,
- "show": true,
- "edit": true,
- "listing": true,
- "task_listing": true,
- "sample_listing": true
}, - "column": 0,
- "column_sort": 0
}
], - "pipeline_statuses": [
- {
- "id": 0,
- "name": "string",
- "closed": true,
- "order": 0,
- "field_set": [
- "string"
], - "task_types": [
- "string"
], - "required_task": "string"
}
], - "locations": [
- {
- "id": 0,
- "name": "string",
- "location_number": "string"
}
], - "divisions": [
- {
- "id": 0,
- "name": "string"
}
]
}