Join the beta

API reference

183 addresses, JSON, and one header with the token.

curl
curl -X POST https://nitrino.ai/api/v1/conversations/<id>/notes \
  -H "Authorization: Bearer <access token>" \
  -H "Idempotency-Key: 4f9c-2a" \
  -H "Content-Type: application/json" \
  -d '{"text":"asked for a call on Tuesday"}'

Addresses

GET/api/v1

Workspace, member and scopes of the token

200
200 {
  "workspace": "9f1c…",
  "member": "4a2b…",
  "client": { "id": "d342…", "name": "Astra" },
  "scopes": ["inbox:read", "inbox:write", "send"]
}

GET/api/v1/conversations

Conversations, most recent first

cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most
qstring
Search across letters, people and transcripts
folderstring
A built-in folder or a smart folder id
tagstring
Tag id from /labels
ownerstring
Member id, nobody or agent
statestring
active, snoozed, closed or all
200
200 {
  "conversations": [
    {
      "id": "c81f…",
      "person": "77a0…",
      "kind": "email",
      "lastMessageAt": "2026-09-20T09:41:02.000Z",
      "waitingSince": "2026-09-20T09:41:02.000Z",
      "importance": "high",
      "intent": "quote",
      "owner": null
    }
  ],
  "next": "MjAyNi0wOS0yMFQwOTo0MTowMi4wMDBafGM4MWY"
}

GET/api/v1/conversations/{id}

One conversation

PATCH/api/v1/conversations/{id}

Snooze, wake, close, reopen, assign, mark a meeting; personal, state by hand, not noise, take off 'meeting booked', no answer needed

snoozeUntilstring | null
Snooze until this moment; null wakes it
whystring
Why it is snoozed — your team reads this
closedboolean
true closes the conversation, false reopens it
reasonstring
Why it is closed: meeting_booked, not_interested, no_response, wrong_person, spam, personal, other
ownerstring | null
Who takes the conversation; null clears the owner
metboolean
A meeting was arranged outside this correspondence
personalboolean
Personal: no agent writes into it
hardStopboolean
Hard stop on the person: no agent or campaign writes to them first
intentstring
The conversation's state, by hand
heedtrue
true — not noise
unbooktrue
true — take off 'meeting booked'
noReplytrue | string
true or the call's moment — no answer needed

POST/api/v1/conversations/{id}/meeting

Tie a meeting to the conversation

meetingstringrequired
Meeting id from the calendar

DELETE/api/v1/conversations/{id}/agents/{agent}

Take an extra agent off the conversation

GET/api/v1/conversations/{id}/messages

Messages of a conversation, newest first

cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most
200
200 {
  "messages": [
    {
      "id": "m4c1…",
      "conversation": "c81f…",
      "direction": "inbound",
      "sender": { "name": "Pete", "handle": "pete@acme.example" },
      "subject": "Slabs",
      "text": "will you send the report?",
      "at": "2026-09-20T09:41:02.000Z",
      "automated": false,
      "agent": null,
      "voice": null
    }
  ],
  "next": null
}

POST/api/v1/conversations/{id}/messages

A message to the client — into the send queue

textstring
What to send; files may go without words
tostring
The client's address, when there is more than one
viastring
Our mailbox, when more than one fits
subjectstring
Subject of a first letter; a reply keeps the thread's
ccstring[]
Email only: who else to put in Cc. The thread's participants are copied anyway
replyAllboolean
Email only: false — do not copy the thread's participants
filesobject[]
Files: name, type and base64 content
shelfstring[]
Agent shelf files, by id
sendAtstring
When to send; without it, as soon as the queue allows
200
200 {
  "message": {
    "id": "o7d2…",
    "state": "queued",
    "sendAt": null,
    "to": "pete@acme.example",
    "cc": ["anna@acme.example"]
  }
}

POST/api/v1/conversations/{id}/notes

A note inside the conversation: the client never sees it

textstringrequired
The note

POST/api/v1/conversations/{id}/labels

Put a label on the conversation, by name

namestringrequired
Label name; an unknown one is created

DELETE/api/v1/conversations/{id}/labels/{label}

Take the label off

GET/api/v1/messages/{id}/files

Files of a message

GET/api/v1/conversations/{id}/draft

The agent's draft and reminder ahead

200
200 {
  "draft": {
    "text": "Pete, the report goes out on Friday.",
    "agent": "a1b2…",
    "reason": "unanswered",
    "note": null,
    "ask": null,
    "calledAt": "2026-10-08T09:41:02.000Z",
    "files": [],
    "blanks": [],
    "route": null
  },
  "reminder": null
}

POST/api/v1/conversations/{id}/draft

Approve the agent's draft — the letter is queued, the agent stays

confirmedbooleanrequired
true is the person’s yes to this letter; without it nothing is sent
textstring
Your own words instead of the draft; without it, the draft as it stands
entrustboolean
true hands the conversation to the agent: from now on it sends by itself
shelfstring[]
Shelf files; absent means the agent’s own, empty means none
filesobject[]
Files: name, type and base64 content
sendAtstring
When to send; without it, as soon as the queue allows

PATCH/api/v1/conversations/{id}/reminder

Change the agent's reminder

textstring
The reminder’s new wording
duestring
New due date, up to two months ahead

DELETE/api/v1/conversations/{id}/reminder

Drop the agent's reminder

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/conversations/{id}/pending

Letters of a conversation not sent yet

200
200 {
  "pending": [
    {
      "id": "o7d2…",
      "state": "queued",
      "sendAt": "2026-10-09T08:00:00.000Z",
      "text": "Pete, a reminder about the report.",
      "subject": null,
      "to": "pete@acme.example",
      "via": "e5f6…",
      "agent": null,
      "edited": false,
      "failure": null,
      "files": [],
      "shelf": [],
      "undoUntil": null
    }
  ]
}

PATCH/api/v1/conversations/{id}/pending/{pending}

Change a waiting letter

textstring
The letter’s new wording
sendAtstring
When to send; without it, as soon as the queue allows
nowboolean
true lets it go with the next queue pass

DELETE/api/v1/conversations/{id}/pending/{pending}

Cancel a waiting letter

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

POST/api/v1/conversations/{id}/pending/undo

Take a letter back from the queue with its words and files

pendingstringrequired
A letter from the waiting list

POST/api/v1/messages/{id}/forward

Forward a message with its files to another conversation

tostringrequired
The conversation to forward into
confirmedbooleanrequired
true is the person’s yes to this letter; without it nothing is sent

POST/api/v1/messages/{id}/reactions

React to a message

emojistringrequired
An emoji from the reaction set

GET/api/v1/files/{id}

The file itself, as bytes

GET/api/v1/people

Clients, by name

cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most

GET/api/v1/people/{id}

One client

PATCH/api/v1/people/{id}

Change a client card

namestring
What you call them; empty clears the name
citystring
City
jobTitlestring
Job title
companystring
Their company
emailstring
Email address — as one more contact
birthdaystring | null
YYYY-MM-DD or --MM-DD; null clears it

GET/api/v1/people/{id}/card

Contacts with ids, field values, birthday

POST/api/v1/people/{id}/contacts

Add a contact; a taken one — 409 with twin

channelstringrequired
email, telegram, whatsapp, linkedin, instagram
handlestringrequired
Address, phone or nickname

PATCH/api/v1/people/{id}/contacts/{contact}

Correct a contact in its channel

handlestringrequired
Address, phone or nickname

POST/api/v1/people/{id}/contacts/ignored

Stop offering a contact from a letter

channelstringrequired
email, telegram, whatsapp, linkedin, instagram
handlestringrequired
Address, phone or nickname

POST/api/v1/people/{id}/colleagues

Other people's contacts — as new cards

contactsobject[]required
[{channel, handle}], up to 10

POST/api/v1/people/{id}/merge

Merge twin into this card — with confirmed

twinstringrequired
The other card's id
confirmedbooleanrequired
true is the person’s yes to merging exactly these two

POST/api/v1/people/{id}/unmerge

Take a merged card back out

twinstringrequired
The other card's id

POST/api/v1/people/{id}/different

Different people: no merge offer

twinstringrequired
The other card's id

PUT/api/v1/people/{id}/fields/{field}

A card field's value, by hand

valuestring | nullrequired
Value; empty or null clears it
rowstring
Row of a table block; empty — the card itself

GET/api/v1/fields

Card field schema

PUT/api/v1/fields

Replace the schema: a field left out goes with its values

sectionsobject[]required
Sections with fields, in order

POST/api/v1/people/{id}/tags

Mark a product on a person: client or partner

labelstringrequired
Product tag
sidestringrequired
Role: buyer — client, seller — partner

DELETE/api/v1/people/{id}/tags

Take the pairing off; it is recorded as removed, not deleted

GET/api/v1/agents

The agents of this workspace, all at once

POST/api/v1/agents

Create an agent: it is born switched on, and its first 10 letters wait for Send

namestringrequired
What the agent is called in the list
goalstringrequired
What the agent leads the conversation towards
rulesstring
How it must behave
knowledgestring
What it knows about your business
companystring
On whose behalf it writes; without it, the only company
labelsstring[]
Product tags it covers; empty means every client
membersstring[]
Whose behalf and channels it writes with; without it, the owners of the connected accounts
channelsstring[]
Connections it writes through; empty means every channel of its people
hoursobject
The agent's hours: timezone, from, to, weekends and replies outside the hours
pricingstring
What it says about money: list names the prices and terms of the products, silent names no price and calls a human
groupsstring[]
The group chats it leads, ids from /agents/groups; the whole list
rankinteger
Its place among switched-on agents when several could lead a conversation: the smaller leads
maxAutointeger
How many letters in a row it writes without an answer
minScoreinteger
Below which closeness to the goal it leaves a silent conversation alone
ignoreAfterinteger
After how many silent days a conversation counts as abandoned
meetHoursobject
From which hour to which it offers meetings, in the workspace timezone
mannersobject
How it handles silence: replyOnly, stopAfterLink, nudgeAfter, staleAfter
delaysobject[]
The wait before a letter, per channel: connection, min and max in minutes; the whole list
confirmedboolean
true is the person’s yes to the agent starting to write by itself; needed along with the send scope when the change switches such an agent on or sets sendMode to auto

GET/api/v1/agents/groups

The group chats an agent can lead, and who leads them

POST/api/v1/agents/bulk

Put an agent on named conversations or take agents off them, up to 200 at once

commandstringrequired
hand puts on, release takes off, pause mutes, resume lifts the mute
conversationsstring[]required
Conversation ids, up to 200
agentstring
The agent to put on; only with hand
confirmedboolean
true is the person’s yes to the agent writing to these people by itself; needed with hand and resume, along with the send scope

GET/api/v1/agents/{id}

One agent, with the words it works by

PATCH/api/v1/agents/{id}

Change an agent: send only what changes

goalstring
What the agent leads the conversation towards
rulesstring
How it must behave
knowledgestring
What it knows about your business
enabledboolean
Whether the agent works at all
labelsstring[]
Product tags it covers; empty means every client
membersstring[]
Whose behalf and channels it writes with; without it, the owners of the connected accounts
channelsstring[]
Connections it writes through; empty means every channel of its people
hoursobject
The agent's hours: timezone, from, to, weekends and replies outside the hours
pricingstring
What it says about money: list names the prices and terms of the products, silent names no price and calls a human
groupsstring[]
The group chats it leads, ids from /agents/groups; the whole list
rankinteger
Its place among switched-on agents when several could lead a conversation: the smaller leads
maxAutointeger
How many letters in a row it writes without an answer
minScoreinteger
Below which closeness to the goal it leaves a silent conversation alone
ignoreAfterinteger
After how many silent days a conversation counts as abandoned
meetHoursobject
From which hour to which it offers meetings, in the workspace timezone
mannersobject
How it handles silence: replyOnly, stopAfterLink, nudgeAfter, staleAfter
delaysobject[]
The wait before a letter, per channel: connection, min and max in minutes; the whole list

DELETE/api/v1/agents/{id}

Delete an agent; one switched on with live conversations only after a pause

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted
successorstring
Who takes over the live conversations, in the body or as ?successor=; without it they go back to people

POST/api/v1/agents/{id}/pause

Stop an agent on one account, or let it write there again

connectionstringrequired
The connection to stop it on or let it back
pausedbooleanrequired
true stops it, false lets it back (with send and confirmed)
confirmedboolean
true is the person’s yes to the agent writing to these people by itself; needed with hand and resume, along with the send scope

POST/api/v1/conversations/{id}/agent

The agent on a conversation: put on, take off, mute, resume

commandstringrequired
hand puts on, release takes off, pause mutes, resume lifts the mute
agentstring
The agent to put on; only with hand
confirmedboolean
true is the person’s yes to the agent writing to these people by itself; needed with hand and resume, along with the send scope

GET/api/v1/products

The products of this workspace, all at once

POST/api/v1/products

Create a company product; a journal line names the program

namestringrequired
What the product is called
sidestringrequired
Selling, buying or both
companystring
Whose product it is; without it, the only company of the workspace
descriptionstring
What this product is, in your own words
goalstring
Deprecated: accepted and ignored. The goal of a conversation is set on the agent and the campaign
conditionsobject[]
Terms as key-value lines, each with its own side
marketsstring[]
Narrows the company's markets; empty means the company's

GET/api/v1/products/{id}

One product with its terms and markets

PATCH/api/v1/products/{id}

Change a product

descriptionstring
What this product is, in your own words
sidestring
Selling, buying or both
goalstring
Deprecated: accepted and ignored. The goal of a conversation is set on the agent and the campaign
conditionsobject[]
Terms as key-value lines, each with its own side
marketsstring[]
Narrows the company's markets; empty means the company's

DELETE/api/v1/products/{id}

Delete a product; its tag stays among the tags

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/workspace

Workspace settings: timezone and machine tagging

GET/api/v1/products/{id}/letters

A product's first letters: a conversation opened with a similar one is about it

POST/api/v1/products/{id}/letters

Keep a first letter for a product

textstringrequired
The letter as it is sent

DELETE/api/v1/products/{id}/letters/{id}

Remove a product's first letter; tags it already put stay

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/products/candidates

Goods found in conversations that no product covers yet

POST/api/v1/products/candidates/skip

“Not now”: take a found product out of the queue

labelstringrequired
The label of a found product

GET/api/v1/goals

The workspace's own goals and own “what the agent does” entries

POST/api/v1/goals

Add an own goal or an own “what the agent does”

kindstringrequired
goal is a goal, role is what the agent does
namestringrequired
Its name in the list, up to 80 characters
aboutstring
What it means, up to 500 characters

PATCH/api/v1/goals/{id}

Rename an own entry or rewrite what it means

namestring
Its name in the list, up to 80 characters
aboutstring
What it means, up to 500 characters

GET/api/v1/sources

Knowledge sources of an agent, a company or the house rules: links, notes and files

agentstring
Agent id: its rules and knowledge
companystring
Company id: what agents say about it
fieldstring
rules or knowledge for an agent and the house rules, pitch for a company

POST/api/v1/sources

Bring a source — a link, a note or a file; a file is parsed to text after the answer

agentstring
Agent id: its rules and knowledge
companystring
Company id: what agents say about it
fieldstring
rules or knowledge for an agent and the house rules, pitch for a company
linkstring
An http or https address
notestring
Text written by a person
fileobject
name and content in base64: xlsx, csv, pdf, docx, txt or md, up to three megabytes

DELETE/api/v1/sources/{id}

Remove a source together with its file

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

POST/api/v1/agents/{id}/files

Put a file on an agent's shelf: the agent attaches it to a letter on its own

fileobjectrequired
name, content in base64 and type; up to three megabytes
whenToSendstring
When the agent sends this file, up to 500 characters

PATCH/api/v1/agents/{id}/files/{id}

Rewrite a shelf file's line on when to send it

whenToSendstringrequired
When the agent sends this file, up to 500 characters

DELETE/api/v1/agents/{id}/files/{id}

Take a file off an agent's shelf

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/workspace

Workspace settings: timezone and machine tagging

PATCH/api/v1/workspace

Change settings; the name has its own route, and the people theirs

timezonestring
The timezone its day is counted in
taggingboolean
Whether the machine tags conversations itself
findsGoodsboolean
Whether the machine looks for goods in conversations that are not among the products

PATCH/api/v1/workspace/name

Rename the workspace

namestringrequired
The new name, 120 characters at most

GET/api/v1/labels

The tags of this workspace: product and correspondence

POST/api/v1/labels

Create a tag

namestringrequired
Tag name
kindstring
goods — product, talk — correspondence
promptstring | null
The model's question that puts the tag on by itself
promptFromstring | null
From which day the tag reaches older letters
hueinteger
Colour, 0–5
synonymsstring[]
Other words for the same tag
keepsMachinesboolean
Talk tags only: true keeps a conversation with this tag open to agents, and the tag can slice an agent. This is how Client and Partner work

PATCH/api/v1/labels/{id}

Change a tag, accept it, archive or bring back

namestring
Tag name
kindstring
goods — product, talk — correspondence
promptstring | null
The model's question that puts the tag on by itself
promptFromstring | null
From which day the tag reaches older letters
hueinteger
Colour, 0–5
synonymsstring[]
Other words for the same tag
keepsMachinesboolean
Talk tags only: true keeps a conversation with this tag open to agents, and the tag can slice an agent. This is how Client and Partner work
acceptedtrue
true — accept a tag the model proposed
archivedboolean
true — archive, false — bring back

POST/api/v1/labels/{id}/run

One slice of running a tag over history: costs model calls

cursorstring
From the previous answer, to go on
200
200 {
  "run": { "label": "5d1e…", "looked": 40, "applied": 3, "asked": 40, "missed": 0, "done": false, "next": "c81f…" }
}

GET/api/v1/folders

Smart folders this member sees

POST/api/v1/folders

Create a smart folder

namestringrequired
Folder name
descriptionstring | null
What the folder holds
conditionsobject
Selection conditions — keys as the folder list gives them
sharesobject[]
Shared with: [{member, role}]
teamstring | null
Whole team: viewer, editor or null

PATCH/api/v1/folders/{id}

Change a smart folder: only what is sent

namestring
Folder name
descriptionstring | null
What the folder holds
conditionsobject
Selection conditions — keys as the folder list gives them
sharesobject[]
Shared with: [{member, role}]
teamstring | null
Whole team: viewer, editor or null

DELETE/api/v1/folders/{id}

Delete a smart folder — by its owner, with confirmed

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/audit

The journal: who opened the door and what was changed through it. Owner and admins only

GET/api/v1/channels

Connections: what the workspace speaks through, and whether they are alive

GET/api/v1/members

Members and what each told the agents about themselves

POST/api/v1/members

Invite a person: a known address joins at once, any other gets a letter. Needs send and confirmed

emailstringrequired
The person’s email address
rolestring
owner, admin, operator or member; member when absent
confirmedbooleanrequired
true is the person’s yes to this invitation; without it no letter goes

POST/api/v1/members/{id}/role

Change a member’s role; needs confirmed

rolestringrequired
owner, admin, operator or member
confirmedbooleanrequired
true is the person’s yes to this role change

DELETE/api/v1/members/{id}

Take a member’s access away; needs confirmed, the last owner stays

confirmedbooleanrequired
true is the person’s yes to this member leaving, in the body or as ?confirmed=true

PATCH/api/v1/members/{id}

Change a member card

jobTitlestring
Their job title
zonestring
What they cover — what makes a question theirs
signaturestring
What their letters are signed with

GET/api/v1/house

The workspace-wide agent rules

PATCH/api/v1/house

Change the house rules of agents; each change is a journal line naming the program

rulesstring
How every agent behaves
knowledgestring
What every agent knows about the business
teamstring
Who in the team answers what
linkstring
The fallback booking link
aliasesstring
The team's own addresses, one per line: letters from them are not a contact's
pricingFieldsstring[]
What an agent learns from a contact before an offer — for products without a list of their own

GET/api/v1/deals

Deals: the client, the partner and where it stands

statestring
open or closed; omit for both

GET/api/v1/deals/{id}

One deal

POST/api/v1/deals

Open a deal

labelstringrequired
The product of the deal
sellerstringrequired
The partner, if there is one
buyerstringrequired
The client
agentstringrequired
The agent leading both sides

PATCH/api/v1/deals/{id}

Move a deal along or close it — one at a time

stagestring
The stage to move to
closedboolean
Close the deal
outcomestring
won or lost: close with an outcome in one call

GET/api/v1/deals/stages

The workspace’s pipeline stages in board order

200
200 {
  "stages": [
    { "id": "paired", "name": null, "builtIn": true, "position": 0, "openDeals": 4 },
    { "id": "3b7e…", "name": "Sample sent", "builtIn": false, "position": 1, "openDeals": 2 }
  ]
}

PATCH/api/v1/deals/stages

Rename, reorder and add stages, for an owner or an admin

stagesobject[]required
Every stage in order: id and name; no id means a new stage

DELETE/api/v1/deals/stages/{id}

Remove a stage of your own, moving its deals

intostringrequired
The stage its deals move to
confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/companies

The companies of this workspace — who the agents write for

POST/api/v1/companies

Create a company; a journal line names the program

namestringrequired
What the company is called
websitestring
The company's website
pitchstring
What the company offers — why an agent writes at all
policystring
What it does not agree to and stays silent about
factsobject[]
Facts as label-value lines
marketsstring[]
Markets as ISO 3166-1 alpha-2 country codes, the whole list

GET/api/v1/companies/{id}

One company with its facts and markets

PATCH/api/v1/companies/{id}

Change a company card

pitchstring
What the company offers — why an agent writes at all
policystring
What it does not agree to and stays silent about
factsobject[]
Facts as label-value lines
marketsstring[]
Markets as ISO 3166-1 alpha-2 country codes, the whole list

GET/api/v1/segments

Saved segments and how many people each holds

200
200 {
  "segments": [
    {
      "id": "5e0b…",
      "name": "Heads of marketing, SaaS in Germany",
      "query": "heads of marketing at SaaS companies in Germany",
      "rules": [{ "kind": "except", "text": "agencies" }],
      "refill": true,
      "saved": true,
      "createdAt": "2026-09-27T08:12:40.000Z",
      "updatedAt": "2026-09-28T10:02:11.000Z",
      "members": 412
    }
  ]
}

POST/api/v1/segments

Describe a segment in words and judge people; the rest by the same call

querystringrequired
Who belongs to the segment, in words
rulesobject[]
Refinements: only or except, and words
refillboolean
Whether the segment takes in new people itself
segmentstring
The segment to go on judging
namestring
Name of a new segment; without it the name follows the query
descriptionstring
The segment in a paragraph of up to 1,200 characters: kind of firm, headline, titles, size, countries, who is left out. New people are searched by it; null removes it

POST/api/v1/segments/agent

Say a line to the segment agent: the answer is a run id, the turn runs after

textstring
What to tell the agent; with a segment and no text, its conversation opens
runstring
The run to speak in; without it, a new one
segmentstring
The segment the conversation is about
freshboolean
A new conversation for the segment instead of the earlier one
200
200 {
  "run": "5b2e…",
  "status": "working"
}

GET/api/v1/segments/agent/{run}

The segment agent’s conversation: lines, spend, people in the segment

200
200 {
  "run": "5b2e…",
  "status": "idle",
  "failure": null,
  "doing": null,
  "talk": [{ "who": "agent", "text": "…", "at": "2026-10-22T09:14:03Z", "aside": false }],
  "spent": { "usd": 1.4, "capUsd": 20, "credits": { "prospeo": 12 } },
  "segment": { "id": "a91f…", "people": 48 }
}

POST/api/v1/segments/agent/{run}/stop

Stop the segment agent’s turn

——
No body needed

GET/api/v1/segments/{id}

A segment: its words, who is in it and its campaigns

PATCH/api/v1/segments/{id}

Keep a draft, replace a segment with it or switch refilling

savedboolean
true keeps the draft
refillboolean
Whether the segment takes in new people itself
replacesstring
The saved segment this draft replaces
companiesstring[] | null
The companies to look for new people in: domains, or names where there is no domain; search and collection from every source apply the list to email on their own; null removes the list

DELETE/api/v1/segments/{id}

Delete a segment; its people stay in the contacts, and a running campaign’s segment only after a pause

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

GET/api/v1/segments/{id}/people

The people of a segment

cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most

POST/api/v1/segments/{id}/people

Put your own contacts into a segment

peoplestring[]required
Contact ids

PATCH/api/v1/segments/{id}/people

Take people out of a segment or bring them back

peoplestring[]required
Contact ids
keepbooleanrequired
false takes out, true brings back

POST/api/v1/segments/{id}/stop

Stop a segment’s searches

——
No body needed

POST/api/v1/segments/{id}/priority

Who a segment takes first: countries and roles

countriesstring[]
ISO country codes, highest first
rolesstring[]
Roles in words, highest first

POST/api/v1/segments/{id}/find

Look for new people: filters and how many are found

wordsstring
Who to look for, in words
channelstring
linkedin or email
filtersobject
Search filters, as find returned them

POST/api/v1/segments/{id}/find/add

Add found people to the contacts and the segment

channelstring
linkedin or email
filtersobject
Search filters, as find returned them
wantinteger
How many people to add
runstring
A run to continue

POST/api/v1/segments/{id}/sample

Free sample: ten people by the segment's description, with the judge's score

filtersobject
Search filters, as find returned them

POST/api/v1/segments/{id}/sample/nod

Nod on a sample person: ours or not ours

personobjectrequired
The sample person, as sample returned them
nodstringrequired
ours, notOurs, or null to take the nod back

POST/api/v1/segments/{id}/sample/edit

Propose a description edit from the nods, or accept one

acceptstring
The accepted description; without it, only a proposal

POST/api/v1/segments/{id}/search

Search every source: a block per source and the total; email also by the segment's company list

wordsstringrequired
Who to look for, in words
sourcesstring[]
Where to collect: linkedin, email, instagram, telegram
filtersobject
LinkedIn and email filters in place of those made from the words; email without companies of its own takes the segment's list
emailboolean
Look up work addresses
seedsobject[]
Instagram accounts where the audience gathers
telegramobject
Telegram: words, country, languages and group ids
200
200 {
  "collecting": false,
  "sources": [
    {
      "source": "linkedin",
      "available": "on",
      "result": "found",
      "count": 1200,
      "rough": false,
      "perMonth": 600,
      "collects": true,
      "want": 600,
      "unsearched": ["gender"],
      "people": [
        { "name": "Mira K.", "title": "Head of Wellness", "company": "Sattva", "place": "Dubai" }
      ],
      "room": { "left": 600, "capacity": 600, "until": "2026-11-07T00:00:00Z" },
      "filters": { "country": ["AE"], "industry": ["wellness"] }
    },
    {
      "source": "telegram",
      "available": "connect",
      "result": "connect",
      "count": null,
      "collects": false,
      "want": 0,
      "perDay": 0,
      "look": { "words": ["wellness dubai"], "country": "AE", "languages": ["en"] },
      "groups": [
        {
          "id": "1001",
          "title": "Wellness Dubai",
          "username": "wellnessdxb",
          "kind": "chat",
          "members": 18500,
          "picked": true,
          "unfit": null
        }
      ]
    }
  ],
  "total": {
    "people": 1200,
    "shared": 0,
    "bySource": [{ "source": "linkedin", "count": 1200, "rough": false }]
  },
  "unmatched": [],
  "unparsed": false
}

POST/api/v1/segments/{id}/search/collect

Collect from every source: a search and a run per source

wordsstringrequired
Who to look for, in words
sourcesstring[]
Where to collect: linkedin, email, instagram, telegram
filtersobject
LinkedIn and email filters in place of those made from the words; email without companies of its own takes the segment's list
emailboolean
Look up work addresses
seedsobject[]
Instagram accounts where the audience gathers
telegramobject
Telegram: words, country, languages and group ids
200
200 {
  "search": "7d1e…",
  "runs": [
    { "id": "a2c4…", "source": "linkedin", "state": "running", "want": 600, "added": 40 }
  ],
  "waiting": [{ "id": "b3d5…", "source": "telegram", "want": 300 }],
  "total": { "people": 1500, "shared": 0, "bySource": [] }
}

GET/api/v1/segments/{id}/search

Search progress: count, outcome and drop reasons by source

200
200 {
  "collecting": false,
  "sources": [
    {
      "source": "linkedin",
      "state": "done",
      "added": 1140,
      "want": 1200,
      "resumes": null,
      "eta": { "unit": "minutes", "count": 4, "kind": "took" },
      "outcome": "done",
      "looked": 1200,
      "entered": 1140,
      "addresses": null,
      "places": null,
      "dropped": [{ "reason": "known", "count": 60 }],
      "unsearched": ["gender"]
    }
  ],
  "total": { "people": 1140, "twice": 0 },
  "pace": [{ "source": "linkedin", "perDay": null }]
}

GET/api/v1/campaigns

The campaigns of the workspace

POST/api/v1/campaigns

Create a campaign draft

namestring
Name in the list
goalstring
What the campaign is for, in words
segmentstring
A saved segment
agentstring
The agent who writes

GET/api/v1/campaigns/{id}

A campaign in full: letters, term and funnel

200
200 {
  "campaign": {
    "id": "a7c3…",
    "name": "Webinar on 14 October",
    "goal": "invite heads of marketing to the webinar",
    "kind": "outreach",
    "state": "running",
    "segment": "5e0b…",
    "agent": "2f91…",
    "senders": ["c0d4…"],
    "people": 412,
    "missing": [],
    "term": { "weeks": 5, "ends": "2026-11-02", "approx": false },
    "materials": [{ "id": "9b2e…", "kind": "file", "name": "Price list.pdf", "chars": 1762, "text": "Starter — 490 € a month…" }],
    "progress": { "people": 412, "written": 96, "replied": 14, "declined": 3, "interested": 6, "meetings": 2 }
  }
}

PATCH/api/v1/campaigns/{id}

Change a campaign; each change is a line in the journal

goalstring
What the campaign is for, in words
segmentstring
A saved segment
agentstring
The agent who writes
sendersstring[]
Connections to write from
addRulesstring[]
Instructions for every letter
addTextsstring[]
Own text for the letters, word for word, up to 4000 characters
materialsobject[]
Texts the letters rely on
sendstring
How it sends: all, first_n or auto; a running one only stricter
acquaintedstring
People already talked to: thread writes in their conversation, skip leaves them out
inviteNoteboolean
LinkedIn request with a message or without
responsiblestring | null
The member responsible for the campaign; null clears it
arrivedinteger | null
How many arrived, counted by hand; null clears it
deadlinestring | null
Last day of first letters, YYYY-MM-DD; null clears it

DELETE/api/v1/campaigns/{id}

Delete a campaign; a running one only after a pause

confirmedbooleanrequired
true is the person’s yes to this deletion, in the body or as ?confirmed=true; without it nothing is deleted

POST/api/v1/campaigns/{id}/state

Pause, resume or finish; starting a draft returns the Check and start link

tostringrequired
running, paused or done; running resumes a paused campaign (send and confirmed) and returns a link for a draft
confirmedboolean
true is the person’s yes to resuming; needed only for running on a paused campaign

POST/api/v1/campaigns/{id}/letters

Write example letters or agree to them

writeinteger[]
Places of the letters to write: 1, 2, 3
agreeinteger[]
Places of read letters to agree to

GET/api/v1/campaigns/{id}/touches

Touches of a campaign: queue and log

statestring
queued, sending, sent, failed or review (written and waiting for approval)
cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most

POST/api/v1/campaigns/{id}/touches/{touch}

A campaign letter: skip, recall or undo an approval

dostringrequired
skip, recall or undo

GET/api/v1/campaigns/{id}/talk

The conversation with the campaign assistant

POST/api/v1/campaigns/{id}/talk

Say a line to the campaign assistant: the answer is the whole turn with the card and letters

textstringrequired
What to tell the assistant, in words
200
200 {
  "campaign": "7c0d…",
  "reply": "…",
  "card": { "name": "…", "goal": "…" },
  "letters": [],
  "trouble": null,
  "untaken": [],
  "handoff": null
}

POST/api/v1/campaigns/{id}/review

Release letters from review: approve, send by themselves, or ten more

dostringrequired
send, trust or more; send and trust reach people and need send and confirmed
touchesstring[]
Letters for send — only the ones named go
subjectstring
Edited subject of one letter
bodystring
Edited text of one letter
atstring
Not before this moment — for one letter
confirmedboolean
true is the person’s yes to exactly these letters; without it nothing goes
200
200 {
  "campaign": "7c0d…",
  "touches": ["e41a…"],
  "state": "approved",
  "goes": null
}

POST/api/v1/campaigns/{id}/touches/{touch}/rewrite

Rewrite a letter in review by the person’s words, without storing it

textstringrequired
What to change in the letter
200
200 {
  "touch": "e41a…",
  "draft": "Hi Anna, the webinar on the 14th is about…",
  "subject": "Webinar on 14 October"
}

GET/api/v1/campaigns/shares

Shared campaign accounts and each campaign’s share

POST/api/v1/campaigns/shares

Set campaign shares on shared accounts

sharesobjectrequired
Account → campaign → percent; every campaign of the account, a hundred in sum

POST/api/v1/conversations/{id}/tasks

A task for the agent: what to write and when

textstringrequired
What to write — in words for the agent
duestring | null
When; a due moment needs the send scope

GET/api/v1/notifications

Your notifications, newest first

POST/api/v1/notifications/read

Mark notifications read

idsstring[]
Which ones; without a list — all unread

GET/api/v1/calendar

Calendars and the meetings of the window

fromstring
Meetings ending after this moment
tostring
Meetings starting before this moment

POST/api/v1/calendar/meetings

Book a meeting; needs the send scope

titlestringrequired
Meeting title
daystringrequired
Day YYYY-MM-DD in the workspace's zone
startstringrequired
Start HH:MM in the workspace's zone
minutesintegerrequired
Length: 15, 30, 45, 60, 90 or 120
guestsstring[]
Guest addresses, twenty at most
agendastring
Agenda — the meeting's description
calendarstring
Calendar; without it — your own
checkboolean
true — only show what would happen

DELETE/api/v1/calendar/meetings/{id}

Cancel a meeting the product booked; needs the send scope

GET/api/v1/meetings

Meetings with their outcome, however long ago

outcomestring
Meeting outcome: held, missed, unknown, unconfirmed
factstring
The person's result: met, booked, missed, unconfirmed
fromstring
Meetings from this moment
tostring
Meetings before this moment
cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most

POST/api/v1/meetings/outcomes

Record the outcome of meetings in a batch

meetingsobject[]required
Rows: meeting, outcome (held or missed), source, note
drybooleanrequired
true — count without writing anything
200
200 {
  "outcomes": {
    "dry": true,
    "counts": { "written": 280, "same": 0, "decided": 12, "unseen": 0, "bad": 0 },
    "people": 274,
    "rows": [{ "meeting": "9b1e…", "state": "written" }]
  }
}

GET/api/v1/senders/health

Account health: level, limits and why

200
200 {
  "checked": true,
  "accounts": [
    {
      "connection": "c0d4…",
      "channel": "linkedin",
      "account": "anna@acme.example",
      "health": {
        "level": "normal",
        "limits": { "day": 15, "week": 70 },
        "weakest": ["acceptance"],
        "signals": [{ "key": "acceptance", "level": "caution", "score": 48, "facts": { "rate": 0.21 } }]
      },
      "used": { "day": 6, "week": 31 }
    }
  ]
}

POST/api/v1/senders/health/check

Check the accounts again

connectionstring
One connection; without it — all

GET/api/v1/senders/voices

Sender voices

GET/api/v1/channels/telegram-folders

Telegram folders with conversation counts

GET/api/v1/senders

Senders: who letters go out in the name of, their accounts and companies

POST/api/v1/senders

Add a sender without a login; the owner adds them

namestringrequired
What they are called
rolestring
Job title
signaturestring
How they sign a letter

POST/api/v1/senders/channels

Pin accounts to a sender, or unpin them

senderstring | nullrequired
Sender id; null unpins
channelsstring[]required
Account ids from GET /channels

POST/api/v1/senders/{member}/companies

A sender's companies — the whole list

companiesstring[]required
Company ids; an empty list means none

PATCH/api/v1/senders/{member}/voice

A sender's own corrections to their voice

channelstringrequired
Kind of channel: email, linkedin, telegram, whatsapp or instagram
ownstringrequired
The corrections in the person's words, up to 4000 characters

PUT/api/v1/senders/{member}/voice/borrow

The sender writes in a colleague's voice in a channel; from: null gives back their own.

channelstringrequired
Kind of channel: email, linkedin, telegram, whatsapp or instagram
fromstring | nullrequired
The sender whose voice to write with, or null for their own.
fromChannelstring
The colleague's voice channel; defaults to the same one.

POST/api/v1/senders/{member}/voice/learn

Retrain the voice now — teacher and judge, up to a minute

channelstringrequired
Kind of channel: email, linkedin, telegram, whatsapp or instagram

POST/api/v1/senders/{member}/voice/check

A dry run: one letter in the sender's voice, nothing is written

channelstringrequired
Kind of channel: email, linkedin, telegram, whatsapp or instagram
turnsobject[]required
The conversation: from — them or us, and body; up to 20 turns
lapseboolean
true — the contact has been silent for a week, and the sender writes again

PATCH/api/v1/channels/{connection}/limits

An account's limits and hands-off

dailyinteger | null
Letters a day; null — by the account's health
hourlyinteger | null
Letters an hour; null — by the account's health
gapinteger | null
Minutes between letters; null — by the account's health
newPerDayinteger | null
New people a day, instead of the automatic limit; null — by the account's health
handsOffboolean
true — agents do not write from this account; taking it off needs the send scope
confirmedboolean
true — the user's yes to agents writing to people

PATCH/api/v1/channels/{connection}/groups

Whether the agent replies in groups; allowing it needs the send scope

replybooleanrequired
true — the agent replies in groups
confirmedboolean
true — the user's yes to agents writing to people

POST/api/v1/channels/repair

Repair a broken connection

connectionstringrequired
Connection id from GET /channels

PATCH/api/v1/channels/{connection}/warmup

Warm-up of an email account at the vendor

onbooleanrequired
true — on, false — off

POST/api/v1/channels/zapmail

Connect Zapmail mailboxes

onlystring[]
Addresses of the mailboxes, when not all of them are wanted. Without it — every one not connected yet

PATCH/api/v1/channels/{connection}/country

Proxy country of a LinkedIn or Instagram account

countrystringrequired
Two-letter country code
confirmedbooleanrequired
true — the user's yes to changing the country at the vendor

GET/api/v1/channels/telegram-folders

Telegram folders with conversation counts

GET/api/v1/routing

Routing rules and the switch

POST/api/v1/routing

Change the rules

dostringrequired
add, drop, up, down, order or switch
fieldstring
label, side, product, sender, company or agent
valuestring
Id of the label, product, sender, company, agent, or the side
memberstring
Who gets the conversation
rulestring
One rule
rulesstring[]
Every rule in the new order
enabledboolean
Switch on or off

POST/api/v1/routing/apply

Hand out unowned conversations by the rules; needs the send scope

rulestring
One rule
depthstring
all, quarter, month or week
cursorstring
From the previous answer, to go on
floorstring
From the previous answer, to go on

POST/api/v1/people

Add a person to the contacts

namestring
Name
emailstring
Email, phone or LinkedIn — at least one
phonestring
Email, phone or LinkedIn — at least one
linkedinstring
Email, phone or LinkedIn — at least one
companystring
Company
intostring
Add to this person instead of creating one

POST/api/v1/people/upsert

A row from Clay or another table: find or add the person and write their fields

emailstring
Email, phone or LinkedIn — at least one
linkedinstring
Email, phone or LinkedIn — at least one
phonestring
Email, phone or LinkedIn — at least one
namestring
Name
first_namestring
First name, when it is apart from the last
last_namestring
Last name
companystring
Company
titlestring
Job title
fieldsobject
Your own fields as name: value pairs, up to 60. An unknown name adds the field; an empty value leaves what is written
segmentstring
The segment the person enters
campaignstring
A campaign: the person enters its segment, and a running campaign picks them up within the hour
200
200 { "person": "4b1d…", "verdict": "merge", "fields": { "written": ["Recent funding", "Opening line"], "cut": [] }, "entered": 1 }

POST/api/v1/people/import

A contacts file or list: plan or write

fileobject
name and content in base64, up to three megabytes
rowsobject[]
People as a list instead of a file, up to 500 per call: name, email, phone, linkedin, company, title, city
columnsobject[]
The plan's columns, edited by you
commitboolean
true — write

POST/api/v1/people/fields

Load people's own fields from a file: the key is an email, a LinkedIn profile or a company domain; without commit — how many match and would be written

fileobject
name and content in base64 — a small file
urlstring
An https link to a CSV or XLSX up to 50 MB — for a large file
rowsobject[]
Rows as objects instead of a file: the key and fields by name
keystring
The key column's header, when it should not be guessed
columnsobject
Header → field (name, field:<uuid>) or null to skip it
frominteger
next from the previous answer — to continue a long file
commitboolean
true — write
200
200 { "rows": 8000, "matched": 7412, "people": 51230, "writes": 102460, "same": 0, "missing": { "count": 588, "sample": [{ "line": 14, "key": "acme.com" }] }, "next": null }

POST/api/v1/people/refresh

Refresh cards from the LinkedIn profile: the city, and the job title and company where empty

segmentstring
The segment whose people to refresh
peoplestring[]
Or person ids as a list
commitboolean
true — read the profiles and write; without it — how many people and the cost
cursorstring
The previous answer's cursor — continue from it

GET/api/v1/archive

Where the archive review stands

POST/api/v1/archive

Go through the archive: download from a day, then review — needs the send scope; or leave it

dostringrequired
start — go through the archive, decline — leave it while it downloads
sincestring
First day to download, YYYY-MM-DD
channelsstring[]
Channels to download from; left out — all that keep history
groupsboolean
Take Telegram groups
freshboolean
Also review past conversations for new products

GET/api/v1/dashboard

Dashboard numbers

daysinteger
Days of the chart; 14 by default

GET/api/v1/billing

Subscription and accounts — read only

GET/api/v1/setup

What is set up on the way to the first campaign and what is missing

GET/api/v1/events

The journal of events about people: what came in and what the rules did

personstring
Only this person's events
typestring
Only events of this type
sourcestring
Only events from this source
cursorstring
Where to continue from: next from the previous answer
limitinteger
How many rows to return: 50 by default, 200 at most
200
200 {
  "events": [
    {
      "id": "9b1c…",
      "person": "3f2a…",
      "source": "11aa…",
      "externalId": "evt_1042",
      "type": "signed_up",
      "email": "anna@example.com",
      "name": "Anna",
      "data": { "plan": "free" },
      "occurredAt": "2026-10-10T09:12:00.000Z",
      "receivedAt": "2026-10-10T09:12:01.000Z",
      "outcomes": [{ "rule": "5c0e…", "action": "segment", "outcome": "done" }],
      "quiet": null
    }
  ],
  "next": null
}

GET/api/v1/events/rules

Event rules in the order they run

POST/api/v1/events/rules

Create an event rule; it runs last

typestringrequired
The event type exactly as the source sends it: signed_up, scan_ready
actionstringrequired
What to do: tag, segment, campaign or task
sourcestring
Source; without it the rule listens to any
tagstring
For tag: the tag. A product tag goes on the person and needs side; a correspondence tag goes on the conversation, so a person with no correspondence cannot get it
sidestring
Required for a product tag: buyer or seller
segmentstring
For segment: the segment the person enters
campaignstring
For campaign: the person enters the campaign's segment, and a running campaign picks them up within the hour
textstring
For task: what the agent running the person's conversation is asked to do, up to 500 characters. With no conversation there is no task
delay_minutesinteger
For task: minutes after the event before the task is due; 0 means at once
agentstring
For task: the agent to put on a conversation with none when no campaign wrote to the person; the campaign's agent goes first. Empty: only that one
enabledboolean
Whether the rule acts; yes by default

PATCH/api/v1/events/rules/{id}

Rewrite an event rule whole

typestringrequired
The event type exactly as the source sends it: signed_up, scan_ready
actionstringrequired
What to do: tag, segment, campaign or task
sourcestring
Source; without it the rule listens to any
tagstring
For tag: the tag. A product tag goes on the person and needs side; a correspondence tag goes on the conversation, so a person with no correspondence cannot get it
sidestring
Required for a product tag: buyer or seller
segmentstring
For segment: the segment the person enters
campaignstring
For campaign: the person enters the campaign's segment, and a running campaign picks them up within the hour
textstring
For task: what the agent running the person's conversation is asked to do, up to 500 characters. With no conversation there is no task
delay_minutesinteger
For task: minutes after the event before the task is due; 0 means at once
agentstring
For task: the agent to put on a conversation with none when no campaign wrote to the person; the campaign's agent goes first. Empty: only that one
enabledboolean
Whether the rule acts; yes by default

DELETE/api/v1/events/rules/{id}

Remove an event rule; the journal stays

GET/api/v1/events/sources

Event sources with their address and headers, without secrets

POST/api/v1/events/sources

Create an event source; the signing secret comes in the answer once

namestringrequired
What the source is, up to 80 characters: Website signups
200
200 {
  "source": {
    "id": "11aa…",
    "name": "Website signups",
    "createdAt": "2026-10-10T09:00:00.000Z",
    "revokedAt": null,
    "signing": {
      "endpoint": "https://nitrino.ai/events/incoming",
      "headers": { "nitrino-workspace": "9f1c…", "nitrino-source": "11aa…", "nitrino-signature": "t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + '.' + raw body)>" },
      "secret": "c2f0…"
    }
  }
}

DELETE/api/v1/events/sources/{id}

Revoke a source: its events get 401 from that second on

Pages

Lists are paged by cursor. Take next from the answer and send it back as it is. An empty next means there is nothing further.

Repeats

Everything that changes requires an Idempotency-Key header. A repeat with the same key returns the earlier answer and does not do the work twice.

Campaigns write only once started

A campaign draft writes to nobody. Starting and resuming need the send scope together with campaigns:write. Letters go out on weekdays, one person at a time, within each account's health: the daily limit is set by health, not by the program.

What else needs send

Besides a message, these actions need send: approving the agent's draft, forwarding a message and reacting to one, a meeting and its cancelling (Google writes to the guests), an agent task with a due moment, handing out the pile by the routing rules, an archive review, an agent put on a conversation or let back to writing — in one conversation, in many, or on an account, letting the agent reply in groups and taking hands-off away from an account, approving campaign letters from review, letting a campaign send without review, resuming a campaign, and inviting a person into the workspace. Most of them also need confirmed, the person’s yes to exactly this; each route says what it asks.

Clay and other tables

Enrichment from Clay reaches campaign letters through the campaign's own address. On the campaign card, open Data from Clay and press Connect Clay: an address appears, and it is the key. In the Clay table, add an HTTP API column: method POST, this address, and a flat JSON body with the name on the left and a Clay column via “/” on the right. Email, LinkedIn, phone, name and company are recognised by the key's name; any other key becomes a card field under its own name, and a campaign letter reads all of the person's fields. A new person joins the campaign's segment. A field's description is your word to the letter on how to use it: Describe sits next to the field on the same screen. Sending a row again is harmless: the same person, the same values. For a table without a campaign, use POST /people/upsert with an API key.

Clay · HTTP API
POST https://nitrino.ai/api/v1/people/upsert
Authorization: Bearer <API key>

{
  "email": "{{Work Email}}",
  "linkedin": "{{LinkedIn Profile}}",
  "first_name": "{{First Name}}",
  "last_name": "{{Last Name}}",
  "company": "{{Company Name}}",
  "title": "{{Job Title}}",
  "campaign": "<campaign id>",
  "fields": {
    "Recent funding": "{{Recent Funding}}",
    "Opening line": "{{Personalized Opener}}"
  }
}

Events about people

Your system tells Nitrino that a person did something on your side: signed up, started a scan. A source is created in Settings, Events, or with POST /events/sources, and its signing secret is shown once. An event goes to /events/incoming with the workspace, source and signature headers: t is the time in seconds, v1 is the HMAC-SHA256 of the secret over t, a dot and the raw body. An unsigned event or one signed more than five minutes ago gets 401 and is not written. The body is one event, an array of up to a hundred, or {"events": [...]}: type and email are required, and so is id or occurred_at: they are how a repeat is recognised, and a repeat does nothing twice. With neither, the event is refused with the word id. name and data are optional. The person is found by email or created, the rules decide what to do, and the agent sees the event in its next letter.

curl
BODY='{"id":"evt_1042","type":"signed_up","email":"anna@example.com","data":{"plan":"free"}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST https://nitrino.ai/events/incoming \
  -H "Content-Type: application/json" \
  -H "Nitrino-Workspace: <workspace id>" \
  -H "Nitrino-Source: <source id>" \
  -H "Nitrino-Signature: t=$T,v1=$SIG" \
  -d "$BODY"
Node.js
const { createHmac } = require("node:crypto");

const body = JSON.stringify({ id: "evt_1042", type: "signed_up", email: "anna@example.com" });
const t = Math.floor(Date.now() / 1000);
const v1 = createHmac("sha256", process.env.NITRINO_EVENT_SECRET).update(t + "." + body).digest("hex");

fetch("https://nitrino.ai/events/incoming", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Nitrino-Workspace": "<workspace id>",
    "Nitrino-Source": "<source id>",
    "Nitrino-Signature": "t=" + t + ",v1=" + v1,
  },
  body,
});
API reference · Nitrino