ReferenceReference by module

Sales API

Every Sales action (31) and data model (11): endpoints, scopes, input, preconditions, events and examples.

Leads, the calls that follow them up, and the partners who send them.

Module code crm · 31 actions · 11 data models · 27 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: crm (everything), crm:read (read only) or crm:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
affiliate_report_linksaffiliate_report_link/api/v1/affiliate_report_links
affiliatesaffiliate/api/v1/affiliatescrm.save_affiliate
call_resultscall_result/api/v1/call_results
call_statusescall_status/api/v1/call_statuses
call_templatescall_template/api/v1/call_templates
callscall/api/v1/calls
lead_cancellationslead_cancellation/api/v1/lead_cancellations
lead_sourceslead_source/api/v1/lead_sources
lead_stageslead_stage/api/v1/lead_stages
leadslead/api/v1/leadscrm.capture_leadcrm.update_leadcrm.archive_lead
lost_reasonslost_reason/api/v1/lost_reasons

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
crm.amend_callcrm:writeCorrect a call
crm.archive_leadcrm:deleteArchive a lead
crm.assign_leadcrm:writeAssign a lead
crm.attribute_leadcrm:writeCredit a lead to an affiliate
crm.cancel_leadcrm:writeCancel a lead
crm.capture_leadcrm:writeOpen a lead
crm.create_call_templatecrm:manageSave a call template
crm.deactivate_affiliatecrm:manageClose an affiliate
crm.deactivate_call_templatecrm:manageClose a call template
crm.deactivate_lost_reasoncrm:manageClose a loss reason
crm.export_callscrm:exportExport calls to Excel
crm.export_leadscrm:exportExport leads
crm.issue_affiliate_report_linkcrm:manageSend an affiliate their report link
crm.log_callcrm:writeLog a call
crm.merge_lost_reasonscrm:manageMerge loss reasons
crm.raise_stale_leadscrm:writeFlag quiet leads
crm.restore_leadcrm:deleteRestore a lead
crm.revoke_affiliate_report_linkcrm:manageClose an affiliate report link
crm.save_affiliatecrm:manageAdd or edit an affiliate
crm.score_leadscrm:writeScore leads
crm.set_call_option_activecrm:manageOpen or close a call option
crm.set_call_resultcrm:manageAdd or change a call result
crm.set_call_statuscrm:manageAdd or change a call outcome
crm.set_lead_sourcecrm:manageAdd or rename a lead source
crm.set_lead_source_activecrm:manageOpen or close a lead source
crm.set_lead_stagecrm:writeMove a lead
crm.set_lead_stage_activecrm:manageOpen or close a stage
crm.set_lead_stage_definitioncrm:manageAdd or change a stage
crm.set_lost_reasoncrm:manageAdd or rename a loss reason
crm.start_trialcrm:writeStart a trial
crm.update_leadcrm:writeEdit a lead

crm.amend_call

Correct a call — Fixes a logged call — and re-runs the auto-close rule, as the source does.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/calls/{id}/actions/amend_call
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.amend_call

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Call · id of call
statusIduuidOutcome · id of call_status
resultIduuidResult · id of call_result
notesstringNotes · max 8000
durationMinutesnumberDuration (minutes) · min 0 · max 1440
isImportantbooleanImportant
followUpDatedateFollow up on
followUpTimestringAt · max 8
followUpNotesstringFollow-up notes · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
call_not_foundThe call must exist.
lead_not_foundIts lead must exist.
call_status_not_availableThe outcome must exist and be active.
call_result_not_availableThe result must exist and be active.
follow_up_in_the_pastA moved follow-up must not land in the past.

Events it publishes: crm.call_amended, crm.lead_stage_changed, crm.lead_lost

What it writes: call (update) — Corrects the call; lead (update) — Re-evaluates the auto-close rule

curl -X POST https://app.getera.io/api/v1/calls/CALL_ID/actions/amend_call \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"statusId":"CALL_STATUS_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_amended",
    "crm.lead_stage_changed",
    "crm.lead_lost"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.calls.amendCall("CALL_ID", {
  statusId: "CALL_STATUS_ID"
})
era calls amend_call CALL_ID \
  --status-id "CALL_STATUS_ID"
tools/call
{
  "name": "era__crm__amend_call",
  "arguments": {
    "id": "CALL_ID",
    "statusId": "CALL_STATUS_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 call_not_found, lead_not_found, call_status_not_available, call_result_not_available, follow_up_in_the_past · 429 slow down

crm.archive_lead

Archive a lead — Takes the lead out of lists without deleting it or its calls.

Needs crm:delete · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/leads/{id}/actions/archive_lead
API key or OAuth token · crm:delete
  • REST alias: DELETE /api/v1/leads/{id}
  • Canonical address: POST /api/v1/actions/crm.archive_lead

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
already_archivedThe lead must still be active.

Events it publishes: crm.lead_archived

What it writes: lead (update) — Stamps archived_at

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/archive_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_archived"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.archiveLead("LEAD_ID")
era leads archive_lead LEAD_ID
tools/call
{
  "name": "era__crm__archive_lead",
  "arguments": {
    "id": "LEAD_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, already_archived · 429 slow down

crm.assign_lead

Assign a lead — Gives the lead an owner, or returns it to the pool.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/assign_lead
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.assign_lead

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead
userIduuidOwner

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
not_a_memberThe owner must be an active member of this workspace.

Events it publishes: crm.lead_assigned

What it writes: lead (update) — Sets the owner

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/assign_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"userId":"USER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_assigned"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.assignLead("LEAD_ID", {
  userId: "USER_ID"
})
era leads assign_lead LEAD_ID \
  --user-id "USER_ID"
tools/call
{
  "name": "era__crm__assign_lead",
  "arguments": {
    "id": "LEAD_ID",
    "userId": "USER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, not_a_member · 429 slow down

crm.attribute_lead

Credit a lead to an affiliate — Says which partner sent the lead; moving existing credit has to be confirmed.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/attribute_lead
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.attribute_lead

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead
affiliateIduuidAffiliate · id of affiliate
replacebooleanMove existing credit

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
lead_archivedAn archived lead is restored first.
affiliate_not_foundThe affiliate must exist and be open.
already_attributedMoving credit from one affiliate to another must be confirmed.
unchangedThe lead must not already be credited that way.

Events it publishes: crm.lead_attributed

What it writes: lead (update) — Sets who referred the lead

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/attribute_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"affiliateId":"AFFILIATE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_attributed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.attributeLead("LEAD_ID", {
  affiliateId: "AFFILIATE_ID"
})
era leads attribute_lead LEAD_ID \
  --affiliate-id "AFFILIATE_ID"
tools/call
{
  "name": "era__crm__attribute_lead",
  "arguments": {
    "id": "LEAD_ID",
    "affiliateId": "AFFILIATE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, lead_archived, affiliate_not_found, already_attributed, unchanged · 429 slow down

crm.cancel_lead

Cancel a lead — Records that the customer withdrew after buying; finance handles the refund.

Needs crm:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/lead_cancellations/actions/cancel_lead
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.cancel_lead

Input

FieldTypeRequiredNotes
iduuidyesLead · id of lead
reasonstringyesReason · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
already_cancelledA lead is cancelled once.

Events it publishes: crm.lead_cancelled

What it writes: lead_cancellation (insert) — Records the cancellation

curl -X POST https://app.getera.io/api/v1/lead_cancellations/actions/cancel_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"id":"LEAD_ID","reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leadCancellations.cancelLead({
  id: "LEAD_ID",
  reason: "Reason"
})
era lead_cancellations cancel_lead \
  --id "LEAD_ID" \
  --reason "Reason"
tools/call
{
  "name": "era__crm__cancel_lead",
  "arguments": {
    "id": "LEAD_ID",
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, already_cancelled · 429 slow down

crm.capture_lead

Open a lead — Starts a sales pursuit for somebody already recognised as a customer.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/actions/capture_lead
API key or OAuth token · crm:write
  • REST alias: POST /api/v1/leads
  • Canonical address: POST /api/v1/actions/crm.capture_lead

Input

FieldTypeRequiredNotes
customerIduuidyesPerson · id of customer
sourceIduuidSource · id of lead_source
stageIduuidStage · id of lead_stage
assignedUserIduuidAssigned to
assignmentNotestringNote for the owner · max 2000
messagestringWhat they said · max 4000
affiliateIduuidReferred by · id of affiliate
trialDurationDaysnumberTrial length (days) · min 0 · max 365

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
customer_not_foundThe person must already exist as a customer.
customer_archivedAn archived person cannot be pursued.
no_open_stageThe workspace must have at least one open stage.
stage_not_availableThe stage must exist and be active.
source_not_availableThe source must exist and be active.
affiliate_not_foundThe referring affiliate must exist.

Events it publishes: crm.lead_captured

What it writes: lead (insert) — Opens the lead

curl -X POST https://app.getera.io/api/v1/leads/actions/capture_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_captured"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.create({
  customerId: "CUSTOMER_ID"
})
era leads create \
  --customer-id "CUSTOMER_ID"
tools/call
{
  "name": "era__crm__capture_lead",
  "arguments": {
    "customerId": "CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 customer_not_found, customer_archived, no_open_stage, stage_not_available, source_not_available, affiliate_not_found · 429 slow down

crm.create_call_template

Save a call template — A quick note the call form can drop in — new, or an existing one edited and reopened.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/call_templates/{id}/actions/create_call_template
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.create_call_template

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Template · id of call_template
namestringyesName · max 255
templateTextstringyesText · max 8000
isActivebooleanIn use

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundAn edited template must exist.

Events it publishes: crm.call_template_changed

What it writes: call_template (insert) — Adds the template; call_template (update) — Or rewrites an existing one, opening or closing it

curl -X POST https://app.getera.io/api/v1/call_templates/CALL_TEMPLATE_ID/actions/create_call_template \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name","templateText":"Text"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_template_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.callTemplates.createCallTemplate("CALL_TEMPLATE_ID", {
  name: "Name",
  templateText: "Text"
})
era call_templates create_call_template CALL_TEMPLATE_ID \
  --name "Name" \
  --template-text "Text"
tools/call
{
  "name": "era__crm__create_call_template",
  "arguments": {
    "id": "CALL_TEMPLATE_ID",
    "name": "Name",
    "templateText": "Text"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

crm.deactivate_affiliate

Close an affiliate — Stops new referrals; open leads keep their attribution.

Needs crm:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/affiliates/{id}/actions/deactivate_affiliate
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.deactivate_affiliate

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Affiliate · id of affiliate

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe affiliate must exist.
already_archivedThe affiliate must still be open.

Events it publishes: crm.affiliate_deactivated

What it writes: affiliate (update) — Closes the affiliate

curl -X POST https://app.getera.io/api/v1/affiliates/AFFILIATE_ID/actions/deactivate_affiliate \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.affiliate_deactivated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.affiliates.deactivateAffiliate("AFFILIATE_ID")
era affiliates deactivate_affiliate AFFILIATE_ID
tools/call
{
  "name": "era__crm__deactivate_affiliate",
  "arguments": {
    "id": "AFFILIATE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_archived · 429 slow down

crm.deactivate_call_template

Close a call template — Takes a template out of the picker.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/call_templates/{id}/actions/deactivate_call_template
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.deactivate_call_template

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Template · id of call_template

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe template must exist.
already_inactiveThe template must still be open.

Events it publishes: crm.call_template_changed

What it writes: call_template (update) — Closes the template

curl -X POST https://app.getera.io/api/v1/call_templates/CALL_TEMPLATE_ID/actions/deactivate_call_template \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_template_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.callTemplates.deactivateCallTemplate("CALL_TEMPLATE_ID")
era call_templates deactivate_call_template CALL_TEMPLATE_ID
tools/call
{
  "name": "era__crm__deactivate_call_template",
  "arguments": {
    "id": "CALL_TEMPLATE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_inactive · 429 slow down

crm.deactivate_lost_reason

Close a loss reason — Takes a reason out of the picker without touching the leads that used it.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/lost_reasons/{id}/actions/deactivate_lost_reason
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.deactivate_lost_reason

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Reason · id of lost_reason

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe reason must exist.
already_inactiveThe reason must still be open.

Events it publishes: crm.lost_reason_changed

What it writes: lost_reason (update) — Closes the reason

curl -X POST https://app.getera.io/api/v1/lost_reasons/LOST_REASON_ID/actions/deactivate_lost_reason \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lost_reason_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.lostReasons.deactivateLostReason("LOST_REASON_ID")
era lost_reasons deactivate_lost_reason LOST_REASON_ID
tools/call
{
  "name": "era__crm__deactivate_lost_reason",
  "arguments": {
    "id": "LOST_REASON_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_inactive · 429 slow down

crm.export_calls

Export calls to Excel — Returns the call log grouped into one workbook sheet per caller.

Needs crm:export · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/actions/crm.export_calls
API key or OAuth token · crm:export

Input

FieldTypeRequiredNotes
outcomeuuidOutcome · id of call_status
callerstringCalled by · max 64
fromdateCalled from
todateCalled until

Events it publishes: crm.calls_exported

curl -X POST https://app.getera.io/api/v1/actions/crm.export_calls \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"outcome":"CALL_STATUS_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.calls_exported"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("crm.export_calls", {
  outcome: "CALL_STATUS_ID"
})
era crm export_calls \
  --outcome "CALL_STATUS_ID"
tools/call
{
  "name": "era__crm__export_calls",
  "arguments": {
    "outcome": "CALL_STATUS_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:export · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

crm.export_leads

Export leads — The lead list as filtered, as rows — the leads screen turns them into a CSV.

Needs crm:export · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/actions/crm.export_leads
API key or OAuth token · crm:export

Input

FieldTypeRequiredNotes
viewstring (enum)View · Allowed values: all, trials, recurring, today, tomorrow, overdue, never, archived
qstringSearch · max 200
stageIduuidStage · id of lead_stage
sourceIduuidSource · id of lead_source
ownerIdstringOwner · max 64
createdFromdateAdded from
createdTodateAdded until
leadIdsobject / arrayOnly these leads

Events it publishes: crm.leads_exported

curl -X POST https://app.getera.io/api/v1/actions/crm.export_leads \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"view":"all"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.leads_exported"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("crm.export_leads", {
  view: "all"
})
era crm export_leads \
  --view "all"
tools/call
{
  "name": "era__crm__export_leads",
  "arguments": {
    "view": "all"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:export · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

Send an affiliate their report link — A link the partner opens without signing in to see their leads and commission. Shown once.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/affiliate_report_links/actions/issue_affiliate_report_link
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.issue_affiliate_report_link
FieldTypeRequiredNotes
affiliateIduuidyesAffiliate · id of affiliate
purposestring (enum)Link for · Allowed values: report, referral
expiresInDaysnumberOpen for (days) · min 1 · max 365

The action is refused with 409 and one of these codes:

CodeRefused when
affiliate_not_foundThe affiliate must exist and be open.

Events it publishes: crm.affiliate_report_link_issued

What it writes: affiliate_report_link (insert) — Stores the hash of a new token

curl -X POST https://app.getera.io/api/v1/affiliate_report_links/actions/issue_affiliate_report_link \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"affiliateId":"AFFILIATE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.affiliate_report_link_issued"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.affiliateReportLinks.issueAffiliateReportLink({
  affiliateId: "AFFILIATE_ID"
})
era affiliate_report_links issue_affiliate_report_link \
  --affiliate-id "AFFILIATE_ID"
tools/call
{
  "name": "era__crm__issue_affiliate_report_link",
  "arguments": {
    "affiliateId": "AFFILIATE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 affiliate_not_found · 429 slow down

crm.log_call

Log a call — Records a call — and closes the lead when it is the third failure in a row.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/log_call
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.log_call

Input

FieldTypeRequiredNotes
leadIduuidyesFilled from the path {id} · Lead · id of lead
statusIduuidyesOutcome · id of call_status
resultIduuidyesResult · id of call_result
durationMinutesnumberDuration (minutes) · min 0 · max 1440
notesstringNotes · max 8000
isImportantbooleanImportant
followUpDatedateFollow up on
followUpTimestringAt · max 8
followUpNotesstringFollow-up notes · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
lead_archivedAn archived lead takes no calls.
call_status_not_availableThe outcome must exist and be active.
call_result_not_availableThe result must exist and be active.
follow_up_in_the_pastA follow-up must not be scheduled in the past.

Events it publishes: crm.call_logged, crm.lead_stage_changed, crm.lead_lost

What it writes: call (insert) — Records the call; lead (update) — Closes the lead on the Nth consecutive failure

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/log_call \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"statusId":"CALL_STATUS_ID","resultId":"CALL_RESULT_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_logged",
    "crm.lead_stage_changed",
    "crm.lead_lost"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.logCall("LEAD_ID", {
  statusId: "CALL_STATUS_ID",
  resultId: "CALL_RESULT_ID"
})
era leads log_call LEAD_ID \
  --status-id "CALL_STATUS_ID" \
  --result-id "CALL_RESULT_ID"
tools/call
{
  "name": "era__crm__log_call",
  "arguments": {
    "leadId": "LEAD_ID",
    "statusId": "CALL_STATUS_ID",
    "resultId": "CALL_RESULT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, lead_archived, call_status_not_available, call_result_not_available, follow_up_in_the_past · 429 slow down

crm.merge_lost_reasons

Merge loss reasons — Moves every lead filed under several reasons onto one, then closes the others.

Needs crm:manage · removes or cannot be undone

POSThttps://app.getera.io/api/v1/lost_reasons/{id}/actions/merge_lost_reasons
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.merge_lost_reasons

Input

FieldTypeRequiredNotes
sourceIdsobject / arrayyesReasons to merge
targetIduuidyesFilled from the path {id} · Keep this one · id of lost_reason

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_sourcesAt least one reason other than the target must be picked.
target_not_foundThe reason everything moves onto must be open.

Events it publishes: crm.lost_reason_changed

What it writes: lead (update) — Re-files the leads onto the surviving reason; lost_reason (update) — Closes the reasons that were folded in

curl -X POST https://app.getera.io/api/v1/lost_reasons/LOST_REASON_ID/actions/merge_lost_reasons \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"sourceIds":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lost_reason_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.lostReasons.mergeLostReasons("LOST_REASON_ID", {
  sourceIds: {}
})
era lost_reasons merge_lost_reasons LOST_REASON_ID --data '{"sourceIds":{}}'
tools/call
{
  "name": "era__crm__merge_lost_reasons",
  "arguments": {
    "targetId": "LOST_REASON_ID",
    "sourceIds": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_sources, target_not_found · 429 slow down

crm.raise_stale_leads

Flag quiet leads — Machine-only (the daily routine): every open lead that has not moved for the window is told about once.

Needs crm:write · changes data

Run by Era itself (machine-only). Listed so the reference is complete; an API key or a token is refused (403 machine_only).
POSThttps://app.getera.io/api/v1/leads/actions/raise_stale_leads
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.raise_stale_leads

Input

This action takes no input.

Events it publishes: crm.lead_stale

What it writes: lead (update) — Records that the lead was flagged as quiet

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

crm.restore_lead

Restore a lead — Brings an archived lead back.

Needs crm:delete · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/restore_lead
API key or OAuth token · crm:delete
  • Canonical address: POST /api/v1/actions/crm.restore_lead

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
not_archivedThe lead must be archived.

Events it publishes: crm.lead_restored

What it writes: lead (update) — Clears archived_at

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/restore_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_restored"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.restoreLead("LEAD_ID")
era leads restore_lead LEAD_ID
tools/call
{
  "name": "era__crm__restore_lead",
  "arguments": {
    "id": "LEAD_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:delete · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, not_archived · 429 slow down

Close an affiliate report link — The link stops working at once; the partner can be sent a new one.

Needs crm:manage · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/affiliate_report_links/{id}/actions/revoke_affiliate_report_link
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.revoke_affiliate_report_link
FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Link · id of affiliate_report_link

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe link must exist.
already_revokedThe link must still be open.

Events it publishes: crm.affiliate_report_link_revoked

What it writes: affiliate_report_link (update) — Stamps it revoked

curl -X POST https://app.getera.io/api/v1/affiliate_report_links/AFFILIATE_REPORT_LINK_ID/actions/revoke_affiliate_report_link \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.affiliate_report_link_revoked"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.affiliateReportLinks.revokeAffiliateReportLink("AFFILIATE_REPORT_LINK_ID")
era affiliate_report_links revoke_affiliate_report_link AFFILIATE_REPORT_LINK_ID
tools/call
{
  "name": "era__crm__revoke_affiliate_report_link",
  "arguments": {
    "id": "AFFILIATE_REPORT_LINK_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_revoked · 429 slow down

crm.save_affiliate

Add or edit an affiliate — The referral channel and the terms it was agreed on.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/affiliates/{id}/actions/save_affiliate
API key or OAuth token · crm:manage
  • REST alias: PATCH /api/v1/affiliates/{id}
  • Canonical address: POST /api/v1/actions/crm.save_affiliate

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Affiliate · id of affiliate
namestringyesName · max 200
emailemailEmail
phonephonePhone
commissionTypestring (enum)yesCommission type · Allowed values: percentage, fixed
commissionRatenumberRate (%) · min 0 · max 100
fixedCommissionAmountnumber (money)Fixed amount · min 0
commissionCurrencystringCurrency · max 3
commissionAccrualModestring (enum)Earned · Allowed values: proportional, on_sale, on_full_collection
notesstringNotes · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_contactAn affiliate needs an email address or a phone number.
rate_requiredA percentage arrangement needs a rate.
amount_requiredA fixed arrangement needs an amount.

Events it publishes: crm.affiliate_created, crm.affiliate_updated

What it writes: affiliate (upsert) — Writes the affiliate

curl -X POST https://app.getera.io/api/v1/affiliates/AFFILIATE_ID/actions/save_affiliate \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name","commissionType":"percentage"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.affiliate_created",
    "crm.affiliate_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.affiliates.saveAffiliate("AFFILIATE_ID", {
  name: "Name",
  commissionType: "percentage"
})
era affiliates save_affiliate AFFILIATE_ID \
  --name "Name" \
  --commission-type "percentage"
tools/call
{
  "name": "era__crm__save_affiliate",
  "arguments": {
    "id": "AFFILIATE_ID",
    "name": "Name",
    "commissionType": "percentage"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_contact, rate_required, amount_required · 429 slow down

crm.score_leads

Score leads — Gives every open lead a 0–100 score from its contact details, stage, calls, value and recency, with the reasons.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/actions/score_leads
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.score_leads

Input

FieldTypeRequiredNotes
leadIdsobject / arrayOnly these leads

Events it publishes: crm.leads_scored

What it writes: lead (update) — Writes score, score_reasons and scored_at

curl -X POST https://app.getera.io/api/v1/leads/actions/score_leads \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"leadIds":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.leads_scored"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.scoreLeads({
  leadIds: {}
})
era leads score_leads --data '{"leadIds":{}}'
tools/call
{
  "name": "era__crm__score_leads",
  "arguments": {
    "leadIds": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 429 slow down

crm.set_call_option_active

Open or close a call option — Takes a call outcome or result out of the picker, without touching the calls that used it.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/call_statuses/actions/set_call_option_active
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_call_option_active

Input

FieldTypeRequiredNotes
tablestring (enum)yesVocabulary · Allowed values: call_status, call_result
iduuidyesOption
activebooleanyesOpen

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe option must exist.
already_in_that_stateThe option is already open, or already closed.

Events it publishes: crm.call_dictionary_changed

What it writes: call_status (update) — Opens or closes the outcome; call_result (update) — Opens or closes the result

curl -X POST https://app.getera.io/api/v1/call_statuses/actions/set_call_option_active \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"table":"call_status","id":"ID_ID","active":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_dictionary_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.callStatuses.setCallOptionActive({
  table: "call_status",
  id: "ID_ID",
  active: true
})
era call_statuses set_call_option_active \
  --table "call_status" \
  --id "ID_ID" \
  --active true
tools/call
{
  "name": "era__crm__set_call_option_active",
  "arguments": {
    "table": "call_status",
    "id": "ID_ID",
    "active": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_in_that_state · 429 slow down

crm.set_call_result

Add or change a call result — Manages the call results, and whether each one counts for or against the lead.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/call_results/{id}/actions/set_call_result
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_call_result

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Result · id of call_result
keystringyesKey · max 50
namestringyesName · max 255
behaviorstring (enum)yesCounts as · Allowed values: positive, neutral, negative

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
key_takenTwo results may not share a key.

Events it publishes: crm.call_dictionary_changed

What it writes: call_result (upsert) — Writes the result

curl -X POST https://app.getera.io/api/v1/call_results/CALL_RESULT_ID/actions/set_call_result \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"key":"Key","name":"Name","behavior":"positive"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_dictionary_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.callResults.setCallResult("CALL_RESULT_ID", {
  key: "Key",
  name: "Name",
  behavior: "positive"
})
era call_results set_call_result CALL_RESULT_ID \
  --key "Key" \
  --name "Name" \
  --behavior "positive"
tools/call
{
  "name": "era__crm__set_call_result",
  "arguments": {
    "id": "CALL_RESULT_ID",
    "key": "Key",
    "name": "Name",
    "behavior": "positive"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 key_taken · 429 slow down

crm.set_call_status

Add or change a call outcome — Manages the call outcomes, and whether each one counts as reaching somebody.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/call_statuses/{id}/actions/set_call_status
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_call_status

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Outcome · id of call_status
keystringyesKey · max 50
namestringyesName · max 255
behaviorstring (enum)yesCounts as · Allowed values: reached, not_reached

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
key_takenTwo outcomes may not share a key.

Events it publishes: crm.call_dictionary_changed

What it writes: call_status (upsert) — Writes the outcome

curl -X POST https://app.getera.io/api/v1/call_statuses/CALL_STATUSE_ID/actions/set_call_status \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"key":"Key","name":"Name","behavior":"reached"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.call_dictionary_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.callStatuses.setCallStatus("CALL_STATUSE_ID", {
  key: "Key",
  name: "Name",
  behavior: "reached"
})
era call_statuses set_call_status CALL_STATUSE_ID \
  --key "Key" \
  --name "Name" \
  --behavior "reached"
tools/call
{
  "name": "era__crm__set_call_status",
  "arguments": {
    "id": "CALL_STATUSE_ID",
    "key": "Key",
    "name": "Name",
    "behavior": "reached"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 key_taken · 429 slow down

crm.set_lead_source

Add or rename a lead source — Manages where leads come from — one row per channel, not 25 spellings.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/lead_sources/{id}/actions/set_lead_source
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_lead_source

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Source · id of lead_source
keystringyesKey · max 50
namestringyesName · max 255
displayOrdernumberOrder · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
key_takenTwo sources may not share a key.

Events it publishes: crm.lead_source_changed

What it writes: lead_source (upsert) — Writes the source

curl -X POST https://app.getera.io/api/v1/lead_sources/LEAD_SOURCE_ID/actions/set_lead_source \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"key":"Key","name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_source_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leadSources.setLeadSource("LEAD_SOURCE_ID", {
  key: "Key",
  name: "Name"
})
era lead_sources set_lead_source LEAD_SOURCE_ID \
  --key "Key" \
  --name "Name"
tools/call
{
  "name": "era__crm__set_lead_source",
  "arguments": {
    "id": "LEAD_SOURCE_ID",
    "key": "Key",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 key_taken · 429 slow down

crm.set_lead_source_active

Open or close a lead source — Takes a channel out of the pickers without touching the leads that came from it.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/lead_sources/{id}/actions/set_lead_source_active
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_lead_source_active

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Source · id of lead_source
activebooleanyesOpen

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe source must exist.
already_in_that_stateThe source is already open, or already closed.

Events it publishes: crm.lead_source_changed

What it writes: lead_source (update) — Opens or closes the source

curl -X POST https://app.getera.io/api/v1/lead_sources/LEAD_SOURCE_ID/actions/set_lead_source_active \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"active":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_source_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leadSources.setLeadSourceActive("LEAD_SOURCE_ID", {
  active: true
})
era lead_sources set_lead_source_active LEAD_SOURCE_ID \
  --active true
tools/call
{
  "name": "era__crm__set_lead_source_active",
  "arguments": {
    "id": "LEAD_SOURCE_ID",
    "active": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_in_that_state · 429 slow down

crm.set_lead_stage

Move a lead — Moves the lead to another stage, and closes it when that stage is terminal.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/set_lead_stage
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.set_lead_stage

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead
stageIduuidyesStage · id of lead_stage
lostReasonIduuidReason · id of lost_reason

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
lead_archivedAn archived lead is restored before it is moved.
stage_not_availableThe stage must exist and be active.
lost_reason_requiredClosing a lead as lost requires a reason.
lost_reason_not_availableThe reason must exist and be active.

Events it publishes: crm.lead_stage_changed, crm.lead_won, crm.lead_lost

What it writes: lead (update) — Moves the lead and stamps its closure

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/set_lead_stage \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"stageId":"LEAD_STAGE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_stage_changed",
    "crm.lead_won",
    "crm.lead_lost"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.setLeadStage("LEAD_ID", {
  stageId: "LEAD_STAGE_ID"
})
era leads set_lead_stage LEAD_ID \
  --stage-id "LEAD_STAGE_ID"
tools/call
{
  "name": "era__crm__set_lead_stage",
  "arguments": {
    "id": "LEAD_ID",
    "stageId": "LEAD_STAGE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, lead_archived, stage_not_available, lost_reason_required, lost_reason_not_available · 429 slow down

crm.set_lead_stage_active

Open or close a stage — Takes a stage out of the pickers; leads already on it stay there.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/lead_stages/{id}/actions/set_lead_stage_active
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_lead_stage_active

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Stage · id of lead_stage
activebooleanyesOpen

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe stage must exist.
already_in_that_stateThe stage is already open, or already closed.
last_terminal_stageA workspace keeps at least one won stage and one lost stage.
last_open_stageA workspace keeps at least one open stage for new leads.

Events it publishes: crm.lead_stage_definition_changed

What it writes: lead_stage (update) — Opens or closes the stage

curl -X POST https://app.getera.io/api/v1/lead_stages/LEAD_STAGE_ID/actions/set_lead_stage_active \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"active":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_stage_definition_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leadStages.setLeadStageActive("LEAD_STAGE_ID", {
  active: true
})
era lead_stages set_lead_stage_active LEAD_STAGE_ID \
  --active true
tools/call
{
  "name": "era__crm__set_lead_stage_active",
  "arguments": {
    "id": "LEAD_STAGE_ID",
    "active": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_in_that_state, last_terminal_stage, last_open_stage · 429 slow down

crm.set_lead_stage_definition

Add or change a stage — Manages the pipeline — and keeps at least one won and one lost stage.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/lead_stages/{id}/actions/set_lead_stage_definition
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_lead_stage_definition

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Stage · id of lead_stage
keystringyesKey · max 50
namestringyesName · max 255
behaviorstring (enum)yesMeans · Allowed values: open, won, lost, suspended
displayOrdernumberOrder · min 0
winProbabilitynumberChance of winning (%) · min 0 · max 100

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe stage must exist.
key_takenTwo stages may not share a key.
last_terminal_stageA workspace keeps at least one won stage and one lost stage.

Events it publishes: crm.lead_stage_definition_changed

What it writes: lead_stage (upsert) — Writes the stage

curl -X POST https://app.getera.io/api/v1/lead_stages/LEAD_STAGE_ID/actions/set_lead_stage_definition \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"key":"Key","name":"Name","behavior":"open"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_stage_definition_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leadStages.setLeadStageDefinition("LEAD_STAGE_ID", {
  key: "Key",
  name: "Name",
  behavior: "open"
})
era lead_stages set_lead_stage_definition LEAD_STAGE_ID \
  --key "Key" \
  --name "Name" \
  --behavior "open"
tools/call
{
  "name": "era__crm__set_lead_stage_definition",
  "arguments": {
    "id": "LEAD_STAGE_ID",
    "key": "Key",
    "name": "Name",
    "behavior": "open"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, key_taken, last_terminal_stage · 429 slow down

crm.set_lost_reason

Add or rename a loss reason — Manages the workspace’s own vocabulary for why a lead was lost.

Needs crm:manage · changes data

POSThttps://app.getera.io/api/v1/lost_reasons/{id}/actions/set_lost_reason
API key or OAuth token · crm:manage
  • Canonical address: POST /api/v1/actions/crm.set_lost_reason

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Reason · id of lost_reason
namestringyesName · max 255
displayOrdernumberOrder · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
name_takenTwo reasons may not share a name.

Events it publishes: crm.lost_reason_changed

What it writes: lost_reason (upsert) — Writes the reason

curl -X POST https://app.getera.io/api/v1/lost_reasons/LOST_REASON_ID/actions/set_lost_reason \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Name"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lost_reason_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.lostReasons.setLostReason("LOST_REASON_ID", {
  name: "Name"
})
era lost_reasons set_lost_reason LOST_REASON_ID \
  --name "Name"
tools/call
{
  "name": "era__crm__set_lost_reason",
  "arguments": {
    "id": "LOST_REASON_ID",
    "name": "Name"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 name_taken · 429 slow down

crm.start_trial

Start a trial — Begins the trial and records the day it began.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/start_trial
API key or OAuth token · crm:write
  • Canonical address: POST /api/v1/actions/crm.start_trial

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead
daysnumberLength (days) · min 1 · max 365

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
lead_archivedAn archived lead cannot start a trial.
no_trial_lengthA trial needs a length in days.

Events it publishes: crm.lead_trial_started

What it writes: lead (update) — Records the trial and its start date

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/start_trial \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"days":1}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_trial_started"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.startTrial("LEAD_ID", {
  days: 1
})
era leads start_trial LEAD_ID \
  --days 1
tools/call
{
  "name": "era__crm__start_trial",
  "arguments": {
    "id": "LEAD_ID",
    "days": 1
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, lead_archived, no_trial_length · 429 slow down

crm.update_lead

Edit a lead — Changes the pursuit — its source, owner, note or trial length.

Needs crm:write · changes data

POSThttps://app.getera.io/api/v1/leads/{id}/actions/update_lead
API key or OAuth token · crm:write
  • REST alias: PATCH /api/v1/leads/{id}
  • Canonical address: POST /api/v1/actions/crm.update_lead

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Lead · id of lead
sourceIduuidSource · id of lead_source
assignedUserIduuidAssigned to
assignmentNotestringNote for the owner · max 2000
messagestringWhat they said · max 4000
affiliateIduuidReferred by · id of affiliate
trialDurationDaysnumberTrial length (days) · min 0 · max 365
expectedValueAmountnumber (money)Deal value
expectedValueCurrencystringDeal currency · max 3
expectedCloseDatedateExpected close

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
lead_not_foundThe lead must exist.
lead_archivedAn archived lead is restored before it is edited.
invalid_currencyA deal value’s currency is a three-letter code.
source_not_availableThe source must exist and be active.
affiliate_not_foundThe referring affiliate must exist.
not_a_memberThe owner must be an active member of this workspace.

Events it publishes: crm.lead_updated

What it writes: lead (update) — Updates the lead

curl -X POST https://app.getera.io/api/v1/leads/LEAD_ID/actions/update_lead \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"sourceId":"LEAD_SOURCE_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "crm.lead_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.leads.updateLead("LEAD_ID", {
  sourceId: "LEAD_SOURCE_ID"
})
era leads update_lead LEAD_ID \
  --source-id "LEAD_SOURCE_ID"
tools/call
{
  "name": "era__crm__update_lead",
  "arguments": {
    "id": "LEAD_ID",
    "sourceId": "LEAD_SOURCE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold crm:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 lead_not_found, lead_archived, invalid_currency, source_not_available, affiliate_not_found, not_a_member · 429 slow down

Data models

Table affiliate_report_link · Resource affiliate_report_links

  • GET /api/v1/affiliate_report_links
  • GET /api/v1/affiliate_report_links/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
affiliate_idstring (uuid) · uuidaffiliate
expires_atstring (date-time) · timestamptzyes
revoked_atstring (date-time) · timestamptzyes
last_used_atstring (date-time) · timestamptzyes
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
purposestring · textreport, referral

Primary key: id

Affiliate

Table affiliate · Resource affiliates

  • GET /api/v1/affiliates
  • GET /api/v1/affiliates/{id}
  • PATCH /api/v1/affiliates/{id} → crm.save_affiliate
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
emailstring · textyes
phonestring · textyes
phone_country_codeinteger · int2yes
statusstring · textactive, inactive
notesstring · textyes
commission_typestring · textpercentage, fixed
commission_ratestring (decimal) · numericyes
fixed_commission_amountstring (int64) · int8yes
commission_currencystring · bpcharmax 3
commission_accrual_modestring · textproportional, on_sale, on_full_collection
archived_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((commission_rate >= (0)::numeric) AND (commission_rate <= (100)::numeric)))
  • CHECK ((fixed_commission_amount >= 0))
  • CHECK ((((commission_type = 'percentage'::text) AND (commission_rate IS NOT NULL)) OR ((commission_type = 'fixed'::text) AND (fixed_commission_amount IS NOT NULL))))

CallResult

Table call_result · Resource call_results

  • GET /api/v1/call_results
  • GET /api/v1/call_results/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
keystring · text
namestring · text
behaviorstring · textpositive, neutral, negative
display_orderinteger · int4
is_activeboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

CallStatus

Table call_status · Resource call_statuses

  • GET /api/v1/call_statuses
  • GET /api/v1/call_statuses/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
keystring · text
namestring · text
behaviorstring · textreached, not_reached
display_orderinteger · int4
is_activeboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

CallTemplate

Table call_template · Resource call_templates

  • GET /api/v1/call_templates
  • GET /api/v1/call_templates/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
template_textstring · text
is_activeboolean · bool
created_by_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Call

Table call · Resource calls

  • GET /api/v1/calls
  • GET /api/v1/calls/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
lead_idstring (uuid) · uuidlead
called_by_user_idstring (uuid) · uuidyesapp_user
assigned_user_idstring (uuid) · uuidyesapp_user
duration_minutesinteger · int4yes
status_idstring (uuid) · uuidcall_status
result_idstring (uuid) · uuidcall_result
notesstring · textyes
is_importantboolean · bool
follow_up_datestring (date) · dateyes
follow_up_timestring (time) · timeyes
follow_up_notesstring · textyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((duration_minutes >= 0))

LeadCancellation

Table lead_cancellation · Resource lead_cancellations

  • GET /api/v1/lead_cancellations
  • GET /api/v1/lead_cancellations/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
lead_idstring (uuid) · uuidlead
reasonstring · text
cancelled_by_user_idstring (uuid) · uuidyesapp_user
cancellation_typestring · text
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

LeadSource

Table lead_source · Resource lead_sources

  • GET /api/v1/lead_sources
  • GET /api/v1/lead_sources/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
keystring · text
namestring · text
display_orderinteger · int4
is_activeboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

LeadStage

Table lead_stage · Resource lead_stages

  • GET /api/v1/lead_stages
  • GET /api/v1/lead_stages/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
keystring · text
namestring · text
behaviorstring · textopen, won, lost, suspended
display_orderinteger · int4
is_activeboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
win_probabilityinteger · int2yes

Primary key: id

Checks:

  • CHECK (((win_probability IS NULL) OR ((win_probability >= 0) AND (win_probability <= 100))))

Lead

Table lead · Resource leads

ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
customer_idstring (uuid) · uuidcustomer
source_idstring (uuid) · uuidyeslead_source
stage_idstring (uuid) · uuidlead_stage
lost_reason_idstring (uuid) · uuidyeslost_reason
affiliate_idstring (uuid) · uuidyesaffiliate
assigned_user_idstring (uuid) · uuidyesapp_user
assignment_notestring · textyes
messagestring · textyes
trial_duration_daysinteger · int4
trial_start_datestring (date) · dateyes
archived_atstring (date-time) · timestamptzyes
closed_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
expected_value_amountstring (int64) · int8yes
expected_value_currencystring · textyes
expected_close_datestring (date) · dateyes
stale_notice_atstring (date-time) · timestamptzyes
scoreinteger · int2yes
scored_atstring (date-time) · timestamptzyes
score_reasonsobject · jsonb

Primary key: id

Checks:

  • CHECK ((((expected_value_amount IS NULL) OR (expected_value_amount >= 0)) AND ((expected_value_currency IS NULL) OR (expected_value_currency ~ '^[A-Z]{3}$'::text)) AND ((expected_value_amount IS NULL) OR (expected_value_currency IS NOT NULL))))
  • CHECK (((score IS NULL) OR ((score >= 0) AND (score <= 100))))
  • CHECK ((jsonb_typeof(score_reasons) = 'array'::text))
  • CHECK ((trial_duration_days >= 0))

LostReason

Table lost_reason · Resource lost_reasons

  • GET /api/v1/lost_reasons
  • GET /api/v1/lost_reasons/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
namestring · text
display_orderinteger · int4
is_activeboolean · bool
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
crm.affiliate_createdAn affiliate was added.id
crm.affiliate_deactivatedAn affiliate was closed.id, openLeads
crm.affiliate_report_link_issuedAn affiliate was sent a report or referral link.id, affiliateId, purpose
crm.affiliate_report_link_revokedAn affiliate report link was closed.id, affiliateId
crm.affiliate_updatedAn affiliate was edited.id
crm.call_amendedA recorded call was corrected.id, leadId
crm.call_dictionary_changedThe call vocabulary changed.id, table
crm.call_loggedA call was recorded.id, leadId
crm.call_template_changedA call template changed.id
crm.calls_exportedSomebody exported the call log as one sheet per caller.count
crm.lead_archivedA lead was archived.id
crm.lead_assignedA lead changed owner.id, userId
crm.lead_attributedA lead was credited to an affiliate, or the credit moved.id, affiliateId, previousAffiliateId
crm.lead_cancelledA customer withdrew after buying.id, customerId
crm.lead_capturedA lead was opened.id, customerId, affiliateId
crm.lead_lostA lead was lost, by hand or by the auto-close rule.id, customerId, lostReasonId, automatic
crm.lead_restoredAn archived lead came back.id
crm.lead_source_changedThe source vocabulary changed.id
crm.lead_stage_changedA lead moved through the pipeline.id, stageId, behavior, stageKey, fromStageId, fromStageKey, customerId
crm.lead_stage_definition_changedThe pipeline changed.id
crm.lead_staleAn open lead has not moved for a while.id, customerId, customerName, ownerUserId, stage, quietDays, lastMovedAt
crm.lead_trial_startedA trial began.id, days
crm.lead_updatedA lead was edited.id
crm.lead_wonA lead was won — finance opens the agreement.id, customerId
crm.leads_exportedSomebody took the lead list out as a file.count, view, truncated
crm.leads_scoredOpen leads were given a score.count
crm.lost_reason_changedThe loss-reason vocabulary changed.id