LinkedIn settings API
Every /api/v1/linkedin/settings mode for templates, automation, monitoring, the message after acceptance and email, with its query, body, answer, scope and errors
LinkedIn settings live on one path, https://app.makesmores.com/api/v1/linkedin/settings. It holds the workspace's message templates and automation defaults, and each LinkedIn account's automation, monitoring hours, message after acceptance and email. Paths below are relative to https://app.makesmores.com/api/v1. Send Authorization: Bearer <key> on every call and bodies as JSON with Content-Type: application/json.
Monitoring is the only section on this page that's enabled in production so far. Until templates, automation, the message after acceptance and email are enabled, their modes answer 404 not_found.
Each mode takes level=workspace or account_id, never both, and a section. account_id is a LinkedIn account's connection_id, as GET /linkedin/accounts lists it. The method and the query together pick one mode, and a request that matches no mode answers 400 invalid_request before S'more reads the key, and costs nothing. A body can't repeat the account: a connection_id in it answers 400 invalid_request. While LinkedIn is closed, every request to this path gets the same plain text 404 page not found as a path S'more doesn't serve. API conventions covers these rules and what every route here shares: errors, caching, billing, timeouts, retries and paging. Every mode bills $0.001 per call.
Answers use snake_case. The JSON samples below are shortened, so read fields by name and expect more of them. No mode here sends a LinkedIn message or an email while you wait. Some writes approve messages that S'more sends later, and each section says which.
How approval works
Templates hold the exact text S'more sends, and approvals decide when it may send it:
- There are five template slots:
invitation,after_acceptfor the first message after someone accepts,follow_up,request_emailfor asking a person who replied for an email address, andemail. Each save of a slot adds a numbered revision, and a revision never changes after that. A save approves nothing and changes nothing already waiting to send. - The automation defaults pin one revision each for the
after_accept,follow_upandrequest_emailsteps, with the delays between them. Saving the defaults approves nothing either. The invitation revision is chosen on each list append, and the email revision when the mailbox owner approves email. - Activating an account is the approval. When an owner or admin activates an account, with apply or the account write, it takes the defaults at the revision they name, under the caller's own key. Automated messages then go only to people who accept after the activation's
cutoff_at, and anyone who accepted earlier is skipped. Another approval while the activation is live keeps itscutoff_at. Approving again after the account was off or the approval lapsed sets a new one. - To use a newer template, read
behindon the defaults, save the defaults with the new revision and apply them again. Steps S'more hasn't started yet take the new text. A message that's being sent, or was sent, keeps its text. An account with exceptions answerskept_exceptionsand keeps its earlier text until you apply withreplace_exceptions, or change the account on its own with the newdefaults_revision. - Exceptions let one account differ from the defaults, such as a longer follow-up delay.
- The message after acceptance is a separate approval of exact text for one account, with its own revision and its own cut-off. It reaches only people who have no first message of their own and accept after it was turned on. While it's on, it takes the place of the defaults'
after_acceptstep, andfirst_message_sourceon the account's automation says which one applies. - Email is approved per LinkedIn account by the owner of the mailbox that sends it, and the approval binds one revision of the
emailtemplate. A later save of that template changes nothing until the mailbox owner approves again. Comparebinding.template_revisionwithbinding.current_template_revisionon the email section to see whether a newer revision is waiting. - No template needs a report, a file or an asset. The email template needs a subject and a body, and a report field in a save answers
400 invalid_request.
Who can change what
Every write needs an owner or admin. S'more reads the role the key's person holds now, on each call, so an admin whose role was changed to member after the key was made gets 403 admin_required. An approval binds the caller's own key. It lapses when that key is revoked or expires, or when its person is no longer an owner or admin, and nothing it approved sends until someone approves again.
Saving templates and defaults, activating an account and approving the message after acceptance also need admin:write. Workspace admin API says how a key gets it.
| Action | Scopes | Authority |
|---|---|---|
| Read templates | linkedin:activity:read | Any member. Members see names and revisions, not text. LinkedIn slot text also needs linkedin:inbox:read on the key |
| Save a template or the defaults | linkedin:automation:write, admin:write | Owner or admin |
| Read the defaults or an account's automation | linkedin:activity:read | Any member. Members don't see the approver's name or key |
| Activate, apply or set exceptions | linkedin:automation:write, linkedin:inbox:read, linkedin:outreach:send, admin:write | Owner or admin. Binds the caller's own key |
| Pause, resume or turn off an account | linkedin:automation:write | Owner or admin |
| Read monitoring | linkedin:activity:read | Any member |
| Change monitoring | linkedin:automation:write, linkedin:inbox:read | Owner or admin. No test covers the refusal without linkedin:inbox:read yet |
| Read the message after acceptance or its suggestions | linkedin:inbox:read | Owner or admin |
| Approve the message after acceptance | linkedin:automation:write, linkedin:inbox:read, linkedin:outreach:send, admin:write | Owner or admin. Binds the caller's own key |
| Turn the message after acceptance off | linkedin:automation:write, linkedin:inbox:read, linkedin:outreach:send | Owner or admin |
| Read email | gmail:read | Any member. Members don't see the template copy, the sending address or the pairings |
| Ask to pair a mailbox | gmail:write | The mailbox's owner, who is an owner or admin. Add gmail:read to see pairings in the answer |
| Confirm a pairing | gmail:write | The workspace owner |
| Remove a pairing | gmail:write | The workspace owner, or the mailbox's owner who is an admin |
| Approve email | gmail:send | The owner of the confirmed mailbox, who is an owner or admin. Binds the caller's own key. Add gmail:read to read the answer |
| Turn email off | gmail:write | Any owner or admin |
Templates
Templates aren't enabled in production yet. Until they are, every section=templates mode answers 404 not_found.
| Method and query | Scope | Authority | Does |
|---|---|---|---|
GET /linkedin/settings?level=workspace§ion=templates | linkedin:activity:read | Any member | The current revision of each saved slot |
GET ...§ion=templates&name= | linkedin:activity:read | Any member | One slot's current revision |
GET ...§ion=templates&name=&revision= | linkedin:activity:read | Any member | One exact revision |
PATCH /linkedin/settings?level=workspace§ion=templates&name= | linkedin:automation:write, admin:write | Owner or admin | Saves the slot's next revision. It approves nothing |
name is invitation, after_accept, follow_up, request_email or email, and revision needs a name. A read answers its revisions in items, in slot order, with no paging. A slot that was never saved isn't listed, and a revision that doesn't exist answers 404 not_found.
{
"items": [
{
"name": "email",
"revision": 1,
"created_at": "2026-10-30T12:00:00Z",
"created_by": "33333333-3333-4333-8333-333333333333",
"created_by_name": "Alex Kim",
"subject": "Great to connect, {first_name}",
"body": "Hi {first_name},\n\nThanks for sharing your email.\n\n{signature}",
"calendar_url": "",
"signature": "Alex Kim"
}
]
}
A LinkedIn slot carries text, and the email slot carries subject, body, calendar_url and signature. A member gets the same items with the text fields empty. An owner or admin reads the email slot, and reads the LinkedIn slots only when the key also holds linkedin:inbox:read.
Save a LinkedIn slot
PATCH ...§ion=templates&name=follow_up, and the same for invitation, after_accept and request_email, takes:
| Field | Rules |
|---|---|
expected_revision | Required. The slot's current revision, or 0 for its first save |
text | Required. The exact message, up to 1,000 characters counted in UTF-16 code units. An invitation note is held to the workspace's invitation_note_limit, which the monitoring section reports |
S'more sends LinkedIn text exactly as written, so a slot takes no merge fields. A {word} token in braces, such as {first_name}, answers 400 invalid_request with field merge_field:
{ "code": "invalid_request", "message": "Invalid request. Field: merge_field.", "runsRemaining": 0, "field": "merge_field" }
A blank text, or one over the limit in UTF-16 code units, names field text, or invitation_note on the invitation slot. Text that's empty or over 1,000 characters answers 400 invalid_request without a field. The answer is the new revision:
{
"name": "follow_up",
"revision": 1,
"created_at": "2026-10-30T12:00:00Z",
"created_by": "33333333-3333-4333-8333-333333333333",
"created_by_name": "Alex Kim",
"text": "Following up on my note."
}
Save the email template
PATCH ...§ion=templates&name=email takes:
| Field | Rules |
|---|---|
expected_revision | Required. As for a LinkedIn slot |
subject | Required. Up to 200 characters on one line. It needs words besides {first_name}, because the name can be missing |
body | Required. Up to 5,000 characters |
calendar_url | Optional. An https:// link of up to 500 characters. Required only when the body uses {calendar_link} |
signature | Optional. Up to 1,000 characters |
An empty subject or body, or a value longer than the table allows, answers 400 invalid_request without a field.
The email takes three merge fields:
{first_name}only in the subject, or in a greeting line on its own such asHi {first_name},. Without a name, the subject drops it and the greeting readsHi there,.{calendar_link}only in the body, where it becomescalendar_url.{signature}only in the body. Without it, a signature goes after the body.
Any other {word} token, or one of these in the wrong place, is refused. A refusal answers 400 invalid_email with field naming the part at fault, such as calendar_url, first_name, merge_field or subject_without_name:
{ "code": "invalid_email", "message": "Add a calendar link that starts with https://. Field: calendar_url.", "runsRemaining": 0, "field": "calendar_url" }
S'more trims the edges of each field and uses plain line breaks, and the answer shows the copy as saved. A field this page doesn't list, such as a report link, answers 400 invalid_request.
A save never changes an approved email. The mailbox owner approves the new revision in Email before S'more sends it.
Automation defaults
Automation defaults aren't enabled in production yet. Until they are, every level=workspace§ion=automation mode, apply included, answers 404 not_found.
| Method and query | Scope | Authority | Does |
|---|---|---|---|
GET /linkedin/settings?level=workspace§ion=automation | linkedin:activity:read | Any member | The workspace's defaults |
PATCH /linkedin/settings?level=workspace§ion=automation | linkedin:automation:write, admin:write | Owner or admin | Replaces the defaults. It approves nothing |
PATCH ...§ion=automation&action=apply | linkedin:automation:write, linkedin:inbox:read, linkedin:outreach:send, admin:write | Owner or admin | Activates the defaults on up to 25 accounts under the caller's key |
The defaults read:
{
"revision": 1,
"after_accept_delay_seconds": 0,
"follow_up_delay_seconds": 259200,
"steps": ["after_accept", "follow_up"],
"templates": { "after_accept": 1, "follow_up": 1, "request_email": null },
"updated_by": "33333333-3333-4333-8333-333333333333",
"updated_by_name": "Alex Kim",
"updated_at": "2026-10-30T12:00:00Z",
"current_template_revisions": { "after_accept": 2, "follow_up": 1, "request_email": null },
"behind": ["after_accept"]
}
Before the first save, revision is 0 and steps is empty. behind names each step whose pinned revision is older than that slot's latest save.
Save the defaults
The PATCH replaces the whole record, so send every field:
| Field | Rules |
|---|---|
expected_revision | Required. The defaults' revision, or 0 for the first save |
after_accept_delay_seconds | Required. How long after an acceptance the first message waits, 0 to 7,776,000 (90 days) |
follow_up_delay_seconds | Required. How long after the first message the follow-up waits, 3,600 (one hour) to 7,776,000 |
steps | Required. Which of after_accept, follow_up and request_email run, each at most once. [] runs none |
templates | Required. An object with after_accept, follow_up and request_email, each a saved revision of that slot or null. Each step in steps needs one |
A revision that wasn't saved answers 400 invalid_request with field such as templates.follow_up. While email requests are closed, request_email in steps answers 400 invalid_request with field steps. Saving the same values again keeps the revision. The answer is the defaults as GET reads them.
Apply the defaults
PATCH ...§ion=automation&action=apply takes:
| Field | Rules |
|---|---|
defaults_revision | Required. The defaults' current revision |
account_ids | Required. 1 to 25 different account ids |
replace_exceptions | Optional. true clears each account's exceptions and applies the defaults. Without it, an account with exceptions is left as it is and answers kept_exceptions |
A stale defaults_revision answers 409 revision_conflict and changes nothing. Otherwise each account is written on its own, and the answer has one result per account in the order you sent them:
{
"defaults_revision": 1,
"results": [
{
"account_id": "55555555-5555-4555-8555-555555555555",
"sender_urn": "urn:li:fsd_profile:alex-kim",
"result": "activated",
"revision": 1,
"email": { "adoption": "needs_pairing", "mailbox_id": null, "template_revision": 1 }
},
{
"account_id": "66666666-6666-4666-8666-666666666666",
"sender_urn": "urn:li:fsd_profile:jo-park",
"result": "needs_monitoring_policy",
"reasons": ["monitoring_off", "account_campaign_missing"],
"revision": 0,
"email": { "adoption": "mailbox_not_connected", "mailbox_id": null, "template_revision": null }
}
]
}
| Result | Meaning |
|---|---|
activated | The account now runs these defaults under your key, with a new cutoff_at |
updated | The account was already active, and now runs these defaults under your key. Its cutoff_at stays |
paused | The account is paused. It now holds these defaults and stays paused |
unchanged | The account already ran exactly this, approved with your key. Nothing changed |
kept_exceptions | The account has exceptions, so nothing changed for it: it keeps its status, its revision and the defaults it ran before. Apply again with replace_exceptions true, or change the account on its own |
needs_monitoring_policy | The account can't run yet. reasons says why |
needs_authority | Your role no longer allows the approval. Nothing changed |
not_found | No LinkedIn account with that id in this workspace |
conflict | That account couldn't be written this time. Nothing changed for it, so apply again |
revision is the account's automation revision after the call, and email is how far its email setup has got, as in Account automation. Applying again with the same key is safe: an account already running these defaults under your key answers unchanged. An apply with another key answers updated and binds the account to that key.
If the defaults run request_email while email requests are closed, apply answers 409 automation_not_ready with reasons ["email_request_unavailable"] and changes nothing.
Account automation
Account automation isn't enabled in production yet. Until it is, section=automation on an account answers 404 not_found, to read and to change.
| Method and query | Scope | Authority | Does |
|---|---|---|---|
GET /linkedin/settings?account_id=§ion=automation | linkedin:activity:read | Any member | The account's automation as it runs now |
PATCH /linkedin/settings?account_id=§ion=automation | linkedin:automation:write. Activating and exceptions also need linkedin:inbox:read, linkedin:outreach:send and admin:write | Owner or admin | Activates, pauses, resumes or turns off the account's automation, or sets its exceptions |
{
"account_id": "55555555-5555-4555-8555-555555555555",
"sender_urn": "urn:li:fsd_profile:alex-kim",
"account_campaign_id": "77777777-7777-4777-8777-777777777777",
"status": "active",
"revision": 1,
"inherits": true,
"exceptions": null,
"defaults_revision": 1,
"defaults_current_revision": 1,
"after_accept_delay_seconds": 0,
"follow_up_delay_seconds": 259200,
"steps": ["after_accept", "follow_up"],
"templates": { "after_accept": 1, "follow_up": 1, "request_email": null },
"cutoff_at": "2026-10-30T12:00:00Z",
"first_message_source": "workspace_after_accept",
"approval": {
"actor_id": "33333333-3333-4333-8333-333333333333",
"actor_name": "Alex Kim",
"grant_id": "99999999-9999-4999-8999-999999999999",
"grant_label": "Setup agent",
"approved_at": "2026-10-30T12:00:00Z",
"expires_at": "2027-01-28T12:00:00Z",
"lapsed_at": null
},
"readiness": { "ready": true, "reasons": [] },
"email": { "adoption": "needs_mailbox_owner", "mailbox_id": "44444444-4444-4444-8444-444444444444", "template_revision": 1 }
}
statusisoff,active,pausedorlapsed. An account that was never activated readsoffwithrevision0.lapsedmeans the approving key or role no longer allows sending.defaults_revisionis the defaults the account runs, anddefaults_current_revisionthe latest.inheritsisfalsewhen the account hasexceptions.approval.expires_atis when the approving key runs out, so you can approve again before the account lapses. A member getsapprovalwithactor_name,grant_idandgrant_labelempty.first_message_sourceisaccount_after_acceptwhen the account's own message after acceptance fills empty first messages,workspace_after_acceptwhen the defaults' step does, andnoneotherwise.readiness.reasonssays why nothing runs, such asautomation_unavailableuntil S'more's first check of the account finishes,monitoring_off,account_campaign_missing,account_campaign_inactive,template_unset,email_request_unavailable,approval_lapsed,pausedoroff.email.adoptionismailbox_not_connected,needs_pairing,pairing_unconfirmed,needs_mailbox_owneroradopted, the last once the mailbox owner approved the latest email revision. A member doesn't seemailbox_id.
Change an account
The PATCH takes:
| Field | Rules |
|---|---|
expected_revision | Required. The account's automation revision, 0 before the first activation |
status | active, paused or off. Send status, exceptions or both |
defaults_revision | The defaults' current revision. Required to activate an account that's off, and with any exceptions. Resuming a paused account doesn't need it |
exceptions | An object with any of after_accept_delay_seconds, follow_up_delay_seconds, steps and templates, using the defaults' rules. Leave it out to keep the account's exceptions, or send null to clear them. Only with status active or no status |
- Activating binds the defaults at
defaults_revision, with the exceptions over them, under your key. From off or lapsed it sets a newcutoff_at. - Pausing holds the steps S'more hasn't started, and resuming lets them run again. Neither needs the approval scopes, and neither changes the approval.
- Turning off cancels every step S'more hasn't started, and keeps the account's exceptions for the next activation.
- A message that's being sent, or was sent, isn't changed by any of these.
These change LinkedIn steps only. Email has its own setting in Email, and pausing or turning off LinkedIn automation isn't checked before an email is scheduled.
The answer is the account's automation as GET reads it. A stale expected_revision or defaults_revision answers 409 revision_conflict. A change the account can't take answers 409 automation_not_ready with reasons, such as pausing an account that's off:
{ "code": "automation_not_ready", "message": "LinkedIn automation is not ready for this account. Reasons: off.", "runsRemaining": 0, "reasons": ["off"] }
Activating with defaults that run request_email while email requests are closed answers 409 automation_not_ready with reasons ["email_request_unavailable"]. Exceptions that leave request_email out can still be activated. Exceptions whose steps include request_email, including ones kept from before, answer 400 invalid_request with field exceptions.steps.
Monitoring
| Method and query | Scope | Authority | Does |
|---|---|---|---|
GET /linkedin/settings?account_id=§ion=monitoring | linkedin:activity:read | Any member | The account's monitoring hours, limits and how current S'more's view of it is |
PATCH /linkedin/settings?account_id=§ion=monitoring | linkedin:automation:write, linkedin:inbox:read | Owner or admin. No test covers the refusal without linkedin:inbox:read yet | Replaces the account's monitoring settings |
An account with no monitoring saved answers 404 not_found to the GET.
{
"connection_id": "55555555-5555-4555-8555-555555555555",
"sender_urn": "urn:li:fsd_profile:alex-kim",
"revision": 3,
"enabled": true,
"auto_enroll": false,
"time_zone": "America/New_York",
"windows": [{ "day": 1, "start": "09:00", "end": "17:00" }],
"daily_limit": 100,
"cycle_limit": 20,
"daily_invitation_limit": 0,
"invitation_note_limit": 200,
"tracking_state": "current",
"initial_observation_pending": false,
"next_check_at": "2026-10-30T12:00:00Z"
}
tracking_state, inbox_coverage, acceptance_coverage, next_check_at and initial_observation_pending say how current S'more's view of the account is. invitation_note_limit is the longest invitation note the workspace can approve, in UTF-16 code units.
The PATCH takes:
| Field | Rules |
|---|---|
expected_revision | Required. The monitoring revision, or 0 for the first save |
enabled | Required. true turns monitoring on |
time_zone | Required. An IANA zone such as America/New_York |
windows | Required. 1 to 21 working-hours windows of day, start and end. day is 0 for Sunday to 6 for Saturday, and start and end are local times such as 09:00, with end up to 24:00. Windows on one day can't overlap, and a window can't cross midnight, so split overnight hours in two |
daily_limit | Required. 1 to 300 LinkedIn requests a UTC day for this account, reads and writes together. It isn't an invitation limit |
cycle_limit | Required. 1 to 20 requests in one check, and no more than daily_limit |
daily_invitation_limit | Optional. 0 to 300 invitations a day in time_zone. Leave it out to keep the saved value, or send 0 to remove the limit |
auto_enroll | Optional. Leave it out to keep the saved value |
interval_minutes | Optional. 30 to 1,440. S'more accepts it, but it doesn't change how often S'more checks the account |
S'more checks a monitored account once every 45 to 90 minutes within its working hours, and automated messages go only within them. Turning monitoring on schedules those checks on LinkedIn and sets initial_observation_pending until the first one finishes. Until then the account's automation reads automation_unavailable.
A save also sets up the account's campaign if it has none, and the message after acceptance and automation need it. If the answer is lost, read the monitoring again before you resend.
The answer is the monitoring settings as GET reads them. A stale expected_revision answers 409 activity_conflict, so read the monitoring again and send its revision.
After accept
The message after acceptance isn't enabled in production yet. Until it is, every section=after_accept mode answers 404 not_found.
The message after acceptance is one exact text an owner or admin approves for one account. S'more plans it no more than once for each person who accepts after it was turned on and has no first message of their own.
| Method and query | Scope | Authority | Does |
|---|---|---|---|
GET /linkedin/settings?account_id=§ion=after_accept | linkedin:inbox:read | Owner or admin | The account's message after acceptance |
GET ...§ion=after_accept&view=suggestions | linkedin:inbox:read | Owner or admin | Texts the account's person sent by hand on LinkedIn, newest first |
PATCH /linkedin/settings?account_id=§ion=after_accept | linkedin:automation:write, linkedin:inbox:read, linkedin:outreach:send. action=approve also needs admin:write | Owner or admin | Approves exact text, or turns the message off |
An account with no campaign yet, such as one whose monitoring was never saved, answers 404 not_found, and a member gets 403 admin_required.
{
"account_campaign_id": "77777777-7777-4777-8777-777777777777",
"sender_urn": "urn:li:fsd_profile:alex-kim",
"status": "on",
"text": "Thanks for connecting, glad to be in touch.",
"revision": 1,
"approved_at": "2026-10-30T12:00:00Z",
"approved_by": "33333333-3333-4333-8333-333333333333",
"approved_by_name": "Alex Kim",
"lapsed_at": null,
"sent_to": 0,
"shared_email": 0,
"accepted_before": 12,
"waiting": 0
}
status is off, on or lapsed. sent_to counts the people it reached and shared_email those of them who later shared an email address. accepted_before counts people who accepted before it was turned on, who never get it, and waiting the people it filled whose message hasn't gone yet.
The suggestions answer items, each a text the account's person sent by hand with first_sent_at, last_sent_at, sent_to and shared_email:
{
"items": [
{ "text": "Thanks for connecting! What are you working on this quarter?", "first_sent_at": "2026-10-27T12:00:00Z", "last_sent_at": "2026-10-30T12:00:00Z", "sent_to": 4, "shared_email": 1 }
]
}
The PATCH takes:
| Field | Rules |
|---|---|
action | Required. approve or off |
expected_revision | Required. The message's revision, 0 before the first approval |
text | Required with approve, and not sent with off. The exact message, up to 1,000 characters |
- Approving while it's off, or after it lapsed, starts a new cut-off at that moment. Approving new text while it's on keeps the cut-off.
- The approval binds your key, and lapses as other approvals do.
- Turning it off takes it back from the people it filled but hasn't sent to, so they have no first message again.
The answer is the message as GET reads it. A stale expected_revision answers 409 activity_conflict.
Email
LinkedIn email isn't enabled in production yet, and neither are the Gmail accounts it sends from. Until they are, every section=email mode answers 404 not_found. A key without the mode's Gmail scope gets 403 insufficient_scope first, and API keys says when the Gmail scopes can be given.
The email section sets up email for one LinkedIn account: which Gmail mailbox sends it, which revision of the email template, and whether each email waits for the mailbox owner. A mailbox is connected on Gmail accounts first. The emails S'more writes are read and sent on LinkedIn API.
| Method and query | Scope | Authority | Does |
|---|---|---|---|
GET /linkedin/settings?account_id=§ion=email | gmail:read | Any member. Members don't see the template copy, the sending address or the pairings | The account's email policy, pairings and template binding |
PATCH ...§ion=email&action=request_pairing | gmail:write | The mailbox's owner, who is an owner or admin | Asks for their own mailbox to send this account's email |
PATCH ...§ion=email&action=confirm_pairing | gmail:write | The workspace owner | Confirms a mailbox that asked. It sends nothing and approves no template |
PATCH ...§ion=email&action=remove_pairing | gmail:write | The workspace owner, or the mailbox's owner who is an admin | Removes the mailbox from this account |
PATCH ...§ion=email&action=approve | gmail:send | The owner of the confirmed mailbox, who is an owner or admin | Approves a template revision and a mode under the caller's key |
PATCH ...§ion=email&action=off | gmail:write | Any owner or admin | Turns the account's email off |
Every mode answers the email section:
{
"connection_id": "55555555-5555-4555-8555-555555555555",
"sender_urn": "urn:li:fsd_profile:alex-kim",
"policy": {
"available": true,
"policy": {
"mailbox_id": "44444444-4444-4444-8444-444444444444",
"mode": "ask",
"subject": "Great to connect, {first_name}",
"body": "Hi {first_name},\n\nThanks for sharing your email.\n\n{signature}",
"calendar_url": "",
"signature": "Alex Kim",
"daily_cap": 20,
"approved_by_name": "Alex Kim",
"approved_at": "2026-10-30T12:00:00Z",
"armed_at": "2026-10-30T12:00:00Z",
"revision": 1,
"from_email": "alex@example.com",
"from_name": "Alex at Example",
"mailbox_status": "connected"
},
"sent_today": 0,
"daily_cap": 20,
"hours": { "time_zone": "America/New_York", "windows": [{ "day": 1, "start": "09:00", "end": "17:00" }] },
"can_edit": true,
"confirmed_mailbox_id": "44444444-4444-4444-8444-444444444444",
"counts": { "waiting_ok": 0, "held": 0, "scheduled": 0, "sending": 0, "sent": 0, "unknown": 0, "failed": 0, "cancelled": 0, "replied": 0, "bounced": 0 }
},
"pairing": { "confirmed": { "mailbox_id": "44444444-4444-4444-8444-444444444444", "revision": 3 }, "requested": [] },
"binding": { "template_revision": 1, "current_template_revision": 2, "in_force": true, "approved_via": "api", "lapsed": false }
}
policy.policyisnulluntil email is first approved, and itsrevisionis whatapproveandoffsend back.modeisask,autooroff.hoursare the account's monitoring hours, and email goes only within them.pairinghas the confirmed mailbox and every mailbox waiting for the workspace owner, each with the mailbox's ownrevision. A member getspairingnulland the template fields empty.binding.in_forceistruewhile an approval made over the API is the one that sends.approved_viaisapithen,browserwhen email was approved in the S'more app, andnullwhen it's off.lapsedistrueonce the approving key or role no longer allows sending.
A key without gmail:read can't read the section, and gets 403 insufficient_scope. Removing a pairing or turning email off with such a key answers the section as a member sees it, with no pairings and no template copy. The other writes give the same answer in code, but no test covers them yet, so hold gmail:read on the key you pair and approve with as well.
Pair a mailbox
request_pairing takes mailbox_id, the caller's own mailbox, and expected_revision, that mailbox's revision from GET /gmail/accounts. When the workspace owner asks for their own mailbox, the pairing is confirmed at once. Otherwise the workspace owner sends confirm_pairing with the same mailbox_id and the revision shown for it in pairing.requested. An account has at most one confirmed mailbox, and another confirmation for it answers 409 pairing_taken.
remove_pairing takes mailbox_id only. It turns the account's email off and cancels the emails waiting to send from that mailbox. Removing it again is safe.
Approve email
action=approve takes:
| Field | Rules |
|---|---|
mailbox_id | Required. The confirmed mailbox, which must be the caller's own |
template_revision | Required. A saved revision of the email template |
mode | Required. ask or auto |
expected_revision | Required. 0 while policy.policy is null, otherwise policy.policy.revision |
daily_cap | Optional. 1 to 50 emails a day from this mailbox. A new policy starts at 20, and an approval without it keeps the current cap |
The approval sends nothing itself. From armed_at on, S'more reads the LinkedIn replies this account receives, and when a person types an email address it writes the approved template for them to that address:
- In
askmode every email is held until the mailbox owner sends it. - In
automode an email is scheduled to send on its own within the account's hours and the daily cap. It's held for the mailbox owner instead when the reply has more than one address, a shared inbox address or words asking not to be contacted, when it arrived too late, or when someone wrote to the person on LinkedIn after that reply. An opt-out or pause recorded in LinkedIn automation isn't checked before an email is scheduled, so a person who opted out or was paused there can still be emailed in auto mode. Inaskmode the held email doesn't show the opt-out either.
A reply that arrived before armed_at, or while email was off, is never emailed. The answer adds rebound, the number of waiting emails that took the newly approved text, and kept, the number of the account's other waiting emails that keep their text, such as ones a person edited or released. An approval that would rewrite more than 1,000 waiting emails answers 409 waiting_emails_limit and changes nothing.
Only the mailbox owner approves. Another admin gets 403 mailbox_owner_required, and a key without gmail:send gets 403 insufficient_scope. A template_revision that wasn't saved answers 400 invalid_request with field template_revision, and an account with no confirmed mailbox 409 pairing_required.
When the approving key is revoked, expires or loses gmail:send, or the mailbox owner is no longer an owner or admin, the approval lapses. S'more then turns the account's email off and cancels the emails waiting to send, and replies that arrive before a new approval are never emailed. Approving or turning off email in the S'more app also ends an approval made over the API.
Turn email off
action=off takes expected_revision, the policy's revision. Any owner or admin can send it, and sending it again is safe. A reply that arrives while email is off is never emailed, even after a new approval.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field doesn't fit, and field names it when S'more can, such as merge_field, templates.follow_up, defaults_revision, exceptions or template_revision. Also an account_id that names no LinkedIn account in this workspace |
| 400 | invalid_email | The email template can't be saved as written. field names the part |
| 403 | admin_required | Only owners and admins can do this, or your role changed since the key was made |
| 403 | owner_required | Only the workspace owner confirms a pairing |
| 403 | mailbox_owner_required | Only the mailbox's owner can do this |
| 403 | insufficient_scope | The key lacks a scope this mode needs, such as admin:write or gmail:send |
| 404 | not_found | No such revision, no monitoring or campaign for the account yet, or a part of LinkedIn this workspace can't use yet. See Gates |
| 404 | mailbox_not_found | No connected mailbox with that mailbox_id in this workspace, on a pairing change |
| 409 | revision_conflict | A template, the defaults, an account's automation or the email policy changed since you read it |
| 409 | activity_conflict | Monitoring or the message after acceptance changed since you read it |
| 409 | automation_not_ready | The account can't take this change yet. reasons says why |
| 409 | pairing_required, pairing_taken, pairing_missing | Email needs a confirmed mailbox first, the account already has one, or there's no request to confirm |
| 409 | mailbox_not_connected | The mailbox needs attention before it can send. Check its status on GET /gmail/accounts |
| 409 | waiting_emails_limit | The approval would rewrite more than 1,000 waiting emails |
Reads are safe to repeat. A write that timed out or lost its answer may still have gone through, so read the section again before you resend, and send the change with the revision you read. A resend with the old revision answers 409 once the first write went through, except apply and remove_pairing, which are safe to send again as they were. API conventions covers the rest.