Platform API · Product API · Project setup
Attention
One GET returns everything currently wrong with a project and the organization behind it — an expired card, a connector that stopped syncing, a scheduled campaign the balance will not cover — worst first, with an absolute deadline wherever one exists.
On this page
- The boundary with readiness
- The report
- Nothing is stored
- An item is one occurrence
- type and severity are independent axes
- summary is not your copy
- Switch on key, never on which fields are missing
- Timestamps are absolute, and absent rather than null
- The dismiss field
- The catalogue
- billing
- data
- delivery
- compliance
- Filtering
- truncated
The readiness endpoints answer "what is left to configure". This one answers the other half: what has broken, or is about to, and by when. An expired card, a connector that has stopped bringing contacts in, a campaign scheduled for tomorrow that the balance will not cover.
It is the endpoint behind a health check — the one an integration calls to answer "is everything OK with this account", and the one an assistant reads before claiming that it is.
Everything demanding attention for a project and the organization behind it, worst first.
Any Product API token with access to the project can read it; no extra scope is required. Note the range: the report covers the project and the organization behind it, so a token scoped to one project still sees the organization's expired card. It has to — the card is what stops that project from sending.
The boundary with readiness
The two engines split the work and never report the same fact twice.
| Question | Example | |
|---|---|---|
| Readiness | What is left to configure? | "You have no legal profile yet" |
| Attention | What has broken, or is about to? | "The card on file expired on 1 April" |
The rule that separates them is worth holding on to, because it is the one that decides where a new fact lands: absence is always readiness. For an attention item to exist, the thing has to exist and be broken. There is no expired card without a card, no disconnected connector without a connector, no underfunded campaign without a campaign.
The practical consequence is the one to design your UI around: a project that has just finished signup has a full checklist and no alerts at all. You will never render "your balance is low" next to the step asking the customer to add funds. If you ever see an error on a brand-new project, that is a bug on our side, not an edge case to handle.
The report
curl "https://api.instasent.com/v1/project/$INSTASENT_PROJECT/attention" \
-H "Authorization: Bearer $INSTASENT_TOKEN"{
"entity": {
"items": [
{
"id": "3f2a1b0c9d8e7f65",
"key": "expired-card",
"scope": "org",
"channel": null,
"type": "problem",
"severity": "error",
"tags": ["billing", "entity:organization", "expired-payment-method"],
"dismiss": { "mode": "snooze", "days": 14 },
"entities": [
{ "type": "card", "id": "pm_1a2b3c", "name": "Visa" },
{ "type": "organization", "id": "5f3a9b2c4d1e0f00112233ab", "name": "ACME" }
],
"data": { "brand": "Visa", "last4": "4242", "expiresOn": "2026-03", "default": true },
"startsAt": "2026-04-01T00:00:00Z"
},
{
"id": "91bc44de20fa5c37",
"key": "scheduled-campaign-underfunded",
"scope": "project",
"channel": null,
"type": "problem",
"severity": "error",
"tags": ["billing", "entity:campaign", "insufficient-funds"],
"dismiss": { "mode": "none" },
"entities": [
{ "type": "campaign", "id": "6512ab34cd56ef7890123456", "name": "Black Friday" }
],
"data": { "estimatedCost": 210.0, "shortfall": 60.0, "currency": "EUR" },
"dueAt": "2026-09-06T09:00:00Z"
}
],
"counts": {
"error": 2,
"warning": 1,
"info": 0,
"byKey": { "expired-card": 1, "scheduled-campaign-underfunded": 1, "pending-connection": 1 }
},
"computedAt": "2026-09-05T09:41:12Z",
"truncated": false
}
}items arrives sorted worst first, then soonest deadline, then key. Render it in the order it arrives.
Nothing is stored
There is no inbox behind this. No table of alerts anyone marks as read, no unread count to reconcile: every call recomputes the whole thing from the account's current data.
That has one consequence that should shape how you build on it: an item disappears when the fact resolves, and only then. The expired-card item goes when the customer adds another card. The half-finished connection goes when the data source is created. Nobody has to remember to turn anything off, and nothing can be left behind pointing at a problem that no longer exists.
It also means the inverse is guaranteed, which is the part that matters if an assistant is reading this: if an item is in the response, it is true right now. A stored inbox drifts from reality and eventually lies — it holds "card expired" from three weeks ago, the customer replaced the card, and someone opens a conversation about a problem that does not exist. That cannot happen here.
computedAt travels for exactly that reason. It is always "now" at assembly time, but a client that fetches once and renders for the next ten minutes is holding an answer that is ten minutes old and needs to know it.
An item is one occurrence
One item is one occurrence of one rule, not one rule with a list of rows. Two connectors that both need reconnecting are two items sharing a key. If you want them rendered as one block, group by key yourself.
idstringContent fingerprint of this occurrence — stable across polls while it is the same problem, and defined per key. An expired card is identified by brand, last4 and expiry rather than by the card's id, so re-adding the same card stays dismissed while a different expired card resurfaces. Nothing volatile goes into it, so it is safe to use as a React key or as the handle for a stored dismissal.
keystringStable rule identifier, and the discriminator for data. The API ships no copy: the label and its translation are yours.
Render an unknown key rather than dropping it — the backend can ship a new one ahead of its consumers, and a silently discarded item is a problem the customer never hears about.
scopestringorg, project or channel. An org item affects every project of the organization, so the same expired card shows up whichever project you asked about.
channelstring | nullsms or rcs, set only on a channel-scoped item; null otherwise.
typestringproblem, live or procedure — what the item is, and the axis is who acts: problem waits on the customer, live is the platform working right now (sending, importing) and moves in seconds, procedure is a third party reviewing (a carrier, a regulator) and moves in days or weeks. The vocabulary also holds recommendation, which only ever appears on recommendations.
The vocabulary holds a third value, recommendation, which never appears on this endpoint: the catalogue is partitioned by type and recommendations are served by their own report. Narrow on the two you get here, but do not assert the enum has only two members. See Recommendations.
severitystringerror, warning or info — how to react: act, act soon, just know.
summarystringOne English sentence, at most 64 characters, saying what this key is. Declared once per key and identical on every item of it. Present on every item. See below.
tagsstring[]A closed vocabulary, constant per key, of which exactly one is a domain tag (billing, compliance, delivery, data, audience, setup).
dismissobjectThe rule's hiding policy — { mode }, plus days when the mode is snooze. See below.
entitiesobject[]What the item is about, so you can link straight to it without parsing copy. Each carries type, id and, when there is one, name. name is the entity's own display name — customer-written text, never personal data.
dataobjectRule-specific payload, discriminated by key. Absent when the rule carries none.
statestringrunning or finished. Optional on any item — it is not tied to type. Today it is emitted by pending-review, which is procedure, and by both live items, campaign-sending and datasource-syncing; it spans the two types precisely because it belongs to neither. Authoritative over endsAt.
progressobject{ done, total }. Optional on any item, exactly like state. Today only the two live items emit it, and only while the work is measurable — an item can travel with state and no progress, which the contract allows.
startsAtstringWhen the underlying fact began. Absent when unknown.
endsAtstringWhen it ended, or will end, if it is bounded.
dueAtstringThe deadline after which the consequence lands.
type and severity are independent axes
They answer different questions and you need both. expired-card and expiring-card are both problem, and one is error while the other is warning — the same card, on either side of the date. And info is not a type: it is what pending-review (procedure) and campaign-sending (live) both carry. Neither axis follows from the other, and neither follows from tags: the tags are constant per key precisely so they can be used for filtering and grouping, never as a severity signal in disguise.
Severity is declared once per key and never varies by occurrence. An import that has been stuck for two hours is not a datasource-syncing item turned warning; it is a different key, the day that key exists.
summary is not your copy
Every item carries a summary: one English sentence, capped at 64 characters, declared once per key. It says what the key is — "The saved payment card has expired" — and it is identical on every occurrence of that key.
It exists for the consumers that have no glossary and no localisation layer: a script, an agent, a support tool that has just met a key it does not know. It is what lets an unknown key still be reported to a human as something rather than as a bare identifier.
A dashboard should never render it. Copy is yours, per key, in your user's language, and nothing situational is in there anyway — the numbers, dates and names are in data and entities, typed. A surface that prints summary will show English to a Spanish customer and will show the same sentence for two occurrences that differ in every detail that matters.
Switch on key, never on which fields are missing
data is discriminated by key. Narrow on the key first and the payload shape narrows with it. Inferring the payload from which fields came back absent breaks the moment a rule gains a field.
Timestamps are absolute, and absent rather than null
Every timestamp is absolute UTC, and omitted rather than sent as null when it is unknown. Nothing relative is ever sent — no "12 days left" — because the payload outlives the instant it was computed: a countdown stops being true as soon as anything stores it or repeats it a few minutes later. Compute the relative form at render time, against the report's computedAt.
The dismiss field
There is no dismiss endpoint, and the backend stores nothing. If your surface offers a way to hide an item, you implement it and you store it — the API never learns it happened.
What the field gives you is the rule's policy, so that every surface makes the same call rather than each inventing one. It is constant per key.
mode | What you may offer |
|---|---|
none | Nothing. Hiding it would only delay the consequence — an overdue invoice, a blocked API. |
dismiss | Hide with no expiry. Bring it back when the content changes, which is what id tracks. |
snooze | Hide for days, then bring it back even if nothing changed. For facts that worsen on their own while their fingerprint stays identical — an expired card. |
A snooze is a floor, not a ceiling: if the content changes inside the window the id changes, and an item you keyed on id reappears early on its own. Since the expiry is computed by whoever stores the dismissal, changing days here is not retroactive over what you already stored.
The catalogue
Sixteen keys, grouped by the domain tag they carry. The condition is what makes each one appear.
billing
key | Appears when |
|---|---|
expired-card | A stored card's expiry date has passed. |
expiring-card | The same card, 30 days before. |
low-balance | Below the organization's configured threshold, and only if money ever entered the account. Zero on a brand-new account is not low balance, it is an unfunded account — that is readiness. On postpaid it looks at consumed allowance instead. |
subscription-past-due | A paid subscription's charge failed and the subscription is not frozen. |
unpaid-invoice | A charge is outstanding with an invoice, due date still ahead. |
overdue-invoice | The same, past its due date. An invoice with no due date is never marked overdue. |
auto-charge-disabled | Prepaid only, only when the project has active automations or flows, and only when the organization is actually able to turn it on. |
scheduled-campaign-underfunded | The cost of the scheduled campaigns exceeds available balance. One item per campaign that is not covered, carrying its send time as dueAt. |
campaign-unpaid | A campaign went unpaid during its send. Shown for 30 days. |
data
key | Appears when |
|---|---|
pending-connection | The customer completed an integration's authorization and never created the data source. The abandoned authorization is the thing that exists. |
datasource-reconnect | The data source exists and its access has dropped or expired, so it is bringing nothing in. |
stale-datasource | Seven days without bringing data in, and only if it ever synced. One that never synced is unfinished setup, not a fault. |
delivery
key | Appears when |
|---|---|
api-rate-limit-blocked | The account carries a manually set negative limit, which rejects every call. Never appears on a normal account: if you see it, somebody set it. |
compliance
key | Appears when |
|---|---|
rejected-registration | A carrier or a regulator turned a registration down. Shown for 30 days, then the fact returns to readiness as a country still to register. |
pending-review | A registration was submitted and is waiting on a carrier or a regulator. The catalogue's only informative entry — nothing is broken and there is nothing to do. |
regulation-deadline | Only in the 30 days before the date, and only for countries the customer can send to today. It says "on the 15th you stop being able to send to Spain", which is not the same as "you have not registered Spain" — that is readiness. |
Filtering
Six optional query parameters. All the comma-separated ones are OR within a parameter and AND across parameters.
severitystringComma-separated. Keeps only items of these severities. error,warning
scopestringComma-separated: org, project, channel.
channelstringsms or rcs. Channel is deliberately not a tag value, because it varies per occurrence — it has its own parameter instead.
tagsstringComma-separated. Keeps items carrying all of them.
notTagsstringComma-separated. Drops items carrying any of them. Applied after tags.
countrystringISO 3166-1 alpha-2. Country, like channel, is not a tag value.
Two things are worth knowing before you build on this.
Filtering is free, and it does not save you a call. The list is assembled first and filtered afterwards, so a filtered request costs exactly what an unfiltered one costs. Filter for the sake of your own rendering, not to make the endpoint cheaper.
counts is always computed over the whole list, never over the filtered one. A surface that asked for one tag still learns there are errors elsewhere, without paying for a second call to find out.
truncated
truncated: true means the engine hit its per-report ceiling and the list you got is not everything. Treat it as "at least this much is wrong", and never render a total from items.length when it is set — counts is the number to trust.