API reference
Log activity
Tell mySDR what you did or heard outside it: an email you sent, a reply, a bounce, a meeting, not interested, an out-of-office, an unsubscribe, a referral, or a note. It can be about a person (lead), a company (company, e.g. when you mailed its general address) or just an address (email). Someone not in the Leads or Companies table yet is added. Log every email you send, with step, template and thread_id, so POST /outreach/check knows what went out. Log your research steps too, as a note on the company with source_url (the careers page you counted, the annual accounts you read). A note without lead, company or email is your work log: a run you finished, a source you tried, a decision the user made. The user sees every line on the Activity tab. Send one activity, or { "activities": [...] } with up to 100 in order; each is logged on its own. mySDR keeps your whole process, so the user never needs a spreadsheet next to it: the state of each company and person (status with a reason, next action, email and contact points, your own fields), every email, reply and bounce you log, the exclusions, and a check before every send. The user sees all of it in the Leads and Companies tabs.
What an activity does besides the log line:
email_sent— withtext, the email shows in the mySDR Inbox and is kept, so you can read it back (GET /outreach/activity?with_text=true); a new address becomes the person's email or a contact point of the company; a planned email (next_action.email) with the samestepis cleared (logged without astep, one planned without a step).email_replied— stops LinkedIn for that person in the whole organization; a planned follow-up is cleared; withlead_statusthe row gets it (laterwithuntil= recontact on that day).email_bounced— the address gets the statusbouncedand goes on the exclusions; a planned follow-up is cleared.meeting_booked— stops LinkedIn; lead statusmeeting.not_interested— stops LinkedIn and blocks a new sequence; lead statusnot_interested; the next action is cleared.out_of_office— away untiluntil; a next action before that day moves to it.unsubscribed— the address goes on the exclusions; stops LinkedIn; the next action is cleared.referral—referral(the person they sent you to) lands in Leads at the same company and in the same campaign (referral_rowin the answer), with a line in their log. It counts as the referrer's answer: their planned follow-up is cleared, and POST /outreach/check does not block the person they referred you to over what already went to that company.note— only the log line. Withoutlead,companyoremailit is a line of your work log.
Someone not in the table yet is added with the address you give, also with email_replied, not_interested or out_of_office. Give their employer in lead.company and lead.company_domain; without a domain, the domain of a work address is used, so their emails count for that company in POST /outreach/check.
A step of your own that is none of these types (a call, a voicemail, a LinkedIn message you sent yourself, a visit) goes in action: a key you choose, such as call or linkedin_message, with action_label for how it reads (default from the key). Leave type out, or send note: an action is a note, so it stops nothing and changes no state, but the user can filter and count it on the Activity tab, and GET /outreach/activity filters on it. Use the same key every time.
external_id (your own id, e.g. the email message id) makes a repeated call count once: the same type about the same person or address. Another type or another person with the same external_id (a bounce that quotes the message id of your email) is logged on its own.
Authentication
Send Authorization: Bearer gtk_…. Create the key under Connect assistant → API keys. Every gtk_ key can call these routes, also while the Signals API is closed. A missing, invalid or revoked key is 401.
Body parameters
typestring— What happened. Without it,actionis required (then it is anote). One of:email_sent,email_replied,email_bounced,meeting_booked,not_interested,out_of_office,unsubscribed,referral,note.actionstring— A step of your own: lowercase letters, digits and_, starting with a letter, up to 40 characters (call,voicemail,linkedin_message,visit). Only withtypenoteor notype; not one of the types above.action_labelstring— How the action reads in the table, up to 60 characters (Call). Default from the key.leadstring | object— The person: a row (sav_…,per_…), a LinkedIn URL or their address as a string; or{ ref, lead_id, person_id, linkedin_url, name, first_name, last_name, company, company_domain, title, email }.companystring | object— Or the company:sav_…,cmp_…, its domain, page or name; or{ ref, name, domain, linkedin_url }. Not together withlead: a person's employer goes inlead.companyandlead.company_domain.emailstring— The address you mailed or that wrote back. Alone it finds the person or company that has it.subjectstring— The subject, up to 200 characters.summarystring— One or two sentences for the table, up to 1,000 characters.textstring—email_sentandemail_replied: the email itself (without the quoted history), up to 20,000 characters; it shows in the Inbox (anemail_sentalso on Templates, above the email you planned next), and GET /outreach/activity?with_text=true gives it back.occurred_atstring— ISO time; default now. Never in the future.campaign_idstring— The campaign it belongs to.external_idstring— Your own id, to log it once (per type and person).stepinteger— Which step of your sequence, from 1 (the first email) to 20.templatestring— The name of your template, up to 80 characters.thread_idstring— The email thread, so replies and follow-ups stay together.sentimentstring—email_replied: how it reads. One of:positive,neutral,negative.untilstring—out_of_office: the day they are back.email_repliedwithlead_statuslater: the day to recontact.lead_statusstring—email_replied: what they said. One of:interested,meeting,later,not_interested.referralobject—referral:{ name, title, linkedin_url, email, note }of the person they sent you to.source_urlstring— Where you found it or did it: an http(s) address of up to 1,000 characters (the careers page, the annual accounts). Another source, such as a file or "Stripe", goes insummary.
Response
idstring· required — The log line.duplicateboolean· required — This activity (sameexternal_id, type and person) was logged before; nothing changed.idandroware those of the earlier line.rowobject | null· required — The row it is about:id,object,name.added_to_tableboolean· required — The row is new in Leads or Companies.stopped_linkedin_sequencesinteger· required — LinkedIn sequences that stopped.in_inboxboolean· required — The email shows in the Inbox.referral_rowobject | null· required —referral: the row of the person they sent you to (id,name), to mail or update.changesstring[]· required — What changed on the row, in a few words.dashboard_urlstring· required — The table in mySDR; for a work log line or a list of activities, the Activity tab.logged / failed / resultsinteger / integer / object[]— Withactivities: how many were logged, how many failed, and per itemindex,okand the fields above orerror.
Credits
Free. This call never uses credits.
Errors
- 400
invalid_request— An unknown field or type, notypeand noaction, anactionwith another type thannoteor one that is a type itself,leadandcompanytogether, nobody to log it about (only anotecan be about nobody),texton a type that is not an email,lead_statuswithoutemail_replied, asource_urlthat is not an http(s) address, anemailthat is not a bare address, or a value that is not valid. - 404
not_found— The person, company or campaign is not in your organization, or no row has this address or its domain. - 409
invalid_state— The key has no user behind it, so mySDR cannot record who did it. - 401
authentication_error— The Authorization header is missing, or the key is unknown or revoked. - 403
permission_error— API access is not part of your plan, or your account is suspended. - 429
rate_limit_error— More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header. - 503
service_unavailable— The API is temporarily unavailable. Retry later. - 500
internal_error— Something went wrong on our side.
Example
cURL
curl -X POST "https://mysdr.app/api/v1/outreach/activity" \
-H "Authorization: Bearer $MYSDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "email_sent",
"company": {
"name": "Acme Logistics",
"domain": "acme-logistics.example"
},
"email": "jobs@acme-logistics.example",
"subject": "Your open roles",
"text": "Hi team, …",
"step": 1,
"template": "step1_jobs_inbox",
"thread_id": "thread-88",
"external_id": "<msg-88@mail.example>"
}'
JavaScript
const response = await fetch('https://mysdr.app/api/v1/outreach/activity', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"type": "email_sent",
"company": {
"name": "Acme Logistics",
"domain": "acme-logistics.example"
},
"email": "jobs@acme-logistics.example",
"subject": "Your open roles",
"text": "Hi team, …",
"step": 1,
"template": "step1_jobs_inbox",
"thread_id": "thread-88",
"external_id": "<msg-88@mail.example>"
})
});
const body = await response.json();
Python
import json
import os
import urllib.request
payload = {
"type": "email_sent",
"company": {
"name": "Acme Logistics",
"domain": "acme-logistics.example"
},
"email": "jobs@acme-logistics.example",
"subject": "Your open roles",
"text": "Hi team, …",
"step": 1,
"template": "step1_jobs_inbox",
"thread_id": "thread-88",
"external_id": "<msg-88@mail.example>"
}
request = urllib.request.Request(
"https://mysdr.app/api/v1/outreach/activity",
headers={
"Authorization": f"Bearer {os.environ['MYSDR_API_KEY']}",
"Content-Type": "application/json"
},
data=json.dumps(payload).encode(),
method="POST",
)
with urllib.request.urlopen(request) as response:
body = json.load(response)
Example response
{
"id": "a71c2f40-8d2e-4b91-9a10-3c5e7d9f0b21",
"duplicate": false,
"row": {
"id": "sav_2b9e8d10-7f4a-4a51-8c2e-5d1f0a3b6c77",
"object": "company",
"name": "Acme Logistics"
},
"added_to_table": true,
"stopped_linkedin_sequences": 0,
"in_inbox": true,
"referral_row": null,
"changes": [
"contact point updated"
],
"dashboard_url": "https://mysdr.app/en/gtm/companies"
}