Skip to content
API pagesReminders

API documentation

Reminders

Chasing is the part of Syllevo an accountant is most nervous about, because the messages go out in their name, to their clients, without them in the room. These routes are the controls for that: what is scheduled today, who receives it, how often, and how to stop it.

Reminders are sent by the engine on a schedule. There is no route that composes a message, and there is no route that sends arbitrary mail. The only send route here asks for the next scheduled touch to happen now, and it refuses more often than it agrees.

Routes

Method Path Needs
GET /v1/reminders/sending-today Any member
GET /v1/reminders/settings Admin
PUT /v1/reminders/settings Admin
GET /v1/profiles/{profileId}/reminders View
GET /v1/profiles/{profileId}/reminders/history View
POST /v1/profiles/{profileId}/reminders/pause SendReminders
POST /v1/profiles/{profileId}/reminders/resume SendReminders
POST /v1/profiles/{profileId}/reminders/send-now SendReminders
GET /v1/profiles/{profileId}/reminders/settings ManageSettings
PUT /v1/profiles/{profileId}/reminders/settings ManageSettings
DELETE /v1/profiles/{profileId}/reminders/settings ManageSettings
GET /v1/profiles/{profileId}/contacts SendReminders
POST /v1/profiles/{profileId}/contacts SendReminders
PATCH /v1/profiles/{profileId}/contacts/{contactId} SendReminders

The vocabulary

Four words, and everything on this page is built from them.

  • A sequence is one client's chasing for one calendar month. It has a state, a number of touches it is allowed to spend, and a list of the touches spent so far.
  • A touch is one message, scheduled or sent. A sequence with three touches sends at most three messages for that month, no matter how many documents are missing.
  • A contact is an address at the client. Everyone marked primary is addressed on the same message, never one message each.
  • A period is the calendar month, written as ISO 8601 year-month: "2026-08".

One message per touch, listing everything currently missing. That batching rule is the whole reason the product does not make the inbox worse.

GET /v1/reminders/sending-today

Everything scheduled to leave during the firm's local day, grouped by client. This is the screen an accountant checks before they trust the engine with anything.

Field Type Means
localDay date The firm's local calendar day
timeZoneId string The time zone that day was resolved in
dayStartUtc date-time That day's start, in UTC
dayEndUtc date-time That day's end, in UTC
totalTouches integer How many messages are scheduled in total
profiles array One entry per client with anything scheduled

Each entry in profiles carries profileId, profileName, touchCount, and touches.

The day is the organization's local day, resolved the same way the daily send cap resolves it, so the preview and the cap can never disagree about which messages belong to today.

The touch shape

touches appears here, on a sequence, and in history. It is always this:

Field Type Means
id uuid The touch
touchNumber integer Which touch of the sequence this is, starting at 1
scheduledFor date-time When it goes out, or was meant to
sentAt date-time, null Null until it is handed to the mail channel
outcome ReminderTouchOutcome See below
cancelReason ReminderTouchCancelReason Why it was called off, null unless cancelled
recipientContactId uuid Which contact it is addressed to
recipientEmail string or null The address, on the sending-today preview only
recipientName string or null The name, on the sending-today preview only
missingItemCount integer or null How many items it lists
isEscalation boolean True for the final message to the escalation contact
isManual boolean True when somebody asked for it by hand

outcome:

Value Name Means
0 Scheduled Planned, not yet attempted
1 Sent Handed to the mail channel. Terminal
2 Cancelled Called off before going out, with a reason. Terminal
3 Unknown In flight when something crashed, not provably delivered. Terminal

cancelReason:

Value Name Means
0 ListEmptied Everything arrived between scheduling and sending
1 StopConditionMet A stop condition was true at send time
2 WindowClosed The send window had closed, and the touch was moved to the next one

A cancelled touch is usually the good ending. The message content is computed at send time rather than at schedule time, exactly so a reminder for documents that already arrived gets cancelled instead of sent.

WindowClosed explains something that will otherwise look like a bug: two rows can carry the same touchNumber. The abandoned attempt keeps its number and the replacement reuses it. Key on id, not on touchNumber.

recipientEmail and recipientName are filled in on the sending-today preview and nowhere else. Everywhere else a touch identifies its recipient by id only.

GET /v1/profiles/{profileId}/reminders

One client's chasing for one month.

Parameter Type Means
period string The month as "2026-08". Omitted answers the most recent sequence

Omitting period does not mean "this month". It means the sequence that is actually running, which is what a firm looking at a client mid-close wants to see.

Field Type Means
profileId uuid The client
profileName string Its name
period string The month this answer is about
sequence object The sequence, or null when there is not one
escalationEnabled boolean Whether the firm switched escalation on
escalationConfirmed boolean Whether the one-time confirmation was given
escalationContactConfigured boolean Whether this client has an escalation contact at all
escalationRunnable boolean Enabled and confirmed. The only flag that decides

A null sequence is normal. It means nothing has been chased for that month, usually because nothing was missing.

The sequence shape

Field Type Means
id uuid The sequence
profileId uuid The client
period string The month
state ReminderSequenceState See below
pauseReason ReminderPauseReason Why it is held, null unless paused
stopReason ReminderStopReason Why it was stopped, null unless stopped
resumeAt date or null When a pause lifts by itself
maxTouches integer The ceiling for this sequence
touchesConsumed integer How many are spent
touchesRemaining integer How many are left
nextTouchAt date-time, null When the next one goes, null when nothing is scheduled
touches array Every touch, in order
outOfOfficeShiftBusinessDays integer How much shift the sequence is allowed
remainingOutOfOfficeShiftBusinessDays integer How much of that is left
foldsInPriorPeriods boolean Whether older unfinished months are folded into this
closeOutSentAt date-time, null When the courtesy closing message went
createdAt date-time When chasing started
endedAt date-time, null When it ended
lastUpdatedAt date-time, null Last change

state:

Value Name Means
0 Active Chasing
1 Paused Held, with a reason. Not an ending
2 Completed Everything the month was missing arrived
3 Exhausted The touches are spent and documents are still missing
4 Stopped Stopped by something outside the missing list

pauseReason:

Value Name Means
0 AccountantPaused Somebody at the firm paused it
1 ClientReplied The client wrote back, so the engine stepped aside
2 OutOfOfficeShift The client is away past the point the sequence can wait

stopReason:

Value Name Means
0 ProfileOffboarding The client is leaving. There is no month-end to chase for
1 OrganizationSuspended The firm's account is not in good standing
2 HardBounce A previous message to that address bounced permanently

3 exhausted is the state that needs a person. It means the engine spent every touch it was allowed and documents are still missing, which is the whole point of having a maximum: at some point the software stops and a human picks up the phone.

1 paused with reason 1, client replied, is the other one worth surfacing. When a client writes back, chasing stops and the reply is forwarded to the firm. Syllevo does not argue with a client by robot, so nothing resumes until somebody says so.

An ordinary out-of-office does not pause anything. If it states a return date the whole remaining sequence shifts, which is what remainingOutOfOfficeShiftBusinessDays counts down. Pause reason 2 is for the far side of that allowance.

GET /v1/profiles/{profileId}/reminders/history

Every sequence this client has ever had, newest first, one summary each.

Field Type Means
profileId uuid The client
sequences array One summary per month

A summary carries id, period, state, pauseReason, stopReason, touchesConsumed, closeOutSentAt, createdAt, endedAt, and lastTouch, which is one touch object rather than the whole list. Fetch the month itself when you need the full set.

POST /v1/profiles/{profileId}/reminders/pause

Holds the chasing. SendReminders.

Field Type Required Means
resumeAt date or null no Lift the pause automatically on this date

Answers 200 with the sequence. Refused with 409 reminder_sequence_not_active when there is nothing running to hold.

The pause is recorded as the firm's, not the client's. A client asking for quiet is a different reason with a different audit trail, and the engine writes that one itself.

POST /v1/profiles/{profileId}/reminders/resume

Starts it again. SendReminders. No body. Answers 200 with the sequence.

Refused with 409 reminder_sequence_not_paused when there is nothing to pick back up.

POST /v1/profiles/{profileId}/reminders/send-now

Asks for one message outside the schedule. SendReminders. No body.

This is the route that refuses most, and every refusal is the engine protecting the firm's relationship with its client. On success:

Field Type Means
sequenceId uuid The sequence it belongs to
verdict ManualTouchVerdict 0 on success
touchId uuid or null The touch that was created
touchNumber integer or null Which touch it is
scheduledFor date-time, null When it actually goes out
pendingTouchAt date-time, null When the already-pending one goes

Note the name: scheduled, not sent. Even a manual send lands in the next open moment of the recipient's send window. Asking at 22:00 does not write to a client at 22:00.

verdict:

Value Name Means
0 Scheduled Placed at the next open moment
1 SequenceNotChasing The sequence is paused or ended
2 NothingMissing There is no list to send
3 NoUsableContact No address this can go to
4 TouchAlreadyPending One is already waiting

Anything other than 0 comes back as 409 rather than 200, carrying the matching code: reminder_sequence_not_chasing, reminder_nothing_missing, reminder_no_usable_contact, or reminder_touch_already_pending. The last one carries pendingTouchAt alongside the usual error fields, so a screen can say when the scheduled message goes instead of just refusing.

{
  "status": 409,
  "code": "reminder_touch_already_pending",
  "messageKey": "web.errors.reminders.touchAlreadyPending",
  "detail": null,
  "details": null,
  "errors": null,
  "pendingTouchAt": "2026-09-12T09:00:00Z"
}

Settings

The firm sets one schedule. A client can override it. Both use the same body, and the effective values are what the engine reads.

GET and PUT /v1/reminders/settings

The firm's default, inherited by every client without an override. Admin on both, because this changes how every client of the firm is chased.

Field Type Default Bounds Means
startOffsetBusinessDays integer 5 0 to 20 Business days after period end before the first message
touchGapBusinessDays integer 4 2 to 20 Business days between messages
escalationGapBusinessDays integer 3 2 to 20 Business days before the escalation message
maxTouches integer 3 1 to 5 How many messages one month may ever cost a client
dailySendCap integer 300 1 to 5000 Firm-wide ceiling on messages in one local day
sendWindowStart time 09:00 Earliest a message may go
sendWindowEnd time 17:00 Latest a message may go
escalationEnabled boolean false Whether the final message to a third party may run
courtesyCloseOutEnabled boolean true Whether a closing thank-you goes when a month completes
confirmEscalation boolean Request only. See below

sendWindowEnd must be later than sendWindowStart and the window must be at least 60 minutes wide. A window cannot wrap past midnight, and a one-minute window would be a send cap of zero in practice.

maxTouches stops at 5 and there is no way to raise it. Five messages about one month is already more than most clients will forgive.

Turning escalation on takes two saves. Escalation writes to somebody the client did not choose to involve, a partner or the client's manager, so the first PUT with escalationEnabled: true is refused with 409 reminder_escalation_confirmation_required. Send it again with confirmEscalation: true and it sticks. After that, escalationConfirmed stays true and ordinary saves do not ask again.

escalationRunnable is the flag that actually decides: enabled and confirmed together. Read that one, not escalationEnabled.

The response adds isDefaulted, true when no row has been saved and the values above are the built-in defaults, plus id, profileId, createdAt, lastUpdatedAt, escalationConfirmedAt, and escalationRunnable.

GET, PUT, and DELETE /v1/profiles/{profileId}/reminders/settings

One client's override. ManageSettings on the client. The PUT body is the same as above.

Field Type Means
profileId uuid The client
hasOverride boolean Whether this client has its own row
override object The override, null when there is not one
effective object What the engine will actually use

effective carries the same schedule fields plus organizationSettingsId and profileSettingsId, so you can tell which row each value came from.

Read effective. It is the answer to "what will happen to this client", and it is the only field on this response that does not require you to work out inheritance yourself.

DELETE drops the override and puts the client back on the firm's default. It answers 200 with the settings that now stand, so a screen does not have to ask again.

Contacts

Who at the client gets written to. SendReminders on the client for all three routes.

Field Type Means
id uuid The contact
profileId uuid The client
email string The address
displayName string or null How they are greeted
locale string Which language their reminders are written in
role ProfileContactRole 0 primary, 1 escalation
canReceiveReminders boolean Whether anything will actually be sent here
hasHardBounced boolean Whether mail to this address bounced permanently
hardBouncedAt date-time, null When
hardBouncedEmail string or null The address that bounced, which may differ from the current one
createdAt date-time When it was added
lastUpdatedAt date-time, null Last change

role:

  • 0 primary receives the sequence itself: one message per touch, listing everything currently missing. A client may have several, an owner and an office admin, and they are addressed together on one message rather than one message each.
  • 1 escalation is written to only as the sequence's final touch, only where the firm configured one, and only in a separate message the client is never copied on. That message does not list the missing documents. Naming a client's outstanding items to a third party is a disclosure the client never agreed to.

hardBouncedEmail is kept separately from email on purpose. When somebody fixes a typo, the current address changes and the record of what bounced stays, so the same mistake is visible rather than quietly overwritten. canReceiveReminders is the field to branch on: a hard-bounced contact is still listed, still editable, and still receives nothing.

GET /v1/profiles/{profileId}/contacts

Every contact this client has, hard-bounced ones included. Answers a plain array.

POST /v1/profiles/{profileId}/contacts

Field Type Required Means
email string yes A valid address
role ProfileContactRole yes 0 primary or 1 escalation
displayName string or null no How they are greeted
locale string or null no Must be a locale Syllevo supports

Answers 201. An unsupported locale is refused with 400 validation.unsupportedLocale rather than quietly falling back to English, because an English reminder to a Romanian client is exactly the failure that makes a firm stop trusting the engine.

POST /v1/profiles/22222222-2222-4222-8222-222222222222/contacts HTTP/1.1
Host: api.syllevo.com
X-Api-Key: syl_live_examplekeynotreal000000000000
Content-Type: application/json

{
  "email": "maria@example.com",
  "role": 0,
  "displayName": "Maria Ionescu",
  "locale": "ro"
}

PATCH /v1/profiles/{profileId}/contacts/{contactId}

Field Type Required Means
email string yes The address, sent every time
role ProfileContactRole no Omit to leave it alone
displayName string or null no Omit to leave it alone
clearDisplayName boolean no Erase the name
locale string or null no Omit to leave it alone

email is required even on a patch. Sending clearDisplayName: true together with a displayName is two contradictory instructions and is refused with 400 rather than guessed at.

There is no delete route. A contact that should stop receiving mail is handled by role and by bounce state, and the history of who was written to stays intact.