StabisoDocs

API reference

Crm

Customer relationships: the deals of an entity's pipeline (opportunities with a customer or a prospect, their stages and probability), the timeline of calls, meetings, notes and tasks, the pipeline by stage, and the sales reports: the pipeline by owner and month, the win rate, each person's activity, quotas and the forecast; the people at customers and vendors (contacts, erased on request) and the organization's own fields on companies, contacts and deals. Nothing here posts.

Operation Summary Scope Idempotency-Key
POST /forms/leads/{key} Send a lead from a website
GET /v1/activities List activities read
POST /v1/activities Record an activity draft required
POST /v1/activities/{activity_id}/complete Mark a task done draft
GET /v1/contacts List contacts read
POST /v1/contacts Add a contact draft required
GET /v1/contacts/{contact_id} Get a contact read
PATCH /v1/contacts/{contact_id} Change a contact draft
POST /v1/contacts/{contact_id}/erase Erase a contact's personal data draft
POST /v1/contacts/{contact_id}/merge Merge a duplicate contact draft
GET /v1/custom-fields List the organization's own fields read
POST /v1/custom-fields Add a field of the organization's own admin required
PATCH /v1/custom-fields/{field_id} Change or retire a field of the organization's own admin
GET /v1/hubspot Get HubSpot admin
POST /v1/hubspot/authorize Start connecting HubSpot on its consent page admin
POST /v1/hubspot/connections Connect HubSpot with a private app admin
POST /v1/hubspot/connections/oauth Connect HubSpot with its consent page's answer admin
PATCH /v1/hubspot/connections/{connection_id} Change what HubSpot hands over admin
DELETE /v1/hubspot/connections/{connection_id} Disconnect HubSpot admin
POST /v1/hubspot/connections/{connection_id}/sync Read HubSpot now admin
GET /v1/lead-forms List website forms read
POST /v1/lead-forms Set up a website form admin required
PATCH /v1/lead-forms/{form_id} Change or switch off a website form admin
POST /v1/lead-forms/{form_id}/new-key Give a website form a new key admin
GET /v1/leads List leads read
POST /v1/leads Add a lead draft required
GET /v1/leads/summary Count leads by status read
GET /v1/leads/{lead_id} Get a lead read
PATCH /v1/leads/{lead_id} Change a lead draft
POST /v1/leads/{lead_id}/convert Convert a lead into a customer, contact and deal draft required
POST /v1/leads/{lead_id}/erase Erase a lead's personal data draft
GET /v1/me/mailbox Get your mailbox link read
DELETE /v1/me/mailbox Unlink your mailbox read
POST /v1/me/mailbox/google Link your Google account draft
POST /v1/me/mailbox/sync Read your mailbox now draft
GET /v1/me/record-access Which sales records you see read
GET /v1/opportunities List opportunities read
POST /v1/opportunities Create an opportunity draft required
GET /v1/opportunities/pipeline Get the pipeline of an entity read
GET /v1/opportunities/{opportunity_id} Get an opportunity read
PATCH /v1/opportunities/{opportunity_id} Change an opportunity draft
POST /v1/opportunities/{opportunity_id}/stage Move an opportunity to a stage draft required
PUT /v1/people/{user_id}/record-visibility Set which sales records a person sees admin
GET /v1/record-access List what each person sees admin
POST /v1/record-access/hand-over Hand one person's records to another admin required
GET /v1/sales-teams List sales teams read
POST /v1/sales-teams Set up a sales team admin required
PATCH /v1/sales-teams/{team_id} Rename a sales team admin
DELETE /v1/sales-teams/{team_id} Remove a sales team admin
PUT /v1/sales-teams/{team_id}/members/{user_id} Add a person to a sales team admin
DELETE /v1/sales-teams/{team_id}/members/{user_id} Take a person out of a sales team admin
GET /v1/sales/activity Get the sales activity per person read
GET /v1/sales/forecast Get the sales forecast read
GET /v1/sales/pipeline Get the pipeline report read
GET /v1/sales/quotas List quotas read
PUT /v1/sales/quotas Set quotas approve
GET /v1/sales/win-rate Get the win rate read

POST /forms/leads/{key}

Send a lead from a website

No credential: the key names the form. Whoever fills in the form becomes a lead of the form's company (status new, source web_form, owned by the form's owner). Limited per client address and per form (429 with Retry-After).

Required scope any valid credential
Idempotency-Key not used
Operation id submit_forms_leads__key__post

Parameters

In Name Type Required Description
path key string yes The form's public key (GET /v1/lead-forms).
query lang string or null Language of an HTML answer.

Request body

application/json (optional): object

Field Type Required Description
first_name string
last_name string
name string
email string yes
company string
role string
phone string
country string
message string
campaign string
website string

application/x-www-form-urlencoded (optional): object

Field Type Required Description
first_name string
last_name string
name string
email string yes
company string
role string
phone string
country string
message string
campaign string
website string

Example request

curl -X POST "$ERP_URL/forms/leads/lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "first_name": "Eva",
  "last_name": "Beispiel",
  "email": "eva@beispiel.example",
  "company": "Beispiel GmbH",
  "message": "Please send us a quote for 25 seats."
}'

Responses

Status Meaning Body
201 Received (also when dropped as spam: a robot learns nothing). JSON to a script; the form's thank-you page to the form's own page. object, text/html
303 Received from a website's own HTML form: to its redirect_url.
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 lead_form_origin: not sent from one of the form's websites.
404 lead_form_unknown: no such form, or it is switched off.
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope.
429 rate_limited: too many submissions; see Retry-After.

Example response (shape)

{
  "received": true
}

GET /v1/activities

List activities

The timeline of a deal, a lead or a customer, or the open tasks: the newest first (open tasks: the earliest due first). Someone who sees only their own or their team's records gets the activities of those people, the tasks assigned to them and the timelines of the deals, leads and customers they see. Read-only.

Required scope read (admin implies every scope)
Operation id list_activities_v1_activities_get

Parameters

In Name Type Required Description
query entity string or null Only this entity's activities (code or id).
query opportunity_id string (uuid) or null Only this deal's.
query party_id string (uuid) or null Only this customer's.
query kind "call" | "email" | "meeting" | "note" | "task" or null Only this kind.
query open_tasks boolean Only tasks not done yet. (default false)
query assignee_user_id string (uuid) or null Only tasks of this person.
query contact_id string (uuid) or null Only those with this contact (GET /v1/contacts).
query owner_user_id string (uuid) or null Only those this person did or set (GET /v1/people).
query lead_id string (uuid) or null Only this lead's (GET /v1/leads).
query limit integer How many, the newest first. (min 1; max 1000; default 100)

Example request

curl "$ERP_URL/v1/activities" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of ActivityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "body": "25 users, budget approved for Q1",
    "created_at": "2026-10-04T08:10:00Z",
    "created_by": {
      "id": "user:alice",
      "name": "Alice",
      "type": "human"
    },
    "entity": "CH01",
    "id": "4a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
    "kind": "call",
    "occurred_on": "2026-10-04",
    "opportunity_id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
    "opportunity_number": "OPP-000001",
    "overdue": false,
    "subject": "Discovery call"
  }
]

POST /v1/activities

Record an activity

A note, call, meeting or e-mail about a deal (opportunity_id) or a customer (entity and party), or a task with its due day and assignee (by default the deal's owner). An activity is never edited or deleted; a task is completed once (POST /v1/activities/{activity_id}/complete). Needs an Idempotency-Key.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id create_activity_v1_activities_post

Request body

application/json (required): ActivityInput

Field Type Required Description
opportunity_id string (uuid) or null The deal it is about (GET /v1/opportunities).
lead_id string (uuid) or null The lead it is about, before they are a customer (GET /v1/leads); then name no deal, entity, customer or contact.
entity string or null Code (or id) of the entity, for an activity about a customer only (one about a deal is in the deal's entity).
party string or null Code (or id) of the customer, for an activity about a customer only (one about a deal names the deal's customer).
kind "call" | "email" | "meeting" | "note" | "task" yes note, call, meeting, email or task.
subject string yes What it was or is. (min length 1; max length 200)
body string or null The details. (max length 5000)
occurred_on string (date) or null When it happened (a task: when it was set); omitted: today.
due_on string (date) or null A task: when it is due.
assignee_user_id string (uuid) or null A task: who does it (GET /v1/people); omitted: the deal's or the lead's owner.
owner_user_id string (uuid) or null Who did it, or set the task (GET /v1/people); omitted: you (a key or an agent: the person behind it). It never changes.
contact_id string (uuid) or null The person at the customer it was with (GET /v1/contacts?party_id=); omitted for an activity of a deal: the deal's contact.

Example request

curl -X POST "$ERP_URL/v1/activities" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "body": "25 users, budget approved for Q1",
  "kind": "call",
  "occurred_on": "2026-10-04",
  "opportunity_id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "subject": "Discovery call"
}'

Responses

Status Meaning Body
201 Created ActivityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "body": "25 users, budget approved for Q1",
  "created_at": "2026-10-04T08:10:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "entity": "CH01",
  "id": "4a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
  "kind": "call",
  "occurred_on": "2026-10-04",
  "opportunity_id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "opportunity_number": "OPP-000001",
  "overdue": false,
  "subject": "Discovery call"
}

POST /v1/activities/{activity_id}/complete

Mark a task done

Record that a task is done, by whom and when. 409 for an activity that is not a task or a task already done; 404 when it does not exist.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id complete_activity_v1_activities__activity_id__complete_post

Parameters

In Name Type Required Description
path activity_id string (uuid) yes Id of the activity (GET /v1/activities).

Example request

curl -X POST "$ERP_URL/v1/activities/4a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d/complete" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK ActivityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "body": "25 users, budget approved for Q1",
  "created_at": "2026-10-04T08:10:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "entity": "CH01",
  "id": "4a7b8c9d-0e1f-4a2b-9c3d-4e5f6a7b8c9d",
  "kind": "call",
  "occurred_on": "2026-10-04",
  "opportunity_id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "opportunity_number": "OPP-000001",
  "overdue": false,
  "subject": "Discovery call"
}

GET /v1/contacts

List contacts

The people at customers and vendors, by last and first name. Someone who sees only their own or their team's records gets the contacts of those people and of the customers they see (and every vendor's). Read-only.

Required scope read (admin implies every scope)
Operation id list_contacts_v1_contacts_get

Parameters

In Name Type Required Description
query entity string or null Only this company's contacts (code or id).
query party_id string (uuid) or null Only the people at this customer or vendor.
query q string or null Text in the name, e-mail or role. (max length 200)
query email string or null Only this e-mail address (any case). (max length 200)
query erased boolean Also the erased contacts. (default false)
query owner_user_id string (uuid) or null Only those this person looks after (GET /v1/people).
query limit integer How many, by last and first name. (min 1; max 1000; default 200)

Example request

curl "$ERP_URL/v1/contacts" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of ContactOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "created_at": "2026-10-23T08:00:00Z",
    "created_by": {
      "id": "user:alice",
      "name": "Alice",
      "type": "human"
    },
    "custom": {},
    "email": "anna.muster@kunde.example",
    "email_opt_out": false,
    "entity": "CH01",
    "erased": false,
    "first_name": "Anna",
    "id": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
    "language": "de",
    "last_name": "Muster",
    "name": "Anna Muster",
    "party_code": "C-CH",
    "party_id": "6c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
    "party_kind": "customer",
    "party_name": "Kunde AG",
    "phone": "+41 44 123 45 67",
    "role": "Head of purchasing"
  }
]

POST /v1/contacts

Add a contact

A person at a customer or vendor (party): name, role, e-mail, phones, the language to write to them in, whether they want sales e-mails, and the organization's own contact fields (custom). An agent may add one. 404 for an unknown company or party; 422 for a field that is not one of the organization's or a value of the wrong type. Needs an Idempotency-Key.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id create_contact_v1_contacts_post

Request body

application/json (required): ContactInput

Field Type Required Description
first_name string or null First name. (min length 1; max length 100)
role string or null Role or department, e.g. Head of purchasing. A role that says billing, accounting or Buchhaltung makes the contact one an invoice goes to by default. (max length 100)
email string or null E-mail address. (max length 200)
phone string or null Phone number. (max length 50)
mobile string or null Mobile number. (max length 50)
last_name string yes Last name. A name sent instead is split: its last word is the last name, the words before it the first name. (min length 1; max length 100)
entity string yes Code (or id) of the company (entity).
party string yes Code (or id) of the customer or vendor the person is at.
kind "customer" | "vendor" or null Needed only when party is a code of a customer and a vendor.
language "de" | "en" | "fr" | "it" | "rm" | "sv" or null Language to write to them in (de, fr, it, en, sv, rm).
email_opt_out boolean They asked not to get sales or marketing e-mails (invoices still go). (default false)
custom map of string | number | integer | boolean or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.
owner_user_id string (uuid) or null Who looks after the contact (GET /v1/people); omitted: you (a key or an agent: the person behind it).

Example request

curl -X POST "$ERP_URL/v1/contacts" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "anna.muster@kunde.example",
  "entity": "CH01",
  "first_name": "Anna",
  "language": "de",
  "last_name": "Muster",
  "party": "C-CH",
  "phone": "+41 44 123 45 67",
  "role": "Head of purchasing"
}'

Responses

Status Meaning Body
201 Created ContactOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "custom": {},
  "email": "anna.muster@kunde.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Anna",
  "id": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
  "language": "de",
  "last_name": "Muster",
  "name": "Anna Muster",
  "party_code": "C-CH",
  "party_id": "6c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
  "party_kind": "customer",
  "party_name": "Kunde AG",
  "phone": "+41 44 123 45 67",
  "role": "Head of purchasing"
}

GET /v1/contacts/{contact_id}

Get a contact

One contact; an erased one shows only that it existed.

Required scope read (admin implies every scope)
Operation id get_contact_v1_contacts__contact_id__get

Parameters

In Name Type Required Description
path contact_id string (uuid) yes Id of the contact (GET /v1/contacts).

Example request

curl "$ERP_URL/v1/contacts/5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK ContactOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "custom": {},
  "email": "anna.muster@kunde.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Anna",
  "id": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
  "language": "de",
  "last_name": "Muster",
  "name": "Anna Muster",
  "party_code": "C-CH",
  "party_id": "6c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
  "party_kind": "customer",
  "party_name": "Kunde AG",
  "phone": "+41 44 123 45 67",
  "role": "Head of purchasing"
}

PATCH /v1/contacts/{contact_id}

Change a contact

Change what the request names ("" clears a text field but the last name; custom changes only the keys sent). A contact never moves to another customer or vendor. The audit trail records which fields changed, never their values. 409 contact_erased for an erased one.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id update_contact_v1_contacts__contact_id__patch

Parameters

In Name Type Required Description
path contact_id string (uuid) yes Id of the contact (GET /v1/contacts).

Request body

application/json (required): ContactUpdate

Field Type Required Description
first_name string or null "" clears. (max length 100)
role string or null Role or department, e.g. Head of purchasing. A role that says billing, accounting or Buchhaltung makes the contact one an invoice goes to by default. (max length 100)
email string or null E-mail address. (max length 200)
phone string or null Phone number. (max length 50)
mobile string or null Mobile number. (max length 50)
last_name string or null New. (min length 1; max length 100)
language "de" | "en" | "fr" | "it" | "rm" | "sv" | "" or null "" clears.
email_opt_out boolean or null New opt-out.
custom map of string | number | integer | boolean or null or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.
owner_user_id string (uuid) or null New owner (GET /v1/people). Someone who sees only their own or their team's records hands a contact only to one of those people.

Example request

curl -X PATCH "$ERP_URL/v1/contacts/5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "anna@kunde.example",
  "role": "CFO"
}'

Responses

Status Meaning Body
200 OK ContactOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "custom": {},
  "email": "anna.muster@kunde.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Anna",
  "id": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
  "language": "de",
  "last_name": "Muster",
  "name": "Anna Muster",
  "party_code": "C-CH",
  "party_id": "6c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
  "party_kind": "customer",
  "party_name": "Kunde AG",
  "phone": "+41 44 123 45 67",
  "role": "Head of purchasing"
}

POST /v1/contacts/{contact_id}/erase

Erase a contact's personal data

Empty the contact's name, role, e-mail, phones, and own fields for good (the person asked to be forgotten, or the relationship ended). The row stays, so that the deals and activities that named it still say someone was there; it cannot be undone. A person's decision: 403 contact_erase_agent for an agent. 409 contact_erased if already erased.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id erase_contact_v1_contacts__contact_id__erase_post

Parameters

In Name Type Required Description
path contact_id string (uuid) yes Id of the contact (GET /v1/contacts).

Example request

curl -X POST "$ERP_URL/v1/contacts/5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e/erase" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK ContactOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "custom": {},
  "email": "anna.muster@kunde.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Anna",
  "id": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
  "language": "de",
  "last_name": "Muster",
  "name": "Anna Muster",
  "party_code": "C-CH",
  "party_id": "6c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
  "party_kind": "customer",
  "party_name": "Kunde AG",
  "phone": "+41 44 123 45 67",
  "role": "Head of purchasing"
}

POST /v1/contacts/{contact_id}/merge

Merge a duplicate contact

Merge this contact, entered twice, into into, another contact of the same customer or vendor (ADR-0089): what the one that stays has no value for (first name, role, e-mail, phones, language, own fields) comes from this one, a "no sales e-mails" wish stays, its deals and activities move, and this one is erased (merged_into_id says where it went). Returns the contact that stays. A person's decision: 403 merge_agent for an agent. 422 contact_merge_other_party for a contact of another customer or vendor, or itself; 409 contact_erased if either was erased.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id merge_contact_v1_contacts__contact_id__merge_post

Parameters

In Name Type Required Description
path contact_id string (uuid) yes Id of the contact (GET /v1/contacts).

Request body

application/json (required): ContactMerge

Field Type Required Description
into string (uuid) yes Id of the contact that stays (same customer or vendor).

Example request

curl -X POST "$ERP_URL/v1/contacts/5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e/merge" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "into": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e"
}'

Responses

Status Meaning Body
200 OK ContactOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "custom": {},
  "email": "anna.muster@kunde.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Anna",
  "id": "5b8c9d0e-1f2a-4b3c-8d4e-5f6a7b8c9d0e",
  "language": "de",
  "last_name": "Muster",
  "name": "Anna Muster",
  "party_code": "C-CH",
  "party_id": "6c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
  "party_kind": "customer",
  "party_name": "Kunde AG",
  "phone": "+41 44 123 45 67",
  "role": "Head of purchasing"
}

GET /v1/custom-fields

List the organization's own fields

The fields the organization added to its companies (customers and vendors), contacts and deals, in their order. A record's values are in its custom, by the field's key.

Required scope read (admin implies every scope)
Operation id list_custom_fields_v1_custom_fields_get

Parameters

In Name Type Required Description
query record "company" | "contact" | "deal" or null Only the fields of company, contact or deal records.
query retired boolean Also the retired fields. (default false)

Example request

curl "$ERP_URL/v1/custom-fields" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of CustomFieldOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "record": "company",
    "key": "string",
    "label": "string",
    "type": "choice",
    "choices": [
      "string"
    ],
    "position": 1,
    "retired": true
  }
]

POST /v1/custom-fields

Add a field of the organization's own

A field on every company, contact or deal: text, number, date, yes_no or choice (with its choices). Its key and type never change. Up to 25 in use per kind of record (409 custom_fields_full); 409 custom_field_exists for a key in use. Needs an Idempotency-Key.

Required scope admin (admin implies every scope)
Idempotency-Key required
Operation id create_custom_field_v1_custom_fields_post

Request body

application/json (required): CustomFieldInput

Field Type Required Description
record "company" | "contact" | "deal" yes company, contact or deal.
key string yes How the API names it: lower case letters, digits and _, e.g. industry; never changes. (pattern ^[a-z][a-z0-9_]{0,39}$)
label string yes What the screens call it. (min length 1; max length 80)
type "choice" | "date" | "number" | "text" | "yes_no" yes text, number, date, yes_no or choice; never changes.
choices array of string or null For choice: the choices, in order (1 to 50).
position integer Lower comes first. (min 0; max 1000; default 0)

Example request

curl -X POST "$ERP_URL/v1/custom-fields" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "choices": [
    "Retail",
    "Manufacturing",
    "Services"
  ],
  "key": "industry",
  "label": "Industry",
  "record": "company",
  "type": "choice"
}'

Responses

Status Meaning Body
201 Created CustomFieldOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "record": "company",
  "key": "string",
  "label": "string",
  "type": "choice",
  "choices": [
    "string"
  ],
  "position": 1,
  "retired": true
}

PATCH /v1/custom-fields/{field_id}

Change or retire a field of the organization's own

A new label, position or choices (a choice a record holds stays: 409 custom_field_choice_in_use), or retired: a retired field leaves the forms and takes no new values; the values records hold stay readable. A field is never deleted.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id update_custom_field_v1_custom_fields__field_id__patch

Parameters

In Name Type Required Description
path field_id string (uuid) yes Id of the field (GET /v1/custom-fields).

Request body

application/json (required): CustomFieldUpdate

Field Type Required Description
label string or null New label. (min length 1; max length 80)
choices array of string or null A choice field's new choices; a choice a record holds stays in the list.
position integer or null New position. (min 0; max 1000)
retired boolean or null true hides the field from forms (its values stay readable); false brings it back.

Example request

curl -X PATCH "$ERP_URL/v1/custom-fields/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "label": "Sector"
}'

Responses

Status Meaning Body
200 OK CustomFieldOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "record": "company",
  "key": "string",
  "label": "string",
  "type": "choice",
  "choices": [
    "string"
  ],
  "position": 1,
  "retired": true
}

GET /v1/hubspot

Get HubSpot

Whether HubSpot can be connected here, whether HubSpot's consent page can be used, the permissions a private app needs, and each company connected: the account, the stage at which a contact becomes a lead, the default owner, how far HubSpot was read and the last error.

Required scope admin (admin implies every scope)
Operation id get_hubspot_v1_hubspot_get

Example request

curl "$ERP_URL/v1/hubspot" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK HubSpotSetup
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "available": true,
  "oauth": true,
  "client_id": "string",
  "scopes": [
    "string"
  ],
  "connections": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "entity": "string",
      "portal_id": 1,
      "account_url": "string",
      "method": "key",
      "stage": "lead",
      "owner": null,
      "status": "active",
      "read_to": "2026-09-21T09:30:00Z",
      "last_sync_at": "2026-09-21T09:30:00Z",
      "last_error": "string",
      "leads": 1,
      "linked_by": {},
      "linked_at": "2026-09-21T09:30:00Z"
    }
  ]
}

POST /v1/hubspot/authorize

Start connecting HubSpot on its consent page

HubSpot's consent page for the company: open url; HubSpot sends the browser back to redirect_uri with a code and a state for POST /v1/hubspot/connections/oauth, within 15 minutes. 409 when this system has no HubSpot app (connect with a private app instead).

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id authorize_v1_hubspot_authorize_post

Request body

application/json (required): HubSpotAuthorize

Field Type Required Description
entity string yes Code (or id) of the company.
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in. (default "marketingqualifiedlead")
owner_user_id string (uuid) or null Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one.

Example request

curl -X POST "$ERP_URL/v1/hubspot/authorize" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "entity": "CH01",
  "stage": "marketingqualifiedlead"
}'

Responses

Status Meaning Body
200 OK HubSpotAuthorizeOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "url": "string",
  "redirect_uri": "string"
}

POST /v1/hubspot/connections

Connect HubSpot with a private app

Connect a company with the access token of a private app in its own HubSpot. HubSpot is asked for the account (proving the token) and Stabiso's properties are added to its contacts. From then on its contacts at the hand-over stage come in as leads every 5 minutes (the first round reads the last 90 days), and each contact is sent what became of its lead. Connecting again replaces the token. 409 in a test copy or when the company is connected to another account; 422 when HubSpot refuses the token or a permission is missing.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id connect_key_v1_hubspot_connections_post

Request body

application/json (required): HubSpotKeyInput

Field Type Required Description
entity string yes Code (or id) of the company.
access_token string yes The access token of a private app in the company's HubSpot with the scopes of GET /v1/hubspot. Kept sealed, never shown again. (min length 10; max length 500)
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in. (default "marketingqualifiedlead")
owner_user_id string (uuid) or null Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one.

Example request

curl -X POST "$ERP_URL/v1/hubspot/connections" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "access_token": "pat-eu1-0b9c4d2e-7f1a-4c3b-9e8d-1a2b3c4d5e6f",
  "entity": "CH01",
  "stage": "marketingqualifiedlead"
}'

Responses

Status Meaning Body
200 OK HubSpotConnection
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "entity": "string",
  "portal_id": 1,
  "account_url": "string",
  "method": "key",
  "stage": "lead",
  "owner": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "string",
    "name": "string"
  },
  "status": "active",
  "read_to": "2026-09-21T09:30:00Z",
  "last_sync_at": "2026-09-21T09:30:00Z",
  "last_error": "string",
  "leads": 1,
  "linked_by": {},
  "linked_at": "2026-09-21T09:30:00Z"
}

POST /v1/hubspot/connections/oauth

Connect HubSpot with its consent page's answer

Finish connecting with the code and state HubSpot sent the browser back with (only for whoever started it). 422 when the state is not theirs or too old; 409 as above.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id connect_oauth_v1_hubspot_connections_oauth_post

Request body

application/json (required): HubSpotOAuthInput

Field Type Required Description
code string yes HubSpot's one-time code. (min length 1; max length 2000)
state string yes The state HubSpot returned. (min length 1; max length 2000)

Example request

curl -X POST "$ERP_URL/v1/hubspot/connections/oauth" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "na1-6a2b-…",
  "state": "eyJvcmciOiI….Xq3v…"
}'

Responses

Status Meaning Body
200 OK HubSpotConnection
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "entity": "string",
  "portal_id": 1,
  "account_url": "string",
  "method": "key",
  "stage": "lead",
  "owner": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "string",
    "name": "string"
  },
  "status": "active",
  "read_to": "2026-09-21T09:30:00Z",
  "last_sync_at": "2026-09-21T09:30:00Z",
  "last_error": "string",
  "leads": 1,
  "linked_by": {},
  "linked_at": "2026-09-21T09:30:00Z"
}

PATCH /v1/hubspot/connections/{connection_id}

Change what HubSpot hands over

Change the hand-over stage or the default owner (from the next round on).

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id update_connection_v1_hubspot_connections__connection_id__patch

Parameters

In Name Type Required Description
path connection_id string (uuid) yes Id of the connection (GET /v1/hubspot).

Request body

application/json (required): HubSpotUpdate

Field Type Required Description
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" or null The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in.
owner_user_id string (uuid) | "" or null Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one. "" clears.

Example request

curl -X PATCH "$ERP_URL/v1/hubspot/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "owner_user_id": "",
  "stage": "lead"
}'

Responses

Status Meaning Body
200 OK HubSpotConnection
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "entity": "string",
  "portal_id": 1,
  "account_url": "string",
  "method": "key",
  "stage": "lead",
  "owner": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "string",
    "name": "string"
  },
  "status": "active",
  "read_to": "2026-09-21T09:30:00Z",
  "last_sync_at": "2026-09-21T09:30:00Z",
  "last_error": "string",
  "leads": 1,
  "linked_by": {},
  "linked_at": "2026-09-21T09:30:00Z"
}

DELETE /v1/hubspot/connections/{connection_id}

Disconnect HubSpot

Disconnect: nothing more is read or sent, and a consent's token is taken back at HubSpot (a private app's token is deleted in HubSpot). The leads stay, and so does which contact each came from, so connecting the same account again brings no contact in twice.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id disconnect_v1_hubspot_connections__connection_id__delete

Parameters

In Name Type Required Description
path connection_id string (uuid) yes Id of the connection (GET /v1/hubspot).

Example request

curl -X DELETE "$ERP_URL/v1/hubspot/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
204 No content
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

POST /v1/hubspot/connections/{connection_id}/sync

Read HubSpot now

A round now (they run every 5 minutes): read the contacts that changed, make leads of the new ones and send each contact what became of its lead. 409 when HubSpot stopped accepting the connection (connect again) or in a test copy.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id sync_connection_v1_hubspot_connections__connection_id__sync_post

Parameters

In Name Type Required Description
path connection_id string (uuid) yes Id of the connection (GET /v1/hubspot).

Example request

curl -X POST "$ERP_URL/v1/hubspot/connections/7d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a/sync" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK HubSpotSyncOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "read": 1,
  "new_leads": 1,
  "updated_leads": 1,
  "sent": 1,
  "connection": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "entity": "string",
    "portal_id": 1,
    "account_url": "string",
    "method": "key",
    "stage": "lead",
    "owner": {
      "id": null,
      "email": null,
      "name": null
    },
    "status": "active",
    "read_to": "2026-09-21T09:30:00Z",
    "last_sync_at": "2026-09-21T09:30:00Z",
    "last_error": "string",
    "leads": 1,
    "linked_by": {},
    "linked_at": "2026-09-21T09:30:00Z"
  }
}

GET /v1/lead-forms

List website forms

The forms on the organization's websites that create leads, by name, with their public key, their own page and how many leads each created.

Required scope read (admin implies every scope)
Operation id list_lead_forms_v1_lead_forms_get

Parameters

In Name Type Required Description
query entity string or null Only this company's forms (code or id).

Example request

curl "$ERP_URL/v1/lead-forms" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of LeadFormOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "active": true,
    "allowed_origins": [
      "https://www.example.ch"
    ],
    "created_at": "2026-10-20T08:00:00Z",
    "created_by": {
      "id": "user:alice",
      "name": "Alice",
      "type": "human"
    },
    "entity": "CH01",
    "id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
    "key": "lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a",
    "language": "de",
    "last_lead_at": "2026-10-23T09:12:00Z",
    "leads": 12,
    "name": "Contact us",
    "owner": {
      "email": "alice@example.com",
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Alice"
    },
    "page_path": "/forms/leads/lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a"
  }
]

POST /v1/lead-forms

Set up a website form

A form for a website: whoever fills it in becomes a lead of the company, owned by owner_user_id, with the source web_form. Link to or embed its own page (page_path), or send your website's own form to it (docs/erp/leads.md). A person's setting: 403 lead_form_agent for an agent. Needs an Idempotency-Key.

Required scope admin (admin implies every scope)
Idempotency-Key required
Operation id create_lead_form_v1_lead_forms_post

Request body

application/json (required): LeadFormInput

Field Type Required Description
entity string yes Code (or id) of the company whose leads it creates.
name string yes What the form is, e.g. Contact us. (min length 1; max length 100)
owner_user_id string (uuid) or null Who the leads go to (GET /v1/people); omitted: nobody yet.
language "de" | "en" | "fr" | "it" | "sv" The language of its own page. (default "en")
allowed_origins array of string The websites it may be sent from, e.g. https://www.example.ch; empty: any. A browser names its page's site, so a copy of the form on another site is refused. (max items 20)
thank_you string or null What its page says after sending. (max length 500)
redirect_url string or null A page of your own (https) to send people to after a form of your own website was sent; omitted: the thank-you page. (max length 500; pattern ^https://)

Example request

curl -X POST "$ERP_URL/v1/lead-forms" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "allowed_origins": [
    "https://www.example.ch"
  ],
  "entity": "CH01",
  "language": "de",
  "name": "Contact us",
  "owner_user_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "thank_you": "Danke! Wir melden uns innert eines Arbeitstages."
}'

Responses

Status Meaning Body
201 Created LeadFormOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "active": true,
  "allowed_origins": [
    "https://www.example.ch"
  ],
  "created_at": "2026-10-20T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "entity": "CH01",
  "id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "key": "lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a",
  "language": "de",
  "last_lead_at": "2026-10-23T09:12:00Z",
  "leads": 12,
  "name": "Contact us",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "page_path": "/forms/leads/lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a"
}

PATCH /v1/lead-forms/{form_id}

Change or switch off a website form

Its name, owner, language, websites, thank-you text or page to go to; active: false switches it off (its key then answers 404). A form is never deleted: its leads name it.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id update_lead_form_v1_lead_forms__form_id__patch

Parameters

In Name Type Required Description
path form_id string (uuid) yes Id of the form (GET /v1/lead-forms).

Request body

application/json (required): LeadFormUpdate

Field Type Required Description
name string or null New name. (min length 1; max length 100)
active boolean or null false switches it off.
owner_user_id string (uuid) | "" or null Who the leads go to; "": nobody.
language "de" | "en" | "fr" | "it" | "sv" or null Its page's language.
allowed_origins array of string or null The websites it may be sent from; []: any. (max items 20)
thank_you string or null "" clears. (max length 500)
redirect_url string or null "" clears. (max length 500; pattern ^(https://.*)?$)

Example request

curl -X PATCH "$ERP_URL/v1/lead-forms/9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "allowed_origins": [
    "https://www.example.ch"
  ],
  "language": "fr"
}'

Responses

Status Meaning Body
200 OK LeadFormOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "active": true,
  "allowed_origins": [
    "https://www.example.ch"
  ],
  "created_at": "2026-10-20T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "entity": "CH01",
  "id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "key": "lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a",
  "language": "de",
  "last_lead_at": "2026-10-23T09:12:00Z",
  "leads": 12,
  "name": "Contact us",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "page_path": "/forms/leads/lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a"
}

POST /v1/lead-forms/{form_id}/new-key

Give a website form a new key

A new public key, for a form whose key is abused: the old key stops working at once, so the website must get the new one.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id new_lead_form_key_v1_lead_forms__form_id__new_key_post

Parameters

In Name Type Required Description
path form_id string (uuid) yes Id of the form (GET /v1/lead-forms).

Example request

curl -X POST "$ERP_URL/v1/lead-forms/9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c/new-key" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK LeadFormOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "active": true,
  "allowed_origins": [
    "https://www.example.ch"
  ],
  "created_at": "2026-10-20T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "entity": "CH01",
  "id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "key": "lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a",
  "language": "de",
  "last_lead_at": "2026-10-23T09:12:00Z",
  "leads": 12,
  "name": "Contact us",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "page_path": "/forms/leads/lf_0b9c4a2e7d1f4e3a9c5b6d7e8f9a0b1c_Qm3xV7pL2sK9tR4wY8zN1a"
}

GET /v1/leads

List leads

The people who may become customers, the newest first, with their status, source and owner. Read-only.

Required scope read (admin implies every scope)
Operation id list_leads_v1_leads_get

Parameters

In Name Type Required Description
query entity string or null Only this company's leads (code or id).
query status "converted" | "disqualified" | "new" | "working" or null Only leads at this status.
query open_only boolean Only open leads (new and working). (default false)
query source "advertising" | "email" | "event" | "hubspot" | "import" | "other" | "partner" | "phone" | "referral" | "web_form" or null Only leads from this source.
query owner_user_id string (uuid) or null Only this person's.
query unassigned boolean Only the leads nobody owns. (default false)
query q string or null Text in the name, company, e-mail or number. (max length 200)
query limit integer How many, the newest first. (min 1; max 1000; default 200)

Example request

curl "$ERP_URL/v1/leads" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of LeadOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "company": "Beispiel GmbH",
    "country": "DE",
    "created_at": "2026-10-23T09:12:00Z",
    "created_by": {
      "id": "system:web_form",
      "name": "Contact us",
      "type": "system"
    },
    "email": "eva@beispiel.example",
    "email_opt_out": false,
    "entity": "CH01",
    "erased": false,
    "first_name": "Eva",
    "form_id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
    "form_name": "Contact us",
    "id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
    "last_name": "Beispiel",
    "message": "Please send us a quote for 25 seats.",
    "name": "Eva Beispiel",
    "number": "LEAD-000042",
    "owner": {
      "email": "alice@example.com",
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Alice"
    },
    "role": "Head of Finance",
    "source": "web_form",
    "source_detail": "Contact us",
    "status": "new"
  }
]

POST /v1/leads

Add a lead

A person who may become a customer: name, company, e-mail, phone, where they came from (source) and who works on them (owner_user_id); it starts new. An agent may add one. Website forms add theirs themselves. Needs an Idempotency-Key.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id create_lead_v1_leads_post

Request body

application/json (required): LeadInput

Field Type Required Description
entity string yes Code (or id) of the company whose lead it is.
first_name string or null First name. (max length 100)
last_name string yes Last name. (min length 1; max length 100)
company string or null Their company. (max length 200)
role string or null Their job title. (max length 100)
email string or null E-mail address. (max length 200)
phone string or null Phone number. (max length 50)
country string or null Their country (ISO 3166-1). (pattern ^[A-Z]{2}$)
message string or null What they asked. (max length 5000)
source "advertising" | "email" | "event" | "hubspot" | "import" | "other" | "partner" | "phone" | "referral" | "web_form" Where the lead came from: web_form, phone, email, referral, partner, event, advertising, import, hubspot, other. (default "other")
source_detail string or null The campaign, event or list, e.g. a fair. (max length 200)
owner_user_id string (uuid) or null Who works on the lead (GET /v1/people); omitted: nobody yet.
email_opt_out boolean They asked not to get sales or marketing e-mails. (default false)

Example request

curl -X POST "$ERP_URL/v1/leads" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "company": "Beispiel GmbH",
  "country": "DE",
  "email": "eva@beispiel.example",
  "entity": "CH01",
  "first_name": "Eva",
  "last_name": "Beispiel",
  "owner_user_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "phone": "+49 30 1234567",
  "role": "Head of Finance",
  "source": "event",
  "source_detail": "Swiss Finance Day 2026"
}'

Responses

Status Meaning Body
201 Created LeadOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "company": "Beispiel GmbH",
  "country": "DE",
  "created_at": "2026-10-23T09:12:00Z",
  "created_by": {
    "id": "system:web_form",
    "name": "Contact us",
    "type": "system"
  },
  "email": "eva@beispiel.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Eva",
  "form_id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "form_name": "Contact us",
  "id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
  "last_name": "Beispiel",
  "message": "Please send us a quote for 25 seats.",
  "name": "Eva Beispiel",
  "number": "LEAD-000042",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "role": "Head of Finance",
  "source": "web_form",
  "source_detail": "Contact us",
  "status": "new"
}

GET /v1/leads/summary

Count leads by status

How many leads the company has at each status, where the open ones came from, how many nobody owns, and how many were converted in the last 30 days.

Required scope read (admin implies every scope)
Operation id lead_summary_v1_leads_summary_get

Parameters

In Name Type Required Description
query entity string yes Code (or id) of the company.

Example request

curl "$ERP_URL/v1/leads/summary?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK LeadSummary
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "entity": "string",
  "by_status": {},
  "open_by_source": {},
  "unassigned": 1,
  "converted_last_30_days": 1
}

GET /v1/leads/{lead_id}

Get a lead

One lead; a converted one names its customer, contact and deal.

Required scope read (admin implies every scope)
Operation id get_lead_v1_leads__lead_id__get

Parameters

In Name Type Required Description
path lead_id string (uuid) yes Id of the lead (GET /v1/leads).

Example request

curl "$ERP_URL/v1/leads/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK LeadOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "company": "Beispiel GmbH",
  "country": "DE",
  "created_at": "2026-10-23T09:12:00Z",
  "created_by": {
    "id": "system:web_form",
    "name": "Contact us",
    "type": "system"
  },
  "email": "eva@beispiel.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Eva",
  "form_id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "form_name": "Contact us",
  "id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
  "last_name": "Beispiel",
  "message": "Please send us a quote for 25 seats.",
  "name": "Eva Beispiel",
  "number": "LEAD-000042",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "role": "Head of Finance",
  "source": "web_form",
  "source_detail": "Contact us",
  "status": "new"
}

PATCH /v1/leads/{lead_id}

Change a lead

Change what the request names: its status (new, working, or disqualified with a reason), owner, source or the person's details ("" clears a text field but the last name). The audit trail records which fields changed, never their values. 409 lead_converted for a converted lead, lead_erased for an erased one.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id update_lead_v1_leads__lead_id__patch

Parameters

In Name Type Required Description
path lead_id string (uuid) yes Id of the lead (GET /v1/leads).

Request body

application/json (required): LeadUpdate

Field Type Required Description
status "disqualified" | "new" | "working" or null new, working or disqualified (with disqualified_reason); a disqualified lead moved back to new or working loses its reason. Converting is POST /v1/leads/{lead_id}/convert.
disqualified_reason string or null Why it is not a fit. (min length 3; max length 500)
first_name string or null "" clears. (max length 100)
last_name string or null Last name. (min length 1; max length 100)
company string or null "" clears. (max length 200)
role string or null "" clears. (max length 100)
email string or null "" clears. (max length 200)
phone string or null "" clears. (max length 50)
country string or null "" clears. (pattern ^([A-Z]{2})?$)
message string or null "" clears. (max length 5000)
source "advertising" | "email" | "event" | "hubspot" | "import" | "other" | "partner" | "phone" | "referral" | "web_form" or null Where the lead came from.
source_detail string or null "" clears. (max length 200)
owner_user_id string (uuid) | "" or null Who works on it; "" leaves it to nobody.
email_opt_out boolean or null No sales or marketing e-mails.

Example request

curl -X PATCH "$ERP_URL/v1/leads/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "owner_user_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "status": "working"
}'

Responses

Status Meaning Body
200 OK LeadOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "company": "Beispiel GmbH",
  "country": "DE",
  "created_at": "2026-10-23T09:12:00Z",
  "created_by": {
    "id": "system:web_form",
    "name": "Contact us",
    "type": "system"
  },
  "email": "eva@beispiel.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Eva",
  "form_id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "form_name": "Contact us",
  "id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
  "last_name": "Beispiel",
  "message": "Please send us a quote for 25 seats.",
  "name": "Eva Beispiel",
  "number": "LEAD-000042",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "role": "Head of Finance",
  "source": "web_form",
  "source_detail": "Contact us",
  "status": "new"
}

POST /v1/leads/{lead_id}/convert

Convert a lead into a customer, contact and deal

In one step: the customer (an existing one, party, or a new one, customer, created with every check a new customer has), the person as its contact (new, or an existing one, contact_id) and, unless create_deal is false, a deal with them owned by the lead's owner, at qualified by default, whose first note is what the person wrote. The lead then names all three and is final. 409 lead_converted, lead_disqualified or lead_erased. A new customer without a country takes the lead's, else the company's; its currency and language follow the country (see creating a customer). Needs an Idempotency-Key.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id convert_lead_v1_leads__lead_id__convert_post

Parameters

In Name Type Required Description
path lead_id string (uuid) yes Id of the lead (GET /v1/leads).

Request body

application/json (required): LeadConvert

Field Type Required Description
party string or null Code (or id) of the existing customer the person works for; or customer.
customer LeadCustomer or null The new customer to create; or party.
contact_id string (uuid) or null An existing contact of party who is this person; omitted: the lead becomes a new contact of the customer.
create_deal boolean Open a deal with the customer. (default true)
deal_title string or null The deal; omitted: the company. (min length 1; max length 200)
amount decimal string The deal's expected net value. (min 0; default "0")
currency string or null The deal's currency; omitted: the customer's. (pattern ^[A-Z]{3}$)
stage "lead" | "negotiation" | "proposal" | "qualified" The open stage the deal starts at (a converted lead is qualified). (default "qualified")
expected_close string (date) or null When it should close.

Example request

curl -X POST "$ERP_URL/v1/leads/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/convert" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "15000.00",
  "customer": {
    "code": "C-BEISPIEL",
    "country": "DE",
    "currency": "EUR"
  },
  "deal_title": "Platform for 25 seats",
  "expected_close": "2026-12-15"
}'

Responses

Status Meaning Body
200 OK LeadOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "company": "Beispiel GmbH",
  "country": "DE",
  "created_at": "2026-10-23T09:12:00Z",
  "created_by": {
    "id": "system:web_form",
    "name": "Contact us",
    "type": "system"
  },
  "email": "eva@beispiel.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Eva",
  "form_id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "form_name": "Contact us",
  "id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
  "last_name": "Beispiel",
  "message": "Please send us a quote for 25 seats.",
  "name": "Eva Beispiel",
  "number": "LEAD-000042",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "role": "Head of Finance",
  "source": "web_form",
  "source_detail": "Contact us",
  "status": "new"
}

POST /v1/leads/{lead_id}/erase

Erase a lead's personal data

Empty the lead's name, company, role, e-mail, phone and message for good (the person asked to be forgotten). The row stays with its number, status and source; it cannot be undone. A converted lead's customer and contact are erased on their own. A person's decision: 403 lead_erase_agent for an agent. 409 lead_erased if already erased.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id erase_lead_v1_leads__lead_id__erase_post

Parameters

In Name Type Required Description
path lead_id string (uuid) yes Id of the lead (GET /v1/leads).

Example request

curl -X POST "$ERP_URL/v1/leads/8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b/erase" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK LeadOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "company": "Beispiel GmbH",
  "country": "DE",
  "created_at": "2026-10-23T09:12:00Z",
  "created_by": {
    "id": "system:web_form",
    "name": "Contact us",
    "type": "system"
  },
  "email": "eva@beispiel.example",
  "email_opt_out": false,
  "entity": "CH01",
  "erased": false,
  "first_name": "Eva",
  "form_id": "9f2a3b4c-5d6e-4f7a-8b8c-9d0e1f2a3b4c",
  "form_name": "Contact us",
  "id": "8e1f2a3b-4c5d-4e6f-9a7b-8c9d0e1f2a3b",
  "last_name": "Beispiel",
  "message": "Please send us a quote for 25 seats.",
  "name": "Eva Beispiel",
  "number": "LEAD-000042",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "role": "Head of Finance",
  "source": "web_form",
  "source_detail": "Contact us",
  "status": "new"
}

GET /v1/me/mailbox

Get your mailbox link

Whether this system offers the Google link (available, with the OAuth client_id and scopes the browser asks for consent with) and your own link: the account, whether Google allowed mail and calendar, how far each was read and the last error. An API key has no mailbox (linked false).

Required scope read (admin implies every scope)
Operation id get_mailbox_v1_me_mailbox_get

Example request

curl "$ERP_URL/v1/me/mailbox" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK MailboxStatus
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "available": true,
  "client_id": "string",
  "scopes": [
    "string"
  ],
  "linked": true,
  "provider": "google",
  "account_email": "string",
  "mail": true,
  "calendar": true,
  "status": "active",
  "linked_at": "2026-09-21T09:30:00Z",
  "last_sync_at": "2026-09-21T09:30:00Z",
  "last_error": "string",
  "mail_read_to": "2026-09-21T09:30:00Z",
  "calendar_read_to": "2026-09-21T09:30:00Z"
}

DELETE /v1/me/mailbox

Unlink your mailbox

Unlink your Google account: Google takes the access back and nothing more is read. What was logged stays on the timelines (an activity is never deleted).

Required scope read (admin implies every scope)
Idempotency-Key not used
Operation id unlink_mailbox_v1_me_mailbox_delete

Example request

curl -X DELETE "$ERP_URL/v1/me/mailbox" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK MailboxStatus
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "available": true,
  "client_id": "string",
  "scopes": [
    "string"
  ],
  "linked": true,
  "provider": "google",
  "account_email": "string",
  "mail": true,
  "calendar": true,
  "status": "active",
  "linked_at": "2026-09-21T09:30:00Z",
  "last_sync_at": "2026-09-21T09:30:00Z",
  "last_error": "string",
  "mail_read_to": "2026-09-21T09:30:00Z",
  "calendar_read_to": "2026-09-21T09:30:00Z"
}

POST /v1/me/mailbox/google

Link your Google account

Link your Gmail and Google Calendar with the one-time code Google's consent popup gave the browser. From then on your e-mails and meetings with the contact of an open deal, or with a customer or one of its contacts, are logged on that deal's or customer's timeline (the last 30 days first). Read-only at Google: nothing is ever sent. Linking again replaces the link. 409 for an API key, in a test copy, when Google refuses the code, or when this system does not offer the link; 422 when Google was allowed neither mail nor calendar.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id link_google_v1_me_mailbox_google_post

Request body

application/json (required): LinkInput

Field Type Required Description
code string yes The one-time code Google's consent popup gave the browser (Google Identity Services' code client, redirect postmessage). (min length 1; max length 2000)

Example request

curl -X POST "$ERP_URL/v1/me/mailbox/google" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "4/0AeanS0b..."
}'

Responses

Status Meaning Body
200 OK MailboxStatus
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "available": true,
  "client_id": "string",
  "scopes": [
    "string"
  ],
  "linked": true,
  "provider": "google",
  "account_email": "string",
  "mail": true,
  "calendar": true,
  "status": "active",
  "linked_at": "2026-09-21T09:30:00Z",
  "last_sync_at": "2026-09-21T09:30:00Z",
  "last_error": "string",
  "mail_read_to": "2026-09-21T09:30:00Z",
  "calendar_read_to": "2026-09-21T09:30:00Z"
}

POST /v1/me/mailbox/sync

Read your mailbox now

Read your linked mailbox and calendar now (a job reads every linked one every 15 minutes) and log what is about deals and customers. Returns how many e-mails and meetings were read and how many were newly logged. 409 when nothing is linked or Google stopped accepting the link (then link again).

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id sync_mailbox_v1_me_mailbox_sync_post

Example request

curl -X POST "$ERP_URL/v1/me/mailbox/sync" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK SyncOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope

Example response (shape)

{
  "emails": 1,
  "meetings": 1,
  "read_emails": 1,
  "read_meetings": 1,
  "cases": 0,
  "mailbox": {
    "available": true,
    "client_id": "string",
    "scopes": [
      "string"
    ],
    "linked": true,
    "provider": "google",
    "account_email": "string",
    "mail": true,
    "calendar": true,
    "status": "active",
    "linked_at": "2026-09-21T09:30:00Z",
    "last_sync_at": "2026-09-21T09:30:00Z",
    "last_error": "string",
    "mail_read_to": "2026-09-21T09:30:00Z",
    "calendar_read_to": "2026-09-21T09:30:00Z"
  }
}

GET /v1/me/record-access

Which sales records you see

Whether you see every sales record, your team's or your own, and whose.

Required scope read (admin implies every scope)
Operation id my_record_access_v1_me_record_access_get

Example request

curl "$ERP_URL/v1/me/record-access" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK MyRecordAccess
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "record_visibility": "all",
  "people": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "email": "string",
      "name": "string"
    }
  ]
}

GET /v1/opportunities

List opportunities

The deals of the pipeline, the newest first, each with its weighted amount. Someone who sees only their own or their team's records gets only those deals. Read-only.

Required scope read (admin implies every scope)
Operation id list_opportunities_v1_opportunities_get

Parameters

In Name Type Required Description
query entity string or null Only this entity's deals (code or id).
query stage "lead" | "lost" | "negotiation" | "proposal" | "qualified" | "won" or null Only deals at this stage.
query open boolean Only open deals (not won or lost). (default false)
query party_id string (uuid) or null Only this customer's deals.
query owner_user_id string (uuid) or null Only this owner's deals.
query kind "expansion" | "new_business" | "renewal" or null Only deals of this kind (renewal: the renewal deals of subscriptions).
query limit integer How many deals, the newest first. (min 1; max 1000; default 100)

Example request

curl "$ERP_URL/v1/opportunities" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of OpportunityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "amount": "15000.00",
    "contact_email": "eva@beispiel.example",
    "contact_name": "Eva Beispiel",
    "created_at": "2026-10-04T08:00:00Z",
    "created_by": {
      "id": "user:alice",
      "name": "Alice",
      "type": "human"
    },
    "currency": "CHF",
    "custom": {},
    "entity": "CH01",
    "expected_close": "2026-12-15",
    "forecast_category": "best_case",
    "forecast_category_set": false,
    "id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
    "kind": "new_business",
    "next_step": "Demo on 20 October",
    "number": "OPP-000001",
    "owner": {
      "email": "alice@example.com",
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Alice"
    },
    "probability": 50,
    "prospect_country": "DE",
    "prospect_name": "Beispiel GmbH",
    "source": "Trade fair",
    "stage": "proposal",
    "title": "Platform for 25 seats",
    "weighted_amount": "7500.00"
  }
]

POST /v1/opportunities

Create an opportunity

A deal in an entity's pipeline, with a customer (party) or with a prospect that is not a customer yet (prospect_name). It gets the next number of the entity (OPP-000001) and the stage's default probability unless probability is given. Nothing is posted. 404 for an unknown entity or customer (a vendor is not one); 422 for an owner who is not a person of the organization. Needs an Idempotency-Key.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id create_opportunity_v1_opportunities_post

Request body

application/json (required): OpportunityInput

Field Type Required Description
entity string yes Code (or id) of the entity whose pipeline the deal is in.
title string yes What the deal is. (min length 1; max length 200)
party string or null Code (or id) of the customer; omit it for a prospect that is not a customer yet (then prospect_name is required).
prospect_name string or null The prospect's company name. (min length 1; max length 200)
prospect_country string or null The prospect's country (ISO 3166-1). (pattern ^[A-Z]{2}$)
contact_name string or null The person the deal is with. (max length 200)
contact_email string or null Their e-mail. (max length 200)
contact_phone string or null Their phone. (max length 50)
kind "expansion" | "new_business" | "renewal" new_business, expansion (more for an existing customer) or renewal. (default "new_business")
stage "lead" | "negotiation" | "proposal" | "qualified" The open stage it starts at. (default "lead")
probability integer or null Percent; omitted: the stage's default (lead (10 %), qualified (25 %), proposal (50 %), negotiation (75 %), won (100 %) or lost (0 %)). (min 0; max 100)
currency string or null Currency of the amount; omitted: the customer's currency, else the entity's functional currency. (pattern ^[A-Z]{3}$)
amount decimal string Expected net value of the deal, e.g. 15000.00. (min 0; default "0")
expected_close string (date) or null When it should close.
owner_user_id string (uuid) or null Who owns the deal (GET /v1/people); omitted: you (a person; a key or an agent: the person behind it). Someone who sees only their own or their team's records names one of those people.
source string or null Where the lead came from. (max length 100)
next_step string or null What happens next. (max length 500)
forecast_category "best_case" | "commit" | "omitted" | "pipeline" or null Where the deal counts in the forecast: commit (expected to close in its month), best_case (could close), pipeline (still being worked) or omitted (left out of the forecast); null: as the stage says (negotiation commit, proposal best case, lead and qualified pipeline).
contact_id string (uuid) or null The person at the customer the deal is with (GET /v1/contacts?party_id=); a deal with a prospect names the person in contact_name instead.
custom map of string | number | integer | boolean or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.

Example request

curl -X POST "$ERP_URL/v1/opportunities" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "15000.00",
  "contact_email": "eva@beispiel.example",
  "contact_name": "Eva Beispiel",
  "currency": "CHF",
  "entity": "CH01",
  "expected_close": "2026-12-15",
  "owner_user_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "prospect_country": "DE",
  "prospect_name": "Beispiel GmbH",
  "source": "Trade fair",
  "title": "Platform for 25 seats"
}'

Responses

Status Meaning Body
201 Created OpportunityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "amount": "15000.00",
  "contact_email": "eva@beispiel.example",
  "contact_name": "Eva Beispiel",
  "created_at": "2026-10-04T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "currency": "CHF",
  "custom": {},
  "entity": "CH01",
  "expected_close": "2026-12-15",
  "forecast_category": "best_case",
  "forecast_category_set": false,
  "id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "kind": "new_business",
  "next_step": "Demo on 20 October",
  "number": "OPP-000001",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "probability": 50,
  "prospect_country": "DE",
  "prospect_name": "Beispiel GmbH",
  "source": "Trade fair",
  "stage": "proposal",
  "title": "Platform for 25 seats",
  "weighted_amount": "7500.00"
}

GET /v1/opportunities/pipeline

Get the pipeline of an entity

The open deals by stage and currency (count, amount, amount weighted by probability), the deals won and lost in the range, and the entity's open and overdue tasks. Amounts are summed per currency and never converted. Read-only.

Required scope read (admin implies every scope)
Operation id get_pipeline_v1_opportunities_pipeline_get

Parameters

In Name Type Required Description
query entity string yes Entity code (or id).
query closed_from string (date) or null First close day of the won and lost deals; default 1 January.
query closed_to string (date) or null Last close day of the won and lost deals; default today.

Example request

curl "$ERP_URL/v1/opportunities/pipeline?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK Pipeline
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "closed": [
    {
      "amount": "12000.00",
      "count": 1,
      "currency": "CHF",
      "stage": "won",
      "weighted_amount": "12000.00"
    }
  ],
  "closed_from": "2026-01-01",
  "closed_to": "2026-10-04",
  "entity": "CH01",
  "open": [
    {
      "amount": "30000.00",
      "count": 2,
      "currency": "CHF",
      "stage": "proposal",
      "weighted_amount": "15000.00"
    }
  ],
  "open_tasks": 3,
  "overdue_tasks": 1
}

GET /v1/opportunities/{opportunity_id}

Get an opportunity

One deal (its timeline: GET /v1/activities?opportunity_id=). 404 when it does not exist.

Required scope read (admin implies every scope)
Operation id get_opportunity_v1_opportunities__opportunity_id__get

Parameters

In Name Type Required Description
path opportunity_id string (uuid) yes Id of the opportunity (GET /v1/opportunities).

Example request

curl "$ERP_URL/v1/opportunities/3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK OpportunityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "amount": "15000.00",
  "contact_email": "eva@beispiel.example",
  "contact_name": "Eva Beispiel",
  "created_at": "2026-10-04T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "currency": "CHF",
  "custom": {},
  "entity": "CH01",
  "expected_close": "2026-12-15",
  "forecast_category": "best_case",
  "forecast_category_set": false,
  "id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "kind": "new_business",
  "next_step": "Demo on 20 October",
  "number": "OPP-000001",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "probability": 50,
  "prospect_country": "DE",
  "prospect_name": "Beispiel GmbH",
  "source": "Trade fair",
  "stage": "proposal",
  "title": "Platform for 25 seats",
  "weighted_amount": "7500.00"
}

PATCH /v1/opportunities/{opportunity_id}

Change an opportunity

Change what the body names (omitted fields keep their value, "" empties a text field), for example the customer once the prospect is set up as one (party). The stage changes through POST .../stage. 409 for a won deal (only owner_user_id and next_step change) and for the probability of a won or lost one; the change is audited with the previous values.

Required scope draft (admin implies every scope)
Idempotency-Key not used
Operation id update_opportunity_v1_opportunities__opportunity_id__patch

Parameters

In Name Type Required Description
path opportunity_id string (uuid) yes Id of the opportunity (GET /v1/opportunities).

Request body

application/json (required): OpportunityUpdate

Field Type Required Description
title string or null New title. (min length 1; max length 200)
party string or null Code (or id) of the customer, e.g. once the prospect is set up as one.
prospect_name string or null "" clears. (max length 200)
prospect_country string or null "" clears. (pattern ^([A-Z]{2})?$)
contact_name string or null "" clears. (max length 200)
contact_email string or null "" clears. (max length 200)
contact_phone string or null "" clears. (max length 50)
kind "expansion" | "new_business" | "renewal" or null New kind.
probability integer or null Percent (an open deal only). (min 0; max 100)
currency string or null New currency. (pattern ^[A-Z]{3}$)
amount decimal string or null New expected value. (min 0)
expected_close string (date) or null New close date.
owner_user_id string (uuid) or null New owner; null clears. Someone who sees only their own or their team's records hands a deal only to one of those people.
source string or null "" clears. (max length 100)
next_step string or null "" clears. (max length 500)
forecast_category "best_case" | "commit" | "omitted" | "pipeline" or null An open deal: where it counts in the forecast, commit (expected to close in its month), best_case (could close), pipeline (still being worked) or omitted (left out of the forecast); null: as the stage says (negotiation commit, proposal best case, lead and qualified pipeline) (null clears the choice).
contact_id string (uuid) | "" or null The person at the customer the deal is with; "" clears.
custom map of string | number | integer | boolean or null or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.

Example request

curl -X PATCH "$ERP_URL/v1/opportunities/3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": "18000.00",
  "expected_close": "2027-01-31"
}'

Responses

Status Meaning Body
200 OK OpportunityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "amount": "15000.00",
  "contact_email": "eva@beispiel.example",
  "contact_name": "Eva Beispiel",
  "created_at": "2026-10-04T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "currency": "CHF",
  "custom": {},
  "entity": "CH01",
  "expected_close": "2026-12-15",
  "forecast_category": "best_case",
  "forecast_category_set": false,
  "id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "kind": "new_business",
  "next_step": "Demo on 20 October",
  "number": "OPP-000001",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "probability": 50,
  "prospect_country": "DE",
  "prospect_name": "Beispiel GmbH",
  "source": "Trade fair",
  "stage": "proposal",
  "title": "Platform for 25 seats",
  "weighted_amount": "7500.00"
}

POST /v1/opportunities/{opportunity_id}/stage

Move an opportunity to a stage

Move a deal to an open stage, to won (it needs a customer: 409 for a prospect; final from then on) or to lost (with lost_reason), or reopen a lost deal. won and lost set the probability to 100 and 0 and the close day (on, default today). The deal's timeline gets a stage_change activity with the note. 409 for a won deal or the stage it is at. Needs an Idempotency-Key.

Required scope draft (admin implies every scope)
Idempotency-Key required
Operation id set_opportunity_stage_v1_opportunities__opportunity_id__stage_post

Parameters

In Name Type Required Description
path opportunity_id string (uuid) yes Id of the opportunity (GET /v1/opportunities).

Request body

application/json (required): StageInput

Field Type Required Description
stage "lead" | "lost" | "negotiation" | "proposal" | "qualified" | "won" yes The new stage: lead (10 %), qualified (25 %), proposal (50 %), negotiation (75 %), won (100 %) or lost (0 %).
probability integer or null For an open stage: percent; omitted: the stage's default. Ignored for won and lost. (min 0; max 100)
lost_reason string or null Why it was lost (for lost). (min length 3; max length 1000)
on string (date) or null For won and lost: the day it closed; omitted: today.
note string or null A note kept with the stage change. (max length 5000)

Example request

curl -X POST "$ERP_URL/v1/opportunities/3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c/stage" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "Pricing agreed in principle",
  "stage": "negotiation"
}'

Responses

Status Meaning Body
200 OK OpportunityOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "amount": "15000.00",
  "contact_email": "eva@beispiel.example",
  "contact_name": "Eva Beispiel",
  "created_at": "2026-10-04T08:00:00Z",
  "created_by": {
    "id": "user:alice",
    "name": "Alice",
    "type": "human"
  },
  "currency": "CHF",
  "custom": {},
  "entity": "CH01",
  "expected_close": "2026-12-15",
  "forecast_category": "best_case",
  "forecast_category_set": false,
  "id": "3f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
  "kind": "new_business",
  "next_step": "Demo on 20 October",
  "number": "OPP-000001",
  "owner": {
    "email": "alice@example.com",
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Alice"
  },
  "probability": 50,
  "prospect_country": "DE",
  "prospect_name": "Beispiel GmbH",
  "source": "Trade fair",
  "stage": "proposal",
  "title": "Platform for 25 seats",
  "weighted_amount": "7500.00"
}

PUT /v1/people/{user_id}/record-visibility

Set which sales records a person sees

all (every customer, contact, deal and activity), team (the records they or anyone in a team of theirs own) or own (the records they own), from their next request on, on every screen, the REST API and MCP alike; their keys and the agents they use see the same. Finance documents and the ledger are not sales records: rights (scopes) govern them. Audited.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id set_record_visibility_v1_people__user_id__record_visibility_put

Parameters

In Name Type Required Description
path user_id string (uuid) yes Id of the person (GET /v1/people).

Request body

application/json (required): VisibilityInput

Field Type Required Description
record_visibility "all" | "own" | "team" yes Which sales records the person sees: all (every customer, contact, deal and activity), team (the records they or anyone in a team of theirs own) or own (the records they own).

Example request

curl -X PUT "$ERP_URL/v1/people/8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d/record-visibility" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "record_visibility": "team"
}'

Responses

Status Meaning Body
200 OK PersonAccess
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "person": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "email": "string",
    "name": "string"
  },
  "status": "string",
  "record_visibility": "all",
  "teams": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "string"
    }
  ]
}

GET /v1/record-access

List what each person sees

Every person of the organization with which sales records they see (all, team or own) and the teams they are in, by name.

Required scope admin (admin implies every scope)
Operation id list_record_access_v1_record_access_get

Example request

curl "$ERP_URL/v1/record-access" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of PersonAccess
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "person": {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "email": "string",
      "name": "string"
    },
    "status": "string",
    "record_visibility": "all",
    "teams": [
      {
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "name": "string"
      }
    ]
  }
]

POST /v1/record-access/hand-over

Hand one person's records to another

Give every customer, contact and deal (open, won and lost) one person owns to another, in one company or all: when someone leaves or changes territory. Activities stay with who did them; open tasks keep their assignee, and how many are left is returned. For someone who sees all records (403 hand_over_needs_all_records); 422 owner_disabled for a disabled new owner. Each record's history shows the change. Needs an Idempotency-Key.

Required scope admin (admin implies every scope)
Idempotency-Key required
Operation id hand_over_records_v1_record_access_hand_over_post

Request body

application/json (required): HandOverInput

Field Type Required Description
from_user_id string (uuid) yes Whose records (GET /v1/people).
to_user_id string (uuid) yes Who gets them.
entity string or null Only this company's records (code or id); omitted: every one.

Example request

curl -X POST "$ERP_URL/v1/record-access/hand-over" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "from_user_id": "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d",
  "to_user_id": "9c2a3b4c-5d6e-4f7a-8b9c-0d1e2f3a4b5c"
}'

Responses

Status Meaning Body
200 OK HandOverOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "customers": 1,
  "contacts": 1,
  "deals": 1,
  "open_tasks_left": 1
}

GET /v1/sales-teams

List sales teams

The organization's sales teams, by name, with their people.

Required scope read (admin implies every scope)
Operation id list_sales_teams_v1_sales_teams_get

Example request

curl "$ERP_URL/v1/sales-teams" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of SalesTeamOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "created_at": "2026-10-23T08:00:00Z",
    "id": "6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
    "members": [
      {
        "email": "lena@example.com",
        "id": "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d",
        "name": "Lena"
      }
    ],
    "name": "Sales DACH"
  }
]

POST /v1/sales-teams

Set up a sales team

A team of sales people; a person may be in several. People who see their team's records (PUT /v1/people/{user_id}/record-visibility) see the records of everyone in their teams. 409 sales_team_exists for a name in use. Needs an Idempotency-Key.

Required scope admin (admin implies every scope)
Idempotency-Key required
Operation id create_sales_team_v1_sales_teams_post

Request body

application/json (required): SalesTeamInput

Field Type Required Description
name string yes The team's name, unique. (min length 1; max length 80)
member_ids array of string (uuid) Its people (GET /v1/people); a person may be in several teams.

Example request

curl -X POST "$ERP_URL/v1/sales-teams" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "member_ids": [
    "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d"
  ],
  "name": "Sales DACH"
}'

Responses

Status Meaning Body
201 Created SalesTeamOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "id": "6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
  "members": [
    {
      "email": "lena@example.com",
      "id": "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d",
      "name": "Lena"
    }
  ],
  "name": "Sales DACH"
}

PATCH /v1/sales-teams/{team_id}

Rename a sales team

A new name for the team (409 sales_team_exists for one in use).

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id rename_sales_team_v1_sales_teams__team_id__patch

Parameters

In Name Type Required Description
path team_id string (uuid) yes Id of the team (GET /v1/sales-teams).

Request body

application/json (required): SalesTeamUpdate

Field Type Required Description
name string yes The new name. (min length 1; max length 80)

Example request

curl -X PATCH "$ERP_URL/v1/sales-teams/6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Sales Switzerland"
}'

Responses

Status Meaning Body
200 OK SalesTeamOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "id": "6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
  "members": [
    {
      "email": "lena@example.com",
      "id": "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d",
      "name": "Lena"
    }
  ],
  "name": "Sales DACH"
}

DELETE /v1/sales-teams/{team_id}

Remove a sales team

Remove the team. Its people and their records stay; who sees their team's records sees from now on what their other teams give them. The audit trail keeps the team's name and people.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id delete_sales_team_v1_sales_teams__team_id__delete

Parameters

In Name Type Required Description
path team_id string (uuid) yes Id of the team (GET /v1/sales-teams).

Example request

curl -X DELETE "$ERP_URL/v1/sales-teams/6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
204 No content
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

PUT /v1/sales-teams/{team_id}/members/{user_id}

Add a person to a sales team

Add the person to the team (again: nothing changes): anyone of the organization, also someone invited who has not signed in yet. 404 for someone who is not.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id add_sales_team_member_v1_sales_teams__team_id__members__user_id__put

Parameters

In Name Type Required Description
path team_id string (uuid) yes Id of the team (GET /v1/sales-teams).
path user_id string (uuid) yes Id of the person (GET /v1/people).

Example request

curl -X PUT "$ERP_URL/v1/sales-teams/6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f/members/8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK SalesTeamOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "id": "6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
  "members": [
    {
      "email": "lena@example.com",
      "id": "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d",
      "name": "Lena"
    }
  ],
  "name": "Sales DACH"
}

DELETE /v1/sales-teams/{team_id}/members/{user_id}

Take a person out of a sales team

Take the person out of the team; their records stay theirs. 404 not_team_member when they are not in it.

Required scope admin (admin implies every scope)
Idempotency-Key not used
Operation id remove_sales_team_member_v1_sales_teams__team_id__members__user_id__delete

Parameters

In Name Type Required Description
path team_id string (uuid) yes Id of the team (GET /v1/sales-teams).
path user_id string (uuid) yes Id of the person (GET /v1/people).

Example request

curl -X DELETE "$ERP_URL/v1/sales-teams/6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f/members/8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK SalesTeamOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
404 The resource does not exist, or belongs to another organization (not_found). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "created_at": "2026-10-23T08:00:00Z",
  "id": "6c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
  "members": [
    {
      "email": "lena@example.com",
      "id": "8b1f2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d",
      "name": "Lena"
    }
  ],
  "name": "Sales DACH"
}

GET /v1/sales/activity

Get the sales activity per person

Per person (or agent), the calls, meetings, e-mails and notes they logged, the tasks they set and did, the deals they moved to another stage, and the tasks assigned to them that are open and overdue today. Read-only.

Required scope read (admin implies every scope)
Operation id get_sales_activity_v1_sales_activity_get

Parameters

In Name Type Required Description
query entity string yes Entity code (or id).
query date_from string (date) or null First day counted; default the 1st of this month.
query date_to string (date) or null Last day counted; default today.

Example request

curl "$ERP_URL/v1/sales/activity?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK SalesActivityReport
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "date_from": "2026-10-01",
  "date_to": "2026-10-23",
  "entity": "CH01",
  "rows": [],
  "total": {
    "agent": false,
    "calls": 14,
    "emails": 22,
    "key": "total",
    "meetings": 5,
    "name": "",
    "notes": 3,
    "open_tasks": 6,
    "overdue_tasks": 2,
    "stage_changes": 11,
    "tasks_done": 7,
    "tasks_set": 9
  }
}

GET /v1/sales/forecast

Get the sales forecast

By owner and by month: the quota, what is won (closed), and the open deals expected in the months by forecast category (commit, best case, pipeline; omitted ones left out), the forecast (closed plus commit), the best case, attainment and the gap to the quota. Deals expected before the months (late) or without a close date (undated) are counted, not summed. Read-only.

Required scope read (admin implies every scope)
Operation id get_forecast_v1_sales_forecast_get

Parameters

In Name Type Required Description
query entity string yes Entity code (or id).
query from_month string or null First month, YYYY-MM; default the first month of this quarter. (pattern `^\d{4}-(0[1-9]
query to_month string or null Last month, YYYY-MM; default the last month of this quarter. (pattern `^\d{4}-(0[1-9]
query rate_day string (date) or null The day whose ECB fixing converts other currencies into the entity's functional currency (the latest fixing on or before it); default today.

Example request

curl "$ERP_URL/v1/sales/forecast?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK ForecastReport
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "currency": "CHF",
  "entity": "CH01",
  "fixing_date": "2026-10-23",
  "from_month": "2026-10",
  "months": [],
  "rate_day": "2026-10-23",
  "rows": [],
  "to_month": "2026-12",
  "total": {
    "attainment": "32.0",
    "best": "118000.00",
    "best_case": {
      "amount": "30000.00",
      "by_currency": [
        {
          "amount": "30000.00",
          "currency": "CHF"
        }
      ]
    },
    "closed": {
      "amount": "48000.00",
      "by_currency": [
        {
          "amount": "48000.00",
          "currency": "CHF"
        }
      ]
    },
    "commit": {
      "amount": "40000.00",
      "by_currency": [
        {
          "amount": "40000.00",
          "currency": "CHF"
        }
      ]
    },
    "deals": 9,
    "forecast": "88000.00",
    "gap": "102000.00",
    "key": "total",
    "late": 1,
    "pipeline": {
      "amount": "25000.00",
      "by_currency": [
        {
          "amount": "25000.00",
          "currency": "CHF"
        }
      ]
    },
    "quota": "150000.00",
    "undated": 2,
    "weighted": {
      "amount": "53750.00",
      "by_currency": [
        {
          "amount": "53750.00",
          "currency": "CHF"
        }
      ]
    }
  },
  "unconverted": []
}

GET /v1/sales/pipeline

Get the pipeline report

The open deals by owner, by stage and by the month they are expected to close: how many, their value and their value weighted by probability, in the entity's functional currency (each deal converted at one ECB fixing) and in each deal's own currency. late counts the open deals whose expected close day has passed. Read-only.

Required scope read (admin implies every scope)
Operation id get_pipeline_report_v1_sales_pipeline_get

Parameters

In Name Type Required Description
query entity string yes Entity code (or id).
query owner_user_id string (uuid) or null Only this owner's deals.
query rate_day string (date) or null The day whose ECB fixing converts other currencies into the entity's functional currency (the latest fixing on or before it); default today.

Example request

curl "$ERP_URL/v1/sales/pipeline?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK PipelineReport
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "by_month": [],
  "by_owner": [
    {
      "amount": {
        "amount": "42870.00",
        "by_currency": [
          {
            "amount": "30000.00",
            "currency": "CHF"
          },
          {
            "amount": "13700.00",
            "currency": "EUR"
          }
        ]
      },
      "count": 3,
      "key": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "owner": {
        "email": "lena@example.com",
        "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "Lena Meier"
      },
      "weighted": {
        "amount": "19040.00",
        "by_currency": [
          {
            "amount": "13000.00",
            "currency": "CHF"
          },
          {
            "amount": "6165.00",
            "currency": "EUR"
          }
        ]
      }
    }
  ],
  "by_stage": [],
  "currency": "CHF",
  "entity": "CH01",
  "fixing_date": "2026-10-23",
  "late": 1,
  "rate_day": "2026-10-23",
  "today": "2026-10-23",
  "total": {
    "amount": {
      "amount": "42870.00",
      "by_currency": [
        {
          "amount": "30000.00",
          "currency": "CHF"
        },
        {
          "amount": "13700.00",
          "currency": "EUR"
        }
      ]
    },
    "count": 3,
    "key": "total",
    "weighted": {
      "amount": "19040.00",
      "by_currency": [
        {
          "amount": "13000.00",
          "currency": "CHF"
        },
        {
          "amount": "6165.00",
          "currency": "EUR"
        }
      ]
    }
  },
  "unconverted": []
}

GET /v1/sales/quotas

List quotas

Each person's quota per month of the year, in the entity's functional currency. Read-only.

Required scope read (admin implies every scope)
Operation id list_quotas_v1_sales_quotas_get

Parameters

In Name Type Required Description
query entity string yes Entity code (or id).
query year integer or null Calendar year; default this year. (min 2000; max 2100)

Example request

curl "$ERP_URL/v1/sales/quotas?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK array of QuotaOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "amount": "50000.00",
    "currency": "CHF",
    "entity": "CH01",
    "month": "2026-10",
    "person": {
      "email": "lena@example.com",
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Lena Meier"
    },
    "updated_at": "2026-10-01T08:00:00Z",
    "updated_by": {
      "id": "user:alice",
      "name": "Alice",
      "type": "human"
    }
  }
]

PUT /v1/sales/quotas

Set quotas

Set what each person is to win in a month, in the entity's functional currency; a quota already set takes the new amount (audited with the previous one) and 0 takes it back. Needs the approve scope: targets are set by a manager, never by an agent. 422 for a person who is not of the organization or a person and month named twice. Repeating the request changes nothing.

Required scope approve (admin implies every scope)
Idempotency-Key not used
Operation id set_quotas_v1_sales_quotas_put

Request body

application/json (required): QuotaInput

Field Type Required Description
entity string yes Code (or id) of the entity; quotas are in its currency.
quotas array of QuotaLine yes Each person's quota for a month. (min items 1; max items 600)

Example request

curl -X PUT "$ERP_URL/v1/sales/quotas" \
  -H "Authorization: Bearer $ERP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "entity": "CH01",
  "quotas": [
    {
      "amount": "50000.00",
      "month": "2026-10",
      "user_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
    },
    {
      "amount": "50000.00",
      "month": "2026-11",
      "user_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
    }
  ]
}'

Responses

Status Meaning Body
200 OK array of QuotaOut
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
409 The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

[
  {
    "amount": "50000.00",
    "currency": "CHF",
    "entity": "CH01",
    "month": "2026-10",
    "person": {
      "email": "lena@example.com",
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Lena Meier"
    },
    "updated_at": "2026-10-01T08:00:00Z",
    "updated_by": {
      "id": "user:alice",
      "name": "Alice",
      "type": "human"
    }
  }
]

GET /v1/sales/win-rate

Get the win rate

The deals won and lost in the range by owner: how many and their value, the win rate by count and by value, the average deal won and the days it took from creation to close. Read-only.

Required scope read (admin implies every scope)
Operation id get_win_rate_v1_sales_win_rate_get

Parameters

In Name Type Required Description
query entity string yes Entity code (or id).
query closed_from string (date) or null First close day counted; default 1 January.
query closed_to string (date) or null Last close day counted; default today.
query rate_day string (date) or null The day whose ECB fixing converts other currencies into the entity's functional currency (the latest fixing on or before it); default today.

Example request

curl "$ERP_URL/v1/sales/win-rate?entity=CH01" \
  -H "Authorization: Bearer $ERP_KEY"

Responses

Status Meaning Body
200 OK WinRateReport
401 Missing or invalid API key (unauthorized). ErrorEnvelope
403 The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). ErrorEnvelope
422 Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. HTTPValidationError

Example response (shape)

{
  "closed_from": "2026-01-01",
  "closed_to": "2026-10-23",
  "currency": "CHF",
  "entity": "CH01",
  "fixing_date": "2026-10-23",
  "rate_day": "2026-10-23",
  "rows": [],
  "total": {
    "average_days_to_win": "41.5",
    "average_won": "16000.00",
    "key": "total",
    "lost": 2,
    "lost_amount": {
      "amount": "24000.00",
      "by_currency": [
        {
          "amount": "24000.00",
          "currency": "CHF"
        }
      ]
    },
    "value_win_rate": "80.0",
    "win_rate": "75.0",
    "won": 6,
    "won_amount": {
      "amount": "96000.00",
      "by_currency": [
        {
          "amount": "96000.00",
          "currency": "CHF"
        }
      ]
    }
  },
  "unconverted": []
}

Schemas

ActivityInput

Field Type Required Description
opportunity_id string (uuid) or null The deal it is about (GET /v1/opportunities).
lead_id string (uuid) or null The lead it is about, before they are a customer (GET /v1/leads); then name no deal, entity, customer or contact.
entity string or null Code (or id) of the entity, for an activity about a customer only (one about a deal is in the deal's entity).
party string or null Code (or id) of the customer, for an activity about a customer only (one about a deal names the deal's customer).
kind "call" | "email" | "meeting" | "note" | "task" yes note, call, meeting, email or task.
subject string yes What it was or is. (min length 1; max length 200)
body string or null The details. (max length 5000)
occurred_on string (date) or null When it happened (a task: when it was set); omitted: today.
due_on string (date) or null A task: when it is due.
assignee_user_id string (uuid) or null A task: who does it (GET /v1/people); omitted: the deal's or the lead's owner.
owner_user_id string (uuid) or null Who did it, or set the task (GET /v1/people); omitted: you (a key or an agent: the person behind it). It never changes.
contact_id string (uuid) or null The person at the customer it was with (GET /v1/contacts?party_id=); omitted for an activity of a deal: the deal's contact.

ActivityOut

Field Type Required Description
id string (uuid) yes Activity id.
entity string yes Entity code.
opportunity_id string (uuid) or null yes The deal it is about.
opportunity_number string or null yes The deal's number.
party_id string (uuid) or null yes The customer it is about.
party_code string or null yes The customer's code.
lead_id string (uuid) or null The lead it is about.
lead_number string or null The lead's number.
contact_id string (uuid) or null The person it was with.
contact_name string or null Their name; "" once the contact is erased.
kind string yes note, call, meeting, email, task or stage_change (written by a stage change of the deal).
subject string yes What it was or is.
body string or null yes The details.
occurred_on string (date) yes When it happened.
due_on string (date) or null yes A task: when it is due.
assignee Person or null yes A task: who does it.
owner Person or null Who did it, or set the task; null for what an agent, a key or the system recorded without a person behind it.
done_at string (date-time) or null yes A task: when it was done (UTC).
done_by object or null yes A task: who did it.
overdue boolean yes A task not done whose due day has passed (today where the entity is).
source "google_calendar" | "google_mail" or null Where an e-mail or meeting logged from a linked mailbox came from (GET /v1/me/mailbox); null for what a person or the system recorded.
external_url string or null Where to open that e-mail or meeting (Gmail, Google Calendar).
created_by object yes Who recorded it (whose mailbox, if captured).
created_at string (date-time) yes When (UTC).

ActivityRow

Field Type Required Description
key string yes Who: the actor id that recorded it (user:<id> for a person, an agent's or a key's id otherwise), total.
name string yes Their name as recorded.
person Person or null yes The person, when it is a user of the org.
agent boolean yes True for an AI agent.
calls integer yes Calls logged.
meetings integer yes Meetings logged.
emails integer yes E-mails logged.
notes integer yes Notes logged.
tasks_set integer yes Tasks set.
tasks_done integer yes Tasks they marked done in the range.
stage_changes integer yes Deals they moved to another stage.
open_tasks integer yes Tasks assigned to them and not done, today.
overdue_tasks integer yes Of them, past their due day.

ContactInput

Field Type Required Description
first_name string or null First name. (min length 1; max length 100)
role string or null Role or department, e.g. Head of purchasing. A role that says billing, accounting or Buchhaltung makes the contact one an invoice goes to by default. (max length 100)
email string or null E-mail address. (max length 200)
phone string or null Phone number. (max length 50)
mobile string or null Mobile number. (max length 50)
last_name string yes Last name. A name sent instead is split: its last word is the last name, the words before it the first name. (min length 1; max length 100)
entity string yes Code (or id) of the company (entity).
party string yes Code (or id) of the customer or vendor the person is at.
kind "customer" | "vendor" or null Needed only when party is a code of a customer and a vendor.
language "de" | "en" | "fr" | "it" | "rm" | "sv" or null Language to write to them in (de, fr, it, en, sv, rm).
email_opt_out boolean They asked not to get sales or marketing e-mails (invoices still go). (default false)
custom map of string | number | integer | boolean or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.
owner_user_id string (uuid) or null Who looks after the contact (GET /v1/people); omitted: you (a key or an agent: the person behind it).

ContactMerge

Field Type Required Description
into string (uuid) yes Id of the contact that stays (same customer or vendor).

ContactOut

Field Type Required Description
id string (uuid) yes Id of the contact.
entity string yes Code of the company.
party_id string (uuid) yes Id of the customer or vendor.
party_code string yes Its code.
party_name string yes Its name.
party_kind "customer" | "vendor" yes customer or vendor.
name string yes First and last name; "" once erased.
first_name string or null First name.
last_name string or null Last name; null only once erased.
role string or null Role or department.
email string or null E-mail address.
phone string or null Phone number.
mobile string or null Mobile number.
language string or null Language to write to them in (de, fr, it, en, sv, rm).
email_opt_out boolean They asked not to get sales or marketing e-mails. (default false)
custom object Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.
owner Person or null Who looks after the contact.
erased boolean Its personal data was erased (it stays as a placeholder). (default false)
erased_at string (date-time) or null When it was erased (UTC).
merged_into_id string (uuid) or null The contact it was merged into (POST .../merge), if any.
created_by object yes Who added it (actor object).
created_at string (date-time) yes When (UTC).
updated_by object or null Who changed it last.
updated_at string (date-time) or null When (UTC).

ContactUpdate

What changes (null or omitted: as is; "" clears a text field but the last name).

Field Type Required Description
first_name string or null "" clears. (max length 100)
role string or null Role or department, e.g. Head of purchasing. A role that says billing, accounting or Buchhaltung makes the contact one an invoice goes to by default. (max length 100)
email string or null E-mail address. (max length 200)
phone string or null Phone number. (max length 50)
mobile string or null Mobile number. (max length 50)
last_name string or null New. (min length 1; max length 100)
language "de" | "en" | "fr" | "it" | "rm" | "sv" | "" or null "" clears.
email_opt_out boolean or null New opt-out.
custom map of string | number | integer | boolean or null or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.
owner_user_id string (uuid) or null New owner (GET /v1/people). Someone who sees only their own or their team's records hands a contact only to one of those people.

CustomFieldInput

Field Type Required Description
record "company" | "contact" | "deal" yes company, contact or deal.
key string yes How the API names it: lower case letters, digits and _, e.g. industry; never changes. (pattern ^[a-z][a-z0-9_]{0,39}$)
label string yes What the screens call it. (min length 1; max length 80)
type "choice" | "date" | "number" | "text" | "yes_no" yes text, number, date, yes_no or choice; never changes.
choices array of string or null For choice: the choices, in order (1 to 50).
position integer Lower comes first. (min 0; max 1000; default 0)

CustomFieldOut

Field Type Required Description
id string (uuid) yes Id of the field.
record "company" | "contact" | "deal" yes company, contact or deal.
key string yes How the API names it.
label string yes What the screens call it.
type "choice" | "date" | "number" | "text" | "yes_no" yes text, number, date, yes_no or choice.
choices array of string or null A choice field's choices.
position integer yes Lower comes first.
retired boolean yes Hidden from forms; its values stay readable.

CustomFieldUpdate

Field Type Required Description
label string or null New label. (min length 1; max length 80)
choices array of string or null A choice field's new choices; a choice a record holds stays in the list.
position integer or null New position. (min 0; max 1000)
retired boolean or null true hides the field from forms (its values stay readable); false brings it back.

ForecastMonth

Field Type Required Description
month string yes YYYY-MM.
quota string yes Everyone's quota.
closed string yes Won in the month.
commit string yes Commit expected in the month.
best_case string yes Best case expected in the month.
pipeline string yes Pipeline expected in the month.

ForecastReport

Field Type Required Description
entity string yes Entity code.
currency string yes The report's currency (quotas are in it).
rate_day string (date) yes The day whose ECB fixing converts the amounts.
fixing_date string (date) or null yes The fixing used; null when there is none near the day (then every other currency is in unconverted).
unconverted array of string yes Currencies left out of the converted amounts.
from_month string yes First month, YYYY-MM.
to_month string yes Last month, YYYY-MM.
rows array of ForecastRow yes Per owner (and everyone with a quota), the highest quota first.
total ForecastRow yes Everyone.
months array of ForecastMonth yes Every month of the range, in order.

ForecastRow

Field Type Required Description
key string yes The owner's user id, none for no owner, total.
owner Person or null yes The owner.
quota string yes Their quota for the months (0: none set).
closed Figure yes Won in the months (by close day).
commit Figure yes Open, expected in the months, category commit.
best_case Figure yes Open, expected in the months, category best case.
pipeline Figure yes Open, expected in the months, category pipeline.
weighted Figure yes Open, expected in the months and not omitted, weighted by probability.
forecast string yes Closed plus commit, converted: the call.
best string yes Closed plus commit plus best case, converted.
attainment string or null yes Closed of quota, in percent (one decimal); null without a quota. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$)
gap string yes Quota minus closed (negative: above quota).
deals integer yes Open deals expected in the months (not omitted).
late integer yes Open deals whose expected close is before the months (not in the forecast).
undated integer yes Open deals without an expected close.

HandOverInput

Field Type Required Description
from_user_id string (uuid) yes Whose records (GET /v1/people).
to_user_id string (uuid) yes Who gets them.
entity string or null Only this company's records (code or id); omitted: every one.

HandOverOut

Field Type Required Description
customers integer yes How many customers changed owner.
contacts integer yes How many contacts.
deals integer yes How many deals (open, won and lost).
open_tasks_left integer yes Open tasks still assigned to the person handing over: a task keeps its assignee (an activity never changes); the new owner sees them on the deals and customers.

HubSpotAuthorize

Field Type Required Description
entity string yes Code (or id) of the company.
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in. (default "marketingqualifiedlead")
owner_user_id string (uuid) or null Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one.

HubSpotAuthorizeOut

Field Type Required Description
url string yes HubSpot's consent page: open it in the browser.
redirect_uri string yes Where HubSpot sends the browser back.

HubSpotConnection

Field Type Required Description
id string (uuid) yes Id of the connection.
entity string yes The company whose leads the contacts become.
portal_id integer yes HubSpot's id of the account (the Hub ID).
account_url string yes The account's contacts in HubSpot.
method "key" | "oauth" yes oauth: connected on HubSpot's consent page; key: with a private app's access token.
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" yes The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in.
owner Person or null yes Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one.
status "active" | "reconnect" yes reconnect: HubSpot stopped accepting the token; connect again.
read_to string (date-time) yes HubSpot's contacts are read up to here (UTC).
last_sync_at string (date-time) or null yes When HubSpot was last read (UTC).
last_error string or null yes Why the last round failed, in plain words.
leads integer yes Leads that came from this account.
linked_by object yes Who connected it.
linked_at string (date-time) yes When it was connected (UTC).

HubSpotKeyInput

Field Type Required Description
entity string yes Code (or id) of the company.
access_token string yes The access token of a private app in the company's HubSpot with the scopes of GET /v1/hubspot. Kept sealed, never shown again. (min length 10; max length 500)
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in. (default "marketingqualifiedlead")
owner_user_id string (uuid) or null Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one.

HubSpotOAuthInput

Field Type Required Description
code string yes HubSpot's one-time code. (min length 1; max length 2000)
state string yes The state HubSpot returned. (min length 1; max length 2000)

HubSpotSetup

Field Type Required Description
available boolean yes Whether HubSpot can be connected on this system (false: tokens could not be kept sealed).
oauth boolean yes Whether HubSpot's consent page can be used (this system has its HubSpot app); else connect with a private app's access token.
client_id string or null yes Stabiso's HubSpot app, when there is one.
scopes array of string yes What Stabiso needs: tick these on a private app.
connections array of HubSpotConnection yes The companies connected.

HubSpotSyncOut

Field Type Required Description
read integer yes Contacts read from HubSpot.
new_leads integer yes Leads made of them.
updated_leads integer yes New leads that HubSpot's changes updated.
sent integer yes Contacts that Stabiso's news of their lead was sent to.
connection HubSpotConnection yes The connection after the round.

HubSpotUpdate

Field Type Required Description
stage "lead" | "marketingqualifiedlead" | "salesqualifiedlead" or null The lifecycle stage at which a HubSpot contact becomes a lead here: lead, marketingqualifiedlead (the default) or salesqualifiedlead. Contacts at that stage or a later one before opportunity come in.
owner_user_id string (uuid) | "" or null Who owns a lead whose HubSpot owner is not a person here (GET /v1/people); none: Autopilot or a person gives it one. "" clears.

LeadConvert

Field Type Required Description
party string or null Code (or id) of the existing customer the person works for; or customer.
customer LeadCustomer or null The new customer to create; or party.
contact_id string (uuid) or null An existing contact of party who is this person; omitted: the lead becomes a new contact of the customer.
create_deal boolean Open a deal with the customer. (default true)
deal_title string or null The deal; omitted: the company. (min length 1; max length 200)
amount decimal string The deal's expected net value. (min 0; default "0")
currency string or null The deal's currency; omitted: the customer's. (pattern ^[A-Z]{3}$)
stage "lead" | "negotiation" | "proposal" | "qualified" The open stage the deal starts at (a converted lead is qualified). (default "qualified")
expected_close string (date) or null When it should close.

LeadCustomer

The customer converting the lead creates (master data, with the customer controls).

Field Type Required Description
code string or null Its code; left out, the company's next customer number (C-00001). (min length 1; max length 40)
name string or null Legal name; omitted: the lead's company. (min length 1; max length 200)
country string or null Country; omitted: the lead's, else the company's. (pattern ^[A-Z]{2}$)
currency string or null Currency of its documents; omitted: its country's when the company has exchange rates for it, else the company's functional currency. (pattern ^[A-Z]{3}$)
business boolean false for a private person (B2C). (default true)

LeadFormInput

Field Type Required Description
entity string yes Code (or id) of the company whose leads it creates.
name string yes What the form is, e.g. Contact us. (min length 1; max length 100)
owner_user_id string (uuid) or null Who the leads go to (GET /v1/people); omitted: nobody yet.
language "de" | "en" | "fr" | "it" | "sv" The language of its own page. (default "en")
allowed_origins array of string The websites it may be sent from, e.g. https://www.example.ch; empty: any. A browser names its page's site, so a copy of the form on another site is refused. (max items 20)
thank_you string or null What its page says after sending. (max length 500)
redirect_url string or null A page of your own (https) to send people to after a form of your own website was sent; omitted: the thank-you page. (max length 500; pattern ^https://)

LeadFormOut

Field Type Required Description
id string (uuid) yes Form id.
entity string yes Company code.
name string yes What the form is.
key string yes Its public key: it goes into your website. A new key (POST /v1/lead-forms/{form_id}/new-key) stops the old one.
page_path string yes Its own page on this server (to link to or embed in an iframe); POST to the same path sends a lead.
active boolean yes Whether it takes leads.
owner Person or null yes Who its leads go to.
language "de" | "en" | "fr" | "it" | "sv" yes Its page's language.
allowed_origins array of string yes The websites it may be sent from; empty: any.
thank_you string or null yes What its page says after sending.
redirect_url string or null yes Where people go after sending your own form.
leads integer yes How many leads it created.
last_lead_at string (date-time) or null yes When it created the last one (UTC).
created_by object yes Who set it up.
created_at string (date-time) yes When (UTC).
updated_by object or null yes Who changed it last.
updated_at string (date-time) or null yes When (UTC).

LeadFormUpdate

Field Type Required Description
name string or null New name. (min length 1; max length 100)
active boolean or null false switches it off.
owner_user_id string (uuid) | "" or null Who the leads go to; "": nobody.
language "de" | "en" | "fr" | "it" | "sv" or null Its page's language.
allowed_origins array of string or null The websites it may be sent from; []: any. (max items 20)
thank_you string or null "" clears. (max length 500)
redirect_url string or null "" clears. (max length 500; pattern ^(https://.*)?$)

LeadInput

Field Type Required Description
entity string yes Code (or id) of the company whose lead it is.
first_name string or null First name. (max length 100)
last_name string yes Last name. (min length 1; max length 100)
company string or null Their company. (max length 200)
role string or null Their job title. (max length 100)
email string or null E-mail address. (max length 200)
phone string or null Phone number. (max length 50)
country string or null Their country (ISO 3166-1). (pattern ^[A-Z]{2}$)
message string or null What they asked. (max length 5000)
source "advertising" | "email" | "event" | "hubspot" | "import" | "other" | "partner" | "phone" | "referral" | "web_form" Where the lead came from: web_form, phone, email, referral, partner, event, advertising, import, hubspot, other. (default "other")
source_detail string or null The campaign, event or list, e.g. a fair. (max length 200)
owner_user_id string (uuid) or null Who works on the lead (GET /v1/people); omitted: nobody yet.
email_opt_out boolean They asked not to get sales or marketing e-mails. (default false)

LeadOut

Field Type Required Description
id string (uuid) yes Lead id.
entity string yes Company code.
number string yes LEAD-000001, per company.
status "converted" | "disqualified" | "new" | "working" yes new (nobody has talked to them yet), working (someone is on it), disqualified (not a fit, with a reason) or converted (now a customer, a contact and a deal).
name string yes First and last name; "" once erased.
first_name string or null yes First name.
last_name string or null yes Last name.
company string or null yes Their company.
role string or null yes Their job title.
email string or null yes E-mail address.
phone string or null yes Phone number.
country string or null yes Their country.
message string or null yes What they asked.
source "advertising" | "email" | "event" | "hubspot" | "import" | "other" | "partner" | "phone" | "referral" | "web_form" yes Where the lead came from.
source_detail string or null yes The campaign, event, list or form.
form_id string (uuid) or null yes The website form that created it.
form_name string or null yes That form's name.
hubspot_url string or null The HubSpot contact the lead came from (GET /v1/hubspot).
owner Person or null yes Who works on it.
email_opt_out boolean yes No sales or marketing e-mails.
disqualified_reason string or null yes Why it is not a fit.
party_id string (uuid) or null yes Converted: the customer.
party_code string or null yes The customer's code.
party_name string or null yes The customer's name.
contact_id string (uuid) or null yes Converted: the person as its contact.
opportunity_id string (uuid) or null yes Converted: the deal, if one was opened.
opportunity_number string or null yes The deal's number.
converted_at string (date-time) or null yes When it was converted (UTC).
converted_by object or null yes Who converted it.
erased boolean yes Its personal data was erased.
erased_at string (date-time) or null yes When (UTC).
created_by object yes Who recorded it (a form: the form's name).
created_at string (date-time) yes When (UTC).
updated_by object or null yes Who changed it last.
updated_at string (date-time) or null yes When (UTC).

LeadSummary

How many leads a company has at each status, and where the open ones came from.

Field Type Required Description
entity string yes Company code.
by_status map of integer yes Leads per status (every status, zeros too).
open_by_source map of integer yes Open leads (new, working) per source.
unassigned integer yes Open leads nobody owns.
converted_last_30_days integer yes Leads converted in the last 30 days.

LeadUpdate

Field Type Required Description
status "disqualified" | "new" | "working" or null new, working or disqualified (with disqualified_reason); a disqualified lead moved back to new or working loses its reason. Converting is POST /v1/leads/{lead_id}/convert.
disqualified_reason string or null Why it is not a fit. (min length 3; max length 500)
first_name string or null "" clears. (max length 100)
last_name string or null Last name. (min length 1; max length 100)
company string or null "" clears. (max length 200)
role string or null "" clears. (max length 100)
email string or null "" clears. (max length 200)
phone string or null "" clears. (max length 50)
country string or null "" clears. (pattern ^([A-Z]{2})?$)
message string or null "" clears. (max length 5000)
source "advertising" | "email" | "event" | "hubspot" | "import" | "other" | "partner" | "phone" | "referral" | "web_form" or null Where the lead came from.
source_detail string or null "" clears. (max length 200)
owner_user_id string (uuid) | "" or null Who works on it; "" leaves it to nobody.
email_opt_out boolean or null No sales or marketing e-mails.

LinkInput

Field Type Required Description
code string yes The one-time code Google's consent popup gave the browser (Google Identity Services' code client, redirect postmessage). (min length 1; max length 2000)

MailboxStatus

Field Type Required Description
available boolean yes Whether this system offers the Google link (its OAuth client is set up). False: nothing can be linked yet.
client_id string or null yes The Google OAuth client the browser asks for consent with (the sign-in one).
scopes array of string yes What the consent asks for.
linked boolean yes Whether your Google account is linked.
provider "google" or null yes The linked provider.
account_email string or null yes The linked Google account.
mail boolean yes Google allowed reading Gmail.
calendar boolean yes Google allowed reading the calendar.
status "active" | "relink" or null yes relink: Google stopped accepting the link; link again.
linked_at string (date-time) or null yes When it was linked (UTC).
last_sync_at string (date-time) or null yes When it was last read (UTC).
last_error string or null yes Why the last reading failed, in plain words.
mail_read_to string (date-time) or null yes E-mails are read up to here (UTC).
calendar_read_to string (date-time) or null yes Meetings are read up to here (UTC).

MyRecordAccess

Field Type Required Description
record_visibility "all" | "own" | "team" yes Which sales records the person sees: all (every customer, contact, deal and activity), team (the records they or anyone in a team of theirs own) or own (the records they own). A key or an agent sees what the person behind it sees; a key the system issued sees every record.
people array of Person yes With team or own: the people whose records you see (you first); empty with all.

OpportunityInput

Field Type Required Description
entity string yes Code (or id) of the entity whose pipeline the deal is in.
title string yes What the deal is. (min length 1; max length 200)
party string or null Code (or id) of the customer; omit it for a prospect that is not a customer yet (then prospect_name is required).
prospect_name string or null The prospect's company name. (min length 1; max length 200)
prospect_country string or null The prospect's country (ISO 3166-1). (pattern ^[A-Z]{2}$)
contact_name string or null The person the deal is with. (max length 200)
contact_email string or null Their e-mail. (max length 200)
contact_phone string or null Their phone. (max length 50)
kind "expansion" | "new_business" | "renewal" new_business, expansion (more for an existing customer) or renewal. (default "new_business")
stage "lead" | "negotiation" | "proposal" | "qualified" The open stage it starts at. (default "lead")
probability integer or null Percent; omitted: the stage's default (lead (10 %), qualified (25 %), proposal (50 %), negotiation (75 %), won (100 %) or lost (0 %)). (min 0; max 100)
currency string or null Currency of the amount; omitted: the customer's currency, else the entity's functional currency. (pattern ^[A-Z]{3}$)
amount decimal string Expected net value of the deal, e.g. 15000.00. (min 0; default "0")
expected_close string (date) or null When it should close.
owner_user_id string (uuid) or null Who owns the deal (GET /v1/people); omitted: you (a person; a key or an agent: the person behind it). Someone who sees only their own or their team's records names one of those people.
source string or null Where the lead came from. (max length 100)
next_step string or null What happens next. (max length 500)
forecast_category "best_case" | "commit" | "omitted" | "pipeline" or null Where the deal counts in the forecast: commit (expected to close in its month), best_case (could close), pipeline (still being worked) or omitted (left out of the forecast); null: as the stage says (negotiation commit, proposal best case, lead and qualified pipeline).
contact_id string (uuid) or null The person at the customer the deal is with (GET /v1/contacts?party_id=); a deal with a prospect names the person in contact_name instead.
custom map of string | number | integer | boolean or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.

OpportunityOut

Field Type Required Description
id string (uuid) yes Opportunity id.
entity string yes Entity code.
number string yes OPP-000001, per entity.
title string yes What the deal is.
party_id string (uuid) or null yes The customer's id; null for a prospect.
party_code string or null yes The customer's code.
party_name string or null yes The customer's name.
prospect_name string or null yes The prospect's company name.
prospect_country string or null yes The prospect's country.
contact_name string or null yes The person the deal is with.
contact_email string or null yes Their e-mail.
contact_phone string or null yes Their phone.
contact_id string (uuid) or null The contact at the customer the deal is with; contact_name, contact_email and contact_phone are then the contact's (empty once it is erased).
kind "expansion" | "new_business" | "renewal" yes new_business, expansion or renewal.
stage "lead" | "lost" | "negotiation" | "proposal" | "qualified" | "won" yes The stage.
probability integer yes Percent.
currency string yes Currency of the amounts.
amount string yes Expected net value.
weighted_amount string yes amount x probability, rounded to the cent.
expected_close string (date) or null yes When it should close.
owner Person or null yes Who owns the deal.
source string or null yes Where the lead came from.
next_step string or null yes What happens next.
lost_reason string or null yes Why it was lost.
closed_on string (date) or null yes The day it was won or lost.
forecast_category "best_case" | "closed" | "commit" | "omitted" | "pipeline" yes Where it counts in the forecast: closed (won), omitted (lost or left out), commit, best_case or pipeline.
forecast_category_set boolean yes True when its owner chose the category; false when it follows the stage.
custom object Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.
subscription_id string (uuid) or null A renewal deal's subscription: the deal was opened when its renewal fell due, and is won or lost with the renewal (docs/erp/recurring-revenue.md).
subscription_number string or null Its number.
renewal_term_start string (date) or null The first day of the term the deal renews.
created_by object yes Who created it (actor object).
created_at string (date-time) yes When (UTC).
updated_by object or null yes Who changed it last.
updated_at string (date-time) or null yes When (UTC).

OpportunityUpdate

Field Type Required Description
title string or null New title. (min length 1; max length 200)
party string or null Code (or id) of the customer, e.g. once the prospect is set up as one.
prospect_name string or null "" clears. (max length 200)
prospect_country string or null "" clears. (pattern ^([A-Z]{2})?$)
contact_name string or null "" clears. (max length 200)
contact_email string or null "" clears. (max length 200)
contact_phone string or null "" clears. (max length 50)
kind "expansion" | "new_business" | "renewal" or null New kind.
probability integer or null Percent (an open deal only). (min 0; max 100)
currency string or null New currency. (pattern ^[A-Z]{3}$)
amount decimal string or null New expected value. (min 0)
expected_close string (date) or null New close date.
owner_user_id string (uuid) or null New owner; null clears. Someone who sees only their own or their team's records hands a deal only to one of those people.
source string or null "" clears. (max length 100)
next_step string or null "" clears. (max length 500)
forecast_category "best_case" | "commit" | "omitted" | "pipeline" or null An open deal: where it counts in the forecast, commit (expected to close in its month), best_case (could close), pipeline (still being worked) or omitted (left out of the forecast); null: as the stage says (negotiation commit, proposal best case, lead and qualified pipeline) (null clears the choice).
contact_id string (uuid) | "" or null The person at the customer the deal is with; "" clears.
custom map of string | number | integer | boolean or null or null Values of the organization's own fields (GET /v1/custom-fields) by key, e.g. {"industry": "Retail"}: text, a number, a date (2026-10-23), true or false, or one of a choice field's choices. On a change only the keys sent change; null removes a value.

PersonAccess

Field Type Required Description
person Person yes The person.
status string yes invited, active or disabled.
record_visibility "all" | "own" | "team" yes Which sales records the person sees: all (every customer, contact, deal and activity), team (the records they or anyone in a team of theirs own) or own (the records they own).
teams array of SalesTeamRef yes The sales teams they are in.

Pipeline

Field Type Required Description
entity string yes Entity code.
closed_from string (date) yes First day of the range of closed.
closed_to string (date) yes Last day of the range of closed.
open array of StageTotal yes The open opportunities by stage (in pipeline order) and currency. Amounts are summed per currency and never converted.
closed array of StageTotal yes The opportunities won and lost in the range, by stage and currency.
open_tasks integer yes Tasks of the entity not done yet.
overdue_tasks integer yes Of them, those whose due day has passed.

PipelineReport

Field Type Required Description
entity string yes Entity code.
currency string yes The report's currency (the entity's functional one).
rate_day string (date) yes The day whose ECB fixing converts the amounts.
fixing_date string (date) or null yes The fixing used; null when there is none near the day (then every other currency is in unconverted).
unconverted array of string yes Currencies left out of the converted amounts.
today string (date) yes Today where the entity is.
by_owner array of PipelineRow yes Open deals by owner, the largest first.
by_stage array of PipelineRow yes Open deals by stage, in pipeline order.
by_month array of PipelineRow yes Open deals by the month they are expected to close, the earliest first; none last.
total PipelineRow yes All open deals.
late integer yes Open deals whose expected close day has passed.

PipelineRow

Field Type Required Description
key string yes What the row groups: an owner's user id (none for no owner), a stage, or a month YYYY-MM of the expected close (none for no date).
owner Person or null The owner (rows by owner).
count integer yes How many open deals.
amount Figure yes Their expected value.
weighted Figure yes Their value weighted by probability.

QuotaInput

Field Type Required Description
entity string yes Code (or id) of the entity; quotas are in its currency.
quotas array of QuotaLine yes Each person's quota for a month. (min items 1; max items 600)

QuotaLine

Field Type Required Description
user_id string (uuid) yes The person (GET /v1/people).
month string yes YYYY-MM. (pattern `^\d{4}-(0[1-9]
amount decimal string yes What they are to win in the month; 0 takes it back. (min 0)

QuotaOut

Field Type Required Description
entity string yes Entity code.
person Person yes Whose quota.
month string yes YYYY-MM.
currency string yes The entity's functional currency.
amount string yes The quota.
updated_by object yes Who set it last (actor object).
updated_at string (date-time) yes When (UTC).

SalesActivityReport

Field Type Required Description
entity string yes Entity code.
date_from string (date) yes First day counted.
date_to string (date) yes Last day counted.
rows array of ActivityRow yes Per person (or agent), the busiest first.
total ActivityRow yes Everyone.

SalesTeamInput

Field Type Required Description
name string yes The team's name, unique. (min length 1; max length 80)
member_ids array of string (uuid) Its people (GET /v1/people); a person may be in several teams.

SalesTeamOut

Field Type Required Description
id string (uuid) yes Id of the team.
name string yes Its name.
members array of Person yes Its people, by name.
created_at string (date-time) yes When it was set up (UTC).

SalesTeamRef

Field Type Required Description
id string (uuid) yes Id of the team.
name string yes Its name.

SalesTeamUpdate

Field Type Required Description
name string yes The new name. (min length 1; max length 80)

StageInput

Field Type Required Description
stage "lead" | "lost" | "negotiation" | "proposal" | "qualified" | "won" yes The new stage: lead (10 %), qualified (25 %), proposal (50 %), negotiation (75 %), won (100 %) or lost (0 %).
probability integer or null For an open stage: percent; omitted: the stage's default. Ignored for won and lost. (min 0; max 100)
lost_reason string or null Why it was lost (for lost). (min length 3; max length 1000)
on string (date) or null For won and lost: the day it closed; omitted: today.
note string or null A note kept with the stage change. (max length 5000)

StageTotal

Field Type Required Description
stage "lead" | "lost" | "negotiation" | "proposal" | "qualified" | "won" yes The stage.
currency string yes The currency of the amounts.
count integer yes How many opportunities.
amount string yes Their expected value.
weighted_amount string yes Their value weighted by probability.

SyncOut

Field Type Required Description
emails integer yes E-mails newly logged on deals and customers.
meetings integer yes Meetings newly logged.
read_emails integer yes E-mails read from the mailbox (matched or not).
read_meetings integer yes Meetings read from the calendar (matched or not).
cases integer E-mails to or from a support queue's inbox newly put on a case. (default 0)
mailbox MailboxStatus yes The link after reading.

VisibilityInput

Field Type Required Description
record_visibility "all" | "own" | "team" yes Which sales records the person sees: all (every customer, contact, deal and activity), team (the records they or anyone in a team of theirs own) or own (the records they own).

WinRateReport

Field Type Required Description
entity string yes Entity code.
currency string yes The report's currency.
rate_day string (date) yes The day whose ECB fixing converts the amounts.
fixing_date string (date) or null yes The fixing used; null when there is none near the day (then every other currency is in unconverted).
unconverted array of string yes Currencies left out of the converted amounts.
closed_from string (date) yes First close day counted.
closed_to string (date) yes Last close day counted.
rows array of WinRateRow yes Per owner, the most won value first.
total WinRateRow yes Every deal closed in the range.

WinRateRow

Field Type Required Description
key string yes The owner's user id, none for no owner, total.
owner Person or null yes The owner.
won integer yes Deals won in the range.
lost integer yes Deals lost in the range.
won_amount Figure yes Value of the deals won.
lost_amount Figure yes Value of the deals lost.
win_rate string or null yes Won of won and lost, by count, in percent (one decimal); null when none closed. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$)
value_win_rate string or null yes Won of won and lost, by converted value, in percent; null when nothing of value closed. (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$)
average_won string or null yes The average deal won, converted.
average_days_to_win string or null yes Days from a won deal's creation to its close, on average (one decimal). (pattern ^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$)
Stabiso, the Enterprise System of Record · generated from the code and its guides · release 6f03a5e846b3