Endpoints
Every path, and the rules for creating and editing radars, companies, custom sources, groups and newsletters.
Paths are under https://app.trymarketradar.com/api/v1. This page gives the rules; each
endpoint's parameters, bodies and responses, field by field, are in the
API reference, generated from the OpenAPI document.
| Method and path | What it does | Access |
|---|---|---|
GET /radars | The radars | Read |
GET /radars/{id} | One radar | Read |
POST /radars | Create a radar and start its first sweep | Read and write |
PATCH /radars/{id} | Rename it, move it to another folder, or change its query, sources or rates | Read and write |
DELETE /radars/{id} | Delete it and its signals | Read and write |
GET /radars/{id}/signals | Its signals | Read |
GET /radars/{id}/tenders | A tender radar's calls for tender | Read |
GET /radars/{id}/sweeps | Its sweeps | Read |
GET /radars/{id}/rates | An exchange rate radar's rates and interest rates | Read |
GET /radars/{id}/prices | A price radar's prices | Read |
GET /folders | The folders | Read |
POST /folders | Create a folder | Read and write |
PATCH /folders/{id} | Rename a folder | Read and write |
DELETE /folders/{id} | Delete a folder and its radars | Read and write |
GET /groups | The groups of companies and of custom sources | Read |
POST /groups | Create a group | Read and write |
GET /groups/{id} | One group | Read |
PATCH /groups/{id} | Rename a group or change its members | Read and write |
DELETE /groups/{id} | Delete a group and its members | Read and write |
GET /signals | Every radar's signals | Read |
GET /signals/{id} | One signal with its sources | Read |
GET /companies | The companies | Read |
POST /companies | Add a company | Read and write |
PATCH /companies/{id} | Edit a company | Read and write |
DELETE /companies/{id} | Delete a company | Read and write |
GET /custom-sources | The custom sources | Read |
GET /custom-sources/{id} | One custom source | Read |
POST /custom-sources | Add a custom source | Read and write |
PATCH /custom-sources/{id} | Edit a custom source | Read and write |
DELETE /custom-sources/{id} | Delete a custom source | Read and write |
GET /filters | The catalogue and the account's own filters | Read |
GET /newsletters | The newsletters | Read |
GET /newsletters/{id} | One newsletter | Read |
POST /newsletters | Create a newsletter, not live | Read and write |
PATCH /newsletters/{id} | Change its settings (Live aside) | Read and write |
DELETE /newsletters/{id} | Delete it and its issues | Read and write |
GET /newsletters/{id}/issues | Its issues | Read |
GET /newsletters/{id}/issues/{issue_id} | One issue, with what each block carries | Read |
POST /newsletters/{id}/issues/{issue_id}/approve | Approve an issue | Read and write |
POST /newsletters/{id}/issues/{issue_id}/skip | Skip an issue | Read and write |
DELETE /newsletters/{id}/issues/{issue_id}/signals/{signal_id} | Take a signal out of an issue | Read and write |
DELETE /newsletters/{id}/issues/{issue_id}/tenders/{tender_id} | Take a call for tender out of an issue | Read and write |
GET /newsletters/{id}/recipients | Its recipients | Read |
POST /newsletters/{id}/recipients | Add recipients | Read and write |
DELETE /newsletters/{id}/recipients/{recipient_id} | Remove a recipient | Read and write |
GET /newsletters/{id}/template | Its template | Read |
POST /newsletters/{id}/template/blocks | Add a block | Read and write |
PATCH /newsletters/{id}/template/blocks/{block_id} | Change a block | Read and write |
POST /newsletters/{id}/template/blocks/{block_id}/move | Move a block | Read and write |
DELETE /newsletters/{id}/template/blocks/{block_id} | Delete a block | Read and write |
PATCH /newsletters/{id}/template/theme | Change the theme | Read and write |
Radars
GET /radars: limit, cursor. A page of radars, news and
tender radars alike: read each one's type.
GET /radars/{id}: the radar.
Create a radar
POST /radars: creates a radar, as Create radar in the app does, in a
folder, and starts its first sweep over the last seven days. Returns
201 with the radar and the sweep.
{
"name": "Listed peers",
"folder_id": 5,
"query": {
"lines": [
{
"joiner": null,
"chips": [
{ "filter": "mergers_acquisitions" },
{ "filter": "new_deals" },
{ "text": "A competitor launching an AI inspection service" }
]
},
{
"joiner": "AND",
"chips": [{ "company_id": 91 }, { "company_id": 92 }]
},
{ "joiner": "NOT", "chips": [{ "filter": "stock_analyses" }] }
]
},
"custom_source_ids": [12]
}{ "radar": { "id": 38, "name": "Listed peers", "sweeping": true, … }, "sweep": { "id": 911, "status": "queued", … } }Where it searches is optional: sources, ["open_web"] or [], custom_source_ids and
source_group_ids. Without them a radar searches the open web alone; with custom_source_ids
or source_group_ids alone, the open web and those custom sources.
A chip is one of:
| Chip | Means |
|---|---|
{ "filter": "mergers_acquisitions" } | A catalogue filter, by key (GET /filters lists them) |
{ "filter": 301 } | One of the account's own filters, by id |
{ "text": "…" } | A new filter in your own words, 10 to 2,000 characters. It is read when the radar is saved: the AI proposes up to three terms and writes its keeps |
{ "text": "…", "terms": ["…"] } | The same, with your own searches: 1 to 3 terms, taken as given. The AI proposes none and writes keeps from the sentence and the terms together |
{ "company_id": 91 } | A company of the account |
{ "group_id": 3 } | One of the account's company groups: the radar follows it |
The rules, each failing with 422 and the field:
nameis required, 1 to 120 characters.- A radar lives in a folder: send
folder_id, one of the account's folders (GET /folders), orfolder_name, 1 to 80 characters, to make the folder with the radar in the same transaction. One of the two, not both (fieldfolder_id). Another account's folder fails onfolder_id; afolder_namethe account already has, whatever its case, fails with409conflict,fieldfolder_name. No plan limits folders. The same holds for every type of radar. - The first line's
joinerisnull; every other line's isANDorNOT. - A line holds companies and company groups only, or filters only. There is at most one line
of companies, and it is not a
NOTline. - A query holds at most 50 chips, a group counting as its companies and a company counted
once however it is reached (
fieldquery). - A
mutefilter goes on aNOTline. - A term takes 1 to 60 characters and is searched as written, with any double quotes dropped.
The same term twice fails. Each fails on
query, naming the rule. - An own filter saved with four or five terms, before the limit went to three on 6 October
2026, keeps them:
{ "filter": id }is never refused for its number of terms. - A sentence sent without terms that names more than three subjects (four standards, say) fails
on
query, asking to split it into two conditions. - Something must name what is searched: a line of companies, or a
textchip or written filter. Catalogue filters alone (M&A about Finance) name nothing to search. sourcesis optional:open_web,linkedin, both, or[];["open_web"]when left out.linkedinreads the LinkedIn company pages of the radar's companies, so it needs a line of companies (fieldsources).[]reads the radar's custom sources alone.custom_source_idsare the account's custom sources andsource_group_idsits website groups; another account's fails oncustom_source_idsorsource_group_ids. Together they search at most 30 custom sources, a group counting as its custom sources, each once (fieldsources).- A radar keeps at least one source, of the three: it fails on
sourceswith A radar keeps at least one source.
In the app, a subject (a topic) is placed on a line of its own, so that it narrows the
radar instead of reading as one more alternative. The API takes the lines as sent.
Create a tender radar
POST /radars with type tenders: a tender radar,
with countries and words in place of a query. Its first sweep looks at the calls of the
last year and keeps those still open. Returns 201 with the radar and the sweep.
{
"name": "Solar carports, France",
"type": "tenders",
"folder_name": "Public tenders",
"countries": ["FRA"],
"words": [
{ "phrase": "ombrières photovoltaïques" },
{ "phrase": "toiture photovoltaïque" },
{ "phrase": "maintenance", "excluded": true }
]
}The rules, each failing with 422 and the field:
countries: 1 to 10, each an ISO 3166 alpha-3 code of the EU or the EEA (FRA,BEL,DEU, …).words: 1 to 20, each 2 to 120 characters, none twice whatever its case, and at least one notexcluded.excludedisfalsewhen absent.
Create an exchange rate radar
POST /radars with type rates: an exchange rate radar,
with pairs, interest_rates or both in place of a query. It is never swept. Returns 201
with the radar and sweep null.
{
"name": "Caillau Mobility, currencies",
"type": "rates",
"pairs": ["USD", "CNY", "BRL"],
"interest_rates": ["estr", "aaa_3m"],
"folder_id": 7
}pairs: any of the 29 currencies the ECB publishes a reference rate for, each quoted against
the euro (USD for EUR/USD), in the order to show them. A currency the ECB does not publish
(the Saudi riyal, the UAE dirham, the Taiwan dollar) fails with 422, field pairs.
interest_rates: any of estr, ecb_deposit, aaa_3m, aaa_6m, aaa_1y,
borrowing_companies (interest rates); any other code, Euribor
included, fails with 422, field interest_rates. Pairs and interest rates together are 1
to 20; sending neither fails with 422, field pairs.
Create a price radar
POST /radars with type commodity_prices: a price radar,
with commodities in place of a query, in a folder (folder_id or folder_name) as every
radar. It is never swept. Returns 201 with the radar and sweep null.
{
"name": "Mathevon, raw materials",
"type": "commodity_prices",
"folder_id": 7,
"commodities": ["nickel", "cobalt", "brent"]
}commodities: 1 to 20 codes, in the order to show them. An unknown code (tungsten,
hot-rolled or stainless steel, which only price agencies publish) fails with 422, field
commodities.
Edit a radar
PATCH /radars/{id}: any of name, query, sources, custom_source_ids,
source_group_ids, folder_id. A query replaces the whole query, and sources,
custom_source_ids and source_group_ids each the whole list;
one left out stays as it is. folder_id moves the radar into that folder of the account, and
nothing else: its query, its sweeps and its signals stay. Returns the radar, as sweep the
sweep a changed query or changed sources started, or null, and as removed_signals how many
signals a changed query took off the radar (0 otherwise, and on a tender, rate or price
radar).
{ "radar": { "id": 38, … }, "sweep": { "id": 912, "started_by": "edited", … }, "removed_signals": 133 }- A new
namealone is saved, or afolder_idalone; nothing else happens, even on a radar left with nothing to search. - A changed
query, or changed sources, starts a sweep over the last seven days, as in the app, and the response carries it assweep. The weekly sweep keeps its day. If a sweep is already running, it is stopped (its status readscancelled) and the new one replaces it; what it already found stays. - Changed
sourcesstart a sweep the same way. A query that drops the last company of a radar readinglinkedinfails with422,fieldsources. - A changed
queryremoves the signals it no longer matches, in the same transaction: those that matched no filter still on a line that must hold, and those about a company it takes out, directly or with a group, while it still names a company or a group.removed_signalssays how many. An added filter, topic orNOTline removes nothing, nor does taking out a whole line, which widens the query, nor a change to a group's members. A signal a sent issue carries stays; one in an issue on its way leaves the issue. The other signals stay, with the filter and company they matched. - To change a written filter's text, replace its chip with a
textchip. That makes a new filter for this radar; other radars using the old text keep it. The old filter's signals go with it, and the edit's sweep finds the last seven days again with the new text.
A tender radar takes any of name, countries, words, folder_id, and no query or sources. Each list replaces
the old one. A change to either starts a sweep over the last year that keeps the calls still
open, stopping one that runs; the calls already on the radar stay.
A body with the other type's fields fails with 422 and the field, and changes nothing:
countries or words on a news radar, query or sources on a tender radar. So does
POST /radars with countries or words but no type tenders.
An exchange rate radar takes any of name, pairs, interest_rates, folder_id. pairs
replaces all its pairs and interest_rates all its interest rates (the one left out stays), and
nothing is swept. Any other field fails with 422, and so do pairs or interest_rates on a
news or tender radar, or on POST /radars without type rates.
A price radar takes any of name, commodities in the same way: commodities replaces all
of them, nothing is swept, any other field fails with 422, and so do commodities on
another type of radar, or on POST /radars without type commodity_prices.
Delete a radar
DELETE /radars/{id}: 204. Its sweeps, signals and sources are deleted, or a tender
radar's calls. Its companies and filters stay in the account.
A radar's signals and sweeps
GET /radars/{id}/signals: since (a date: signals first reported on or after it),
limit, cursor. Newest first by first_reported_at.
GET /radars/{id}/sweeps: limit, cursor. To follow a sweep you started, read the
radar's sweeping, or the first sweep of this list, every few seconds. A sweep takes a few
minutes.
An exchange rate radar's rates
GET /radars/{id}/rates: since (a date: each rate from it on). An exchange rate radar's
pairs, in its order, each with the ECB's latest euro reference rate, its change in per cent
over a week and three months, and the year's low and high. The ECB publishes once a working
day, around 16:00 Frankfurt time; a rate is how much of the currency one euro buys. Then its
interest_rates, each an interest rate: in per cent, its change
in basis points, and the day it last moved. A news or tender radar's lists are empty.
{
"data": [
{
"currency": "USD",
"pair": "EUR/USD",
"name": "US dollar",
"latest": { "day": "2026-09-29", "rate": 1.1355 },
"change": { "week": -0.94, "three_months": -0.45 },
"year": {
"low": { "day": "2026-09-29", "rate": 1.1355 },
"high": { "day": "2026-03-06", "rate": 1.1974 }
}
}
],
"source": "ECB euro foreign exchange reference rates",
"interest_rates": [
{
"code": "ecb_deposit",
"name": "ECB deposit rate",
"description": "Deposit facility",
"unit": "%",
"cadence": "daily",
"publisher": "ECB",
"credit": "Source: ECB",
"latest": { "day": "2026-09-30", "rate": 2.5 },
"changed_on": "2026-09-16",
"change_bp": { "week": 0, "three_months": 25, "month": null, "year": null },
"year": {
"low": { "day": "2025-10-01", "rate": 2 },
"high": { "day": "2026-09-16", "rate": 2.5 }
}
}
]
}A price radar's prices
GET /radars/{id}/prices: since (a date: each commodity's prices from it on). A price
radar's commodities, in its order, each a price: the latest in its unit,
its publisher, its change over a week and three months when priced daily or a month and a
year when priced monthly, and the year's low and high. Another type of radar's list is empty.
{
"data": [
{
"code": "nickel",
"name": "Nickel",
"unit": "USD/t",
"cadence": "daily",
"publisher": "market data",
"latest": { "day": "2026-09-29", "price": 16540 },
"change": {
"week": -1.2,
"three_months": -6.4,
"month": null,
"year": null
},
"range": {
"low": { "day": "2026-01-13", "price": 14590 },
"high": { "day": "2026-05-04", "price": 18920 }
}
}
]
}A tender radar's calls
GET /radars/{id}/tenders: status (open or closed; both when absent), limit,
cursor. A page of calls for tender, newest found first. A news
radar has none.
GET /api/v1/radars/41/tenders?status=openFolders
GET /folders: the account's folders by name, each with its
radars' ids. Not paged: an account holds at most 20.
{
"data": [
{
"id": 5,
"name": "Competitors",
"radar_ids": [37, 38],
"created_at": "2026-09-30T09:12:40Z"
},
{
"id": 6,
"name": "Public tenders",
"radar_ids": [41],
"created_at": "2026-09-30T09:14:02Z"
}
]
}POST /folders: creates an empty folder, as Create folder in the app does. Returns 201
with the folder.
{ "name": "Caillau Mobility" }{
"id": 8,
"name": "Caillau Mobility",
"radar_ids": [],
"created_at": "2026-10-01T09:20:15Z"
}name: required, 1 to 80 characters once trimmed, unique in the account whatever its case. One too short or too long fails with422,fieldname; a name taken fails with409 conflict,fieldname. No plan limits folders.
Put a radar in it with POST /radars and its id as folder_id, or move one
in with PATCH /radars/{id}.
PATCH /folders/{id}: renames a folder, as Rename in the app does. Returns 200 with the
folder; its radars stay in it.
{ "name": "Caillau Mobility, 2027" }name: required, with the rules ofPOST /folders. Another folder's name, whatever its case, fails with409 conflict,fieldname; the folder's own name in another case is saved.
DELETE /folders/{id}: 204. As in the app, its radars are deleted with it, each as
DELETE /radars/{id} would: their sweeps, signals and sources, or a tender radar's calls, go
too, and each radar leaves the newsletters that used it. Companies, custom sources, groups and
filters stay. To keep a radar, move it to another folder first (PATCH /radars/{id} with
folder_id).
Groups
GET /groups: the account's groups, company groups first, each kind
by name, with their members' ids and the radars that follow them. kind, companies or
websites, lists one kind only. Not paged.
{
"data": [
{
"id": 3,
"name": "French banks",
"kind": "companies",
"member_ids": [91, 92, 93],
"radar_ids": [37],
"created_at": "2026-09-30T10:02:11Z"
},
{
"id": 4,
"name": "Regulators",
"kind": "websites",
"member_ids": [12],
"radar_ids": [37],
"created_at": "2026-09-30T10:04:40Z"
}
]
}POST /groups: returns 201 with the group.
{ "kind": "companies", "name": "French banks", "member_ids": [91, 92, 93] }kind: required,companiesorwebsites.name: required, 1 to 60 characters, unique among the account's groups of its kind whatever its case: a name taken fails with409 conflict,fieldname.member_ids: optional, its companies or its custom sources, askindsays. One that is not the account's fails with422,fieldmember_ids, and no group is made.
No radar follows a new group: name it in a radar's query ({ "group_id": 3 }) or its
source_group_ids.
GET /groups/{id}: the group.
PATCH /groups/{id}: name, member_ids, or both. member_ids replaces the whole list
([] empties the group); a company or custom source taken out stays in the account. A group's
kind never changes: a body that sends one fails with 422. Every radar following the group
searches its members as they are from its next sweep; no sweep starts. A member that would
take a following radar past its limit (50 companies and filters in a query, 30 custom sources)
fails with 422, field member_ids, naming the radar, and nothing changes.
DELETE /groups/{id}: 204. As in the app, its members are deleted with it, each as
DELETE /companies/{id} or DELETE /custom-sources/{id} would, so a member leaves its other
groups too, and every radar that followed the group or searched a member stops searching
them. The signals already found stay. To delete the group alone, PATCH its member_ids to
[] first.
Signals
GET /signals: every radar's signals. since,
radar_id, q (words searched in headlines and summaries, each must match, at most eight), limit,
cursor.
GET /api/v1/signals?since=2026-09-15&q=acquire&limit=20GET /signals/{id}: the signal with its sources.
Companies
GET /companies: limit, cursor. A page of companies.
Add a company
POST /companies: returns 201 with the company.
{
"name": "BNP Paribas",
"website": "bnpparibas.com"
}name: required, 1 to 120 characters.website: required, a domain or a URL, stored as its domain. Unique in the account: a website the account already holds fails with409 conflict,fieldwebsite.linkedin_url: optional, the company's LinkedIn company page, under/company/or/showcase/, in any form (linkedin.com/company/bnp-paribas, with or withouthttps://,www., a country prefix or a sub-page). Stored ashttps://www.linkedin.com/company/bnp-paribas. A person's profile or any other address fails with422; a page another of the account's companies holds fails with409 conflict,fieldlinkedin_url.
Edit a company
PATCH /companies/{id}: any of name, website, linkedin_url (null removes the page;
left out, it stays). Every radar watching the company uses the new details from its next
sweep. No sweep starts. A website another of the account's companies holds fails with 409 conflict.
Delete a company
DELETE /companies/{id}: 204. The company leaves every radar's query (its
radar_ids say which, before you delete). The signals already found about it stay. A radar
left with nothing to search reads searchable: false.
Custom sources
GET /custom-sources: limit, cursor. A page of
custom sources.
GET /custom-sources/{id}: the custom source.
Add a custom source
POST /custom-sources: returns 201 with the custom source.
{ "name": "ACPR", "address": "https://acpr.banque-france.fr/en/news" }name: required, 1 to 120 characters.address: required, a domain or a URL, stored as its domain (acpr.banque-france.fr). Unique in the account: an address the account already holds fails with409 conflict,fieldaddress.
To search it, add its id to a radar's custom_source_ids.
Edit a custom source
PATCH /custom-sources/{id}: any of name, address. Every radar searching it uses the
new details from its next sweep. No sweep starts.
Delete a custom source
DELETE /custom-sources/{id}: 204. Every radar that searched it stops (its radar_ids
say which, before you delete); a radar for which it was the last source searches the open web
again. The signals already found on it stay. No sweep starts.
Filters
GET /filters: section (what_happened, subject, leave_out) narrows the
catalogue. Not paged.
{
"catalogue": [
{
"section": "what_happened",
"category": { "key": "deals_finance", "label": "Deals and finance" },
"filters": [
{
"id": 212,
"key": "mergers_acquisitions",
"kind": "event",
"label": "M&A",
"definition": "…"
},
{
"id": null,
"key": "funding_events",
"kind": "event",
"label": "Funding Events",
"definition": "…"
}
]
}
],
"own": [
{
"id": 301,
"key": null,
"kind": "written",
"label": "A competitor launching or…",
"definition": "A competitor launching or buying an AI inspection service",
"terms": ["AI inspection", "inspection service"],
"keeps": "Articles on a watched company launching or buying an AI inspection service"
}
]
}Newsletters
Newsletters are the account's emails of what its radars found (the app's Newsletters). A newsletter has settings, a template and recipients; each week or month it assembles an issue, which a reviewer may approve or skip before it goes out. Live, which sends real email to real recipients, is turned on and off in the app only: the API and the MCP server read it.
GET /newsletters: limit, cursor. A page of newsletters, newest first.
GET /newsletters/{id}: the newsletter.
Create a newsletter
POST /newsletters: returns 201 with the newsletter, not live, with a header and a
footer and the account's last saved theme, as Create newsletter in the app.
{ "name": "Weekly Market Radar", "time_zone": "Europe/Paris" }name: required, 1 to 80 characters, unique in the account (409 conflict,fieldname).time_zone: optional, an IANA zone;Europe/Parisby default. An unknown zone fails with422ontime_zone.
Change a newsletter's settings
PATCH /newsletters/{id}: any of the fields below; what is not sent stays. All or
nothing: a request that fails a rule changes nothing.
{
"schedule": { "cadence": "weekly", "weekday": 1, "time": "09:00" },
"subject_pattern": "issue_date",
"review": true,
"reviewer": "aiko@acme.com",
"hold_back": false
}name: 1 to 80 characters, unique in the account.schedule: any ofcadence(weekly;monthlyis refused for now),weekday(ISO, 1 is Monday),time(HH:MM, on the hour or the half hour) andtime_zone.subject_pattern:issue(Weekly Market Radar, issue 15),date(…, 5 October) orissue_date(both).review: each issue goes to the reviewer an hour before it is sent. It needs a reviewer, in the same request or already set; without one it fails with422onreviewer.reviewer: a member of the workspace, by email;nullfor none, which fails while review is on. An address that is not a member's fails with422.hold_back: an issue not approved in time is held back; otherwise it is sent.livefails with422onlive: turn it on or off in the app.
Delete a newsletter
DELETE /newsletters/{id}: 204. Its issues, their web versions and its recipients'
subscriptions go, and no more issues are sent. Its radars and their signals stay.
Issues
GET /newsletters/{id}/issues: limit, cursor. A page of issues, newest first by
send: the one on its way comes first once it is assembled.
GET /newsletters/{id}/issues/{issue_id}: the issue with its blocks: each radar, rates
and prices block in order, with its signals, calls for tender, rates or prices, as readers get
them.
POST /newsletters/{id}/issues/{issue_id}/approve: the issue awaiting review goes out at
its send time. Returns the issue. Approving one already approved, or on its way out, changes
nothing and answers the same, so a retry is safe; approving one skipped or held back fails
with 409 conflict, field status.
POST /newsletters/{id}/issues/{issue_id}/skip: the issue awaiting review, approved or
scheduled is not sent. Skipping one already skipped answers the same; skipping one sent fails
with 409 conflict.
DELETE /newsletters/{id}/issues/{issue_id}/signals/{signal_id} and
DELETE /newsletters/{id}/issues/{issue_id}/tenders/{tender_id}: the signal, or the call
for tender, leaves this issue only, until it starts sending: the radar keeps it, and the block
does not refill. Returns the issue. An issue already sent fails with 409 conflict.
An approval or a skip records who made it: the member an assistant signed in acts for, or the member who made the key while they are still a member, or else the key.
Recipients
GET /newsletters/{id}/recipients: limit, cursor, and status (subscribed,
unsubscribed or bounced). A page of recipients, newest first.
POST /newsletters/{id}/recipients: returns how many were added, already_there,
unsubscribed (not added) and invalid.
{
"recipients": [
{ "email": "aiko@acme.com", "name": "Aiko Tanaka" },
{ "email": "tomas@acme.com" }
]
}New addresses are subscribed. An address already there is left as it is. One that
unsubscribed or bounced is never subscribed again from here: only its owner can, from the
link in the email. At most 2,000 recipients a newsletter (403 account_limit).
DELETE /newsletters/{id}/recipients/{recipient_id}: 204, a subscribed recipient. One
who unsubscribed or bounced stays, so the address is not added again by mistake: it fails with
409 conflict, field status.
The template
GET /newsletters/{id}/template: the template and its template_version.
A template changes one block at a time. Each change applies from the next issue assembled;
an issue already assembled keeps what it was assembled with. Each answers the new
template_version. Two changes made at once both land; a member with the editor open is told
the template changed when they next save.
POST /newsletters/{id}/template/blocks: returns 201 with the block.
{
"type": "radar",
"after": "a1b2c3d4e5",
"radar_ids": [37],
"title": "Competitors",
"max": 5
}type:radar,rates,prices,heading,textordivider. A template has one header and one footer, always there.after: the block to place it after; just above the footer when absent. Nothing goes below the footer.- Any of the type's fields; the rest start as the editor's palette drops them. A field the
type has not fails with
422, naming it. - A radar block's
kindfollows itsradar_ids, and is never sent: news radars or tender radars, never both; a tender block orders by closing date. A rates block takes exchange rate radars only, a prices block commodity price radars only. - A text block also takes
text: plain text, a blank line between paragraphs. - At most 30 blocks.
PATCH /newsletters/{id}/template/blocks/{block_id}: any of the block's fields; what is
not sent stays, and show changes key by key. Its id and type do not change. Returns the
block.
POST /newsletters/{id}/template/blocks/{block_id}/move: { "after": "d4e5f6" }. The
header stays first and the footer last. Returns the template.
DELETE /newsletters/{id}/template/blocks/{block_id}: any block but the header and the
footer. Returns the template.
PATCH /newsletters/{id}/template/theme: any of colors (brand, text, background,
as #RRGGBB), fonts (headings, text: geist, inter, source-serif, libre-caslon,
arial or georgia) and logo: null removes it. A logo is uploaded in the app; an address
that is not one of the account's uploads fails with 422 on logo.