API reference
Update a company
Changes where a company stands in your work: its status with a reason, a snooze until the rescan, what it said, your next action, its general addresses and contact form, your own fields. Send only what changes. {id} is its id, one of its saved_ids, or its domain or LinkedIn page; a company that is not in the table yet is added by its domain or page (pass name). A status change is logged. 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.
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.
Path parameters
idstring· required — The row, a domain or a LinkedIn page (URL-encoded).
Body parameters
statusstring—new(in the work),needs_decision(waits for the user; say why instatus_reason),snoozed(untilsnooze_until, thennewagain) orarchived(out; say why). One of:new,needs_decision,snoozed,archived.status_reasonstring | null— Why, in a few words: "Below 50 FTE", "Rescan 23/11". Up to 500 characters.snooze_untilstring | null— The day the row comes back asnew: a date (2026-11-23, 09:00 Brussels time) or an ISO time. Alone it snoozes the row.lead_statusstring | null— What they said.not_interestedalso stops LinkedIn for them and blocks a new sequence. One of:interested,meeting,later,not_interested.next_actionobject | null— Your next step and when:at(a date or ISO time),kind(follow_up,recontact,rescan,approval,other),note, andemailwhen that step is an email you will send yourself:{ "text", "subject", "step", "template", "to" }(textrequired, up to 20,000 characters;subjectup to 200 andtemplateup to 80, as in POST /outreach/activity;step1 to 20;toonly if it is not the row’s address;kinddefaults tofollow_up). The user sees the planned email on Templates, with the emails you already logged above it, and GET /agenda gives it back on its day, so you keep no copy. A list (GET /tables/leads, /tables/companies) gives it withouttext; one row (GET /tables/leads/{id}, /tables/companies/{id}), the response of a change and GET /agenda give the text. Loggingemail_sentwith the samestepclears it (logged without a step, it clears one planned without a step), and so do a reply, a meeting, a bounce, an unsubscribe, a referral andnot_interested; plan the next one. A newnext_actionreplaces the current one and the email planned for it (replacedin the response gives them back);nullclears both. GET /agenda lists the due ones.out_of_office_untilstring | null— Away until this day (from an out-of-office reply).contact_pointsobject[]— Replaces the list of general addresses and contact forms:{ "email": "jobs@acme.com", "label": "jobs", "source_url": "…", "email_status": "verified" }or{ "form": "https://acme.com/contact" }. Up to 20.add_contact_pointsobject[]— Adds or updates these (the same address or form is updated), keeping the others.fieldsobject— Your own columns, merged with what is there: a name in lowercase letters, digits and_(fte,ats,open_roles) with a value (text, number, true/false) or{ "value", "label", "source_url", "observed_at" };nullremoves one. Up to 40 per row. The user sees them as columns.campaign_idstring— Put the row in this campaign (POST /outreach/campaigns, or one from a LinkedIn sequence).campaign_idsstring[]— Replaces the campaigns of the row.stagestring | null— One of the organization's own stages (GET /stages), by its key (mql) or its name ("MQL");nullclears it. A stage sits next tostatus: it stops nothing and changes no status. A change is logged.ownerstring | null— Who in the team follows the row up:me(the user behind the key), or the email address or user id of a colleague (GET /team);nullclears it. A change is logged.notestring | null— Your note on the row, up to 500 characters.namestring— The name, when the company is added by its domain or page.
Response
idstring· required — The row: what list, PATCH and DELETE take.objectstring· required —personorcompany.namestring· required — Its name.domainstring | null— Its domain.linkedin_urlstring | null— Its LinkedIn page.statusstring· required — Its status now.status_reasonstring | null— Why.snooze_untilstring | null— While snoozed: until when.lead_statusstring | null— What they said.next_actionobject | null—at,kind,note.out_of_office_untilstring | null— While out of office: until when.contact_pointsobject[]— Its general addresses and forms.fieldsobject— Your own fields.campaign_idsstring[]— Its campaigns.stagestring | null— Its stage.owner_idstring | null— The user id of its owner.ownerobject | null— Withownerorstagein the body: the owner (id,name,email).notestring | null— Your note on the row.changedboolean· required — Something changed.replacedobject— When this call set another next action (ornull): the one it replaced,{ next_action: { at, kind, note, email } }, with the full text of its planned email, so you can put it back.added_to_tableboolean· required — The row was not in the table before this call (a company by its domain or page).stopped_linkedin_sequencesinteger— Withlead_statusnot_interested: the LinkedIn sequences that stopped.dashboard_urlstring· required — The table in mySDR.
Credits
Free. This call never uses credits.
Errors
- 400
invalid_request— An unknown field (emailbelongs to a person; a company hascontact_points), nothing to change, or a value that is not valid. - 404
not_found— No company matches, and the id is not a domain or page to add it by. - 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 PATCH "https://mysdr.app/api/v1/tables/companies/acme-logistics.example" \
-H "Authorization: Bearer $MYSDR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "snoozed",
"snooze_until": "2026-11-23",
"status_reason": "Rescan: only 2 open roles",
"add_contact_points": [
{
"email": "jobs@acme-logistics.example",
"label": "jobs",
"source_url": "https://acme-logistics.example/contact",
"email_status": "catch_all"
}
],
"fields": {
"fte": {
"value": 233,
"label": "FTE",
"source_url": "https://example.org/annual-accounts"
},
"ats": "Recruitee"
}
}'
JavaScript
const response = await fetch('https://mysdr.app/api/v1/tables/companies/acme-logistics.example', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"status": "snoozed",
"snooze_until": "2026-11-23",
"status_reason": "Rescan: only 2 open roles",
"add_contact_points": [
{
"email": "jobs@acme-logistics.example",
"label": "jobs",
"source_url": "https://acme-logistics.example/contact",
"email_status": "catch_all"
}
],
"fields": {
"fte": {
"value": 233,
"label": "FTE",
"source_url": "https://example.org/annual-accounts"
},
"ats": "Recruitee"
}
})
});
const body = await response.json();
Python
import json
import os
import urllib.request
payload = {
"status": "snoozed",
"snooze_until": "2026-11-23",
"status_reason": "Rescan: only 2 open roles",
"add_contact_points": [
{
"email": "jobs@acme-logistics.example",
"label": "jobs",
"source_url": "https://acme-logistics.example/contact",
"email_status": "catch_all"
}
],
"fields": {
"fte": {
"value": 233,
"label": "FTE",
"source_url": "https://example.org/annual-accounts"
},
"ats": "Recruitee"
}
}
request = urllib.request.Request(
"https://mysdr.app/api/v1/tables/companies/acme-logistics.example",
headers={
"Authorization": f"Bearer {os.environ['MYSDR_API_KEY']}",
"Content-Type": "application/json"
},
data=json.dumps(payload).encode(),
method="PATCH",
)
with urllib.request.urlopen(request) as response:
body = json.load(response)
Example response
{
"id": "sav_2b9e8d10-7f4a-4a51-8c2e-5d1f0a3b6c77",
"object": "company",
"name": "Acme Logistics",
"domain": "acme-logistics.example",
"linkedin_url": null,
"status": "snoozed",
"status_reason": "Rescan: only 2 open roles",
"snooze_until": "2026-11-23T08:00:00.000Z",
"lead_status": null,
"next_action": null,
"out_of_office_until": null,
"contact_points": [
{
"kind": "email",
"value": "jobs@acme-logistics.example",
"label": "jobs",
"source_url": "https://acme-logistics.example/contact",
"email_status": "catch_all"
}
],
"fields": {
"fte": {
"value": 233,
"label": "FTE",
"source_url": "https://example.org/annual-accounts",
"observed_at": "2026-10-23T09:00:00.000Z"
},
"ats": {
"value": "Recruitee",
"label": null,
"source_url": null,
"observed_at": "2026-10-23T09:00:00.000Z"
}
},
"campaign_ids": [],
"note": null,
"changed": true,
"added_to_table": true,
"dashboard_url": "https://mysdr.app/en/gtm/companies"
}