# CentralStationCRM MCP Server — 175 tools via DADL

The CentralStationCRM DADL turns CentralStationCRM's API into an MCP server that Claude, GPT or any MCP-compatible agent can consume directly. One YAML file declares all 175 tools — person, company, deal, project, protocol, cal, and more — and ToolMesh serves them at runtime. No Python boilerplate, no per-endpoint code, no separate MCP server process.

Below: the endpoint coverage matrix, a two-block ToolMesh setup, the full tool reference grouped by CentralStationCRM feature area.

**Source:** [CentralStationCRM API v1](https://centralstationcrm.com/api-basics)

**Updated:** 2026-11-01

**Tags:** crm, crud, user-management, notifications, project-management, issue-tracking, logging, automation, auth:apikey

## Which CentralStationCRM endpoints are covered?

**88%** (175 of ~199 endpoints).

**Focus:** CRM v1: people (contact details, addresses, positions, assistants, historic events), companies (contact details, addresses, avatars), deals (pipelines, stages, values, states), projects (states, target dates), tasks (badges, owners, due times), protocols (notes, calls, meetings), tags, custom fields, calendar (events, attendees, group calendars), activity stream, filter groups, webhooks, users.

**Missing:** deprecated nested protocol-attachment routes (/people|companies|deals|projects/{id}/protocols/{id}/attachments — use the flat /protocols/{id}/attachments tools), nested avatar show/destroy (covered by the account-level get_avatar/delete_avatar), bracket-nested filter[field][modifier] query syntax and the custom_fields[type_id]=value filter on people (find_records_by_custom_field pages instead), the reference_for_webhooks echo parameter, OAuth2 authorization-code flow (API key only here)

*Last reviewed: 2026-11-01*

## How do you configure the CentralStationCRM DADL?

1. Log in to your CentralStationCRM account (https://YOUR_ACCOUNT.centralstationcrm.net)
2. Open 'My settings' (Meine Einstellungen) from the user menu in the top right
3. Go to the API / API keys section and create a new API key — it is bound to YOUR user and inherits your permissions, so use a dedicated integration user if you want to limit access
4. Copy the key and store it as CREDENTIAL_CENTRALSTATIONCRM_API_KEY in .env — ToolMesh sends it as the X-apikey header
5. Verify the wiring by calling the health tool (or check_connection directly); a valid key passes, an invalid or revoked one answers 401

**Environment variable:** `CREDENTIAL_CENTRALSTATIONCRM_API_KEY`

[Authentication docs](https://centralstationcrm.com/api-basics)

*One credential is needed: CREDENTIAL_CENTRALSTATIONCRM_API_KEY. CentralStationCRM has no OAuth scopes for API keys — a key carries the full rights of the user who created it, so create it under a dedicated integration user if you want to restrict what the agent can reach. The url above works for any account; the per-account host https://YOUR_ACCOUNT.centralstationcrm.net/api is equivalent and can be substituted. Rate limit is 50 requests per 10 seconds (429 + Retry-After). A free Starter account for development is available at https://centralstationcrm.com/signup/new?plan=Starter; developer questions go to dev-support@42he.com.
*

## How do you install the CentralStationCRM MCP server with ToolMesh?

Add to your `backends.yaml`:

```yaml
- name: centralstationcrm
  transport: rest
  dadl: centralstationcrm.dadl
  url: "https://api.centralstationcrm.net/api"

```

Set the credential:

```
CREDENTIAL_CENTRALSTATIONCRM_API_KEY=your-token-here
```

## What 175 tools does the CentralStationCRM DADL expose?

- **GET** `check_connection` — Verify that the configured API key is valid. Returns 200/204 for a working key and 401 otherwise. Cheapest possible health check — call it once before a batch of writes instead of discovering a bad key mid-workflow.

- **GET** `get_current_user` — Details of the user the API key belongs to: id, first, last, name (login e-mail), accounts (all accounts this user can reach), current_account (the account subdomain this key operates on), timezone and the three salutation variants. Use it to find out WHICH account and user an agent is acting as.

- **GET** `get_user_maildrop` — The current user's maildrop address for people and companies. Forwarding or BCC-ing an e-mail to this address files it into the CRM as a protocol on the matching record. Returns {email_address, entity_type, entity_id, ...}.

- **GET** `list_users` — All users of the account (id, first, name, login e-mail). Needed to resolve the user_id required by tasks, deals, calendar events and the responsible-user field on people and companies. Pass active=true to skip deactivated users.

- **GET** `get_user` — Retrieve one user of the account by numeric id.
- **GET** `search_users` — Find users by first name (`first`) and/or last name (`name`). Returns the same objects as list_users. Use it to turn a human name from a prompt into the user_id that tasks and deals require.

- **GET** `search_records` — Global full-text search across the account — the same box as the CRM's own search field. Pass `term` and receive matching records of mixed types (people, companies, deals, projects). Use it when you do not yet know which object type you are looking for; use search_people / search_companies / search_deals when you do, because those return typed, unwrapped objects.

- **GET** `list_people` — List people (contacts) of the account. Returns unwrapped person objects: id, first_name, name (last name), salutation, title, gender_identity, country_code, background, user_id (responsible user), group_id, created_at, updated_at. Embed children with includes: positions, companies, deals, projects, tags, tasks, pending_tasks, upcoming_cal_events, tels, emails, ims, sms, homepages, addrs, addrs_from_company, custom_fields, historic_events, assis, connections, user — or "all". Extra computed fields via methods: responsible_user_natural_name, salutation_official, salutation_formal, salutation_informal, backlink, stream_updated_at, absolute_url, absolute_avatar_url — or "all". For an incremental sync use stream_updated_at_after. To find people by a custom field value call find_records_by_custom_field — the API's custom_fields[type_id]=value filter is bracket-keyed and cannot be sent as a flat parameter (LIVE: a flat custom_fields value answers HTTP 500).

- **GET** `get_person` — Retrieve one person by numeric id. Combine with includes ("all" pulls positions, companies, tels, emails, addrs, tags, custom_fields, historic_events, assis, tasks, upcoming_cal_events) and methods ("all" adds the salutation variants, absolute_url, absolute_avatar_url) to build a complete contact view in ONE call instead of a dozen sub-resource requests.

- **POST** `create_person` — Create a person. Wrap everything under the `person` key. Writable fields: name (last name, REQUIRED, max 80), first_name, salutation (a custom salutation; one is generated automatically otherwise), title (e.g. "Dr."), gender_identity (male|female|diverse|family|unknown), country_code (ISO-639-1 language code of the person, e.g. "de"), background (free text notes such as hobbies), user_id (responsible user), group_id. Child records can be embedded in the same request via tels_attributes, emails_attributes, homepages_attributes, ims_attributes, sms_attributes, addrs_attributes, assis_attributes, positions_attributes, historic_events_attributes, tags_attributes, custom_fields_attributes and avatar_attributes — each an array of the corresponding child objects. Example: api.create_person({ person: { name: "Mäh", first_name: "Jolly", emails_attributes: [{ atype: "office", name: "jolly@example.com" }] } })

- **PUT** `update_person` — Update a person. Send only the fields you want to change inside the `person` object — same schema as create_person (name, first_name, salutation, title, gender_identity, country_code, background, user_id, group_id, plus the *_attributes children). `name` is the last name and must stay non-empty.

- **DELETE** `delete_person` — Permanently delete a person including their contact details, addresses, positions and notes. Irreversible — confirm with the user first, and prefer tagging or a background note when the record only needs to be retired.

- **GET** `search_people` — Find people by exact-ish attributes: name (LAST name), first_name, email or phone. email and phone are mutually exclusive — pass one or the other. This is the correct way to answer "do we already know this address?" before creating a duplicate contact. Supports the same includes/methods as list_people.

- **GET** `count_people` — Total number of people, as {"total_entries": N}. Honors tag_id / tag_name. Call this before paging a large account — it costs one request instead of ten.

- **GET** `get_person_stats` — Key aggregate figures for people (total count and related metrics), optionally narrowed by tag_id or tag_name.
- **POST** `merge_person` — Merge one or more duplicate people INTO the person given by {id}. The records listed in looser_ids are consumed and disappear; their contact details, notes and relations move to the surviving person. Irreversible — verify both sides with get_person before calling.

- **GET** `list_person_external_emails` — E-mails filed into the CRM (via maildrop or an e-mail integration) that belong to this person. Read-only. Use includes=comments to pull discussion threads.
- **GET** `count_person_external_emails` — Number of filed e-mails for this person, as {"total_entries": N}.
- **GET** `list_companies` — List companies. Returns unwrapped company objects: id, name, background, user_id (responsible user), group_id, created_at, updated_at. includes: positions, deals, projects, tags, tasks, pending_tasks, cal_events, upcoming_cal_events, tels, emails, ims, sms, homepages, addrs, connections, custom_fields — or "all". methods: responsible_user_natural_name, related_tasks, stream_updated_at, absolute_url — or "all". Use stream_updated_at_after for incremental syncs. LIVE: includes=custom_fields is the ONLY way to read a company's custom fields — the API has no /companies/{id}/custom_fields route — and it returns them for the whole page in one request, which makes this the right tool for auditing a company-level field across the account. Each entry carries id (the value id), custom_fields_type_id, custom_fields_type_name (the label) and name (the value). To look up companies by a value use find_records_by_custom_field.

- **GET** `get_company` — Retrieve one company by numeric id. Use includes=all / methods=all for a complete company view (people via positions, deals, addresses, contact details, tags, custom fields) in a single request.
- **POST** `create_company` — Create a company. Wrap everything under the `company` key. Writable fields: name (REQUIRED, max 120), background (free text, e.g. company history), user_id (responsible user), group_id. Embeddable children in the same request: custom_fields_attributes, avatar_attributes, tels_attributes, emails_attributes, homepages_attributes, ims_attributes, sms_attributes, addrs_attributes, tags_attributes, connections_attributes. LIVE: custom_fields_attributes is the ONLY way to write a custom field on a company — the API has no /companies/{id}/custom_fields route (custom-field CRUD exists for people only). Verified working with custom_fields_attributes: [{ custom_fields_type_id: <id from list_custom_field_types>, name: "<value>" }]; read it back with get_company({ includes: "custom_fields" }). Only one value per field type and company — to change it later use update_company or set_custom_field_value. Example: api.create_company({ company: { name: "Example GmbH", addrs_attributes: [{ atype: "work_hq", street: "Musterstr. 1", zip: "50667", city: "Köln", country_code: "DE" }], custom_fields_attributes: [{ custom_fields_type_id: 1234, name: "C-1001" }] } })

- **PUT** `update_company` — Update a company. Send the changed fields inside the `company` object — same schema as create_company (name, background, user_id, group_id, plus the *_attributes children, which are also the only route to a company's custom fields). LIVE for custom fields: change a value with custom_fields_attributes: [{ id: <value id from includes=custom_fields>, name: "<new value>" }], remove it with [{ id, _destroy: true }] or an empty name, add one with [{ custom_fields_type_id, name }] — adding a second value for a type the company already has answers 422. set_custom_field_value does this lookup for you. LIVE: the response body is empty — re-read with get_company.

- **DELETE** `delete_company` — Permanently delete a company with its addresses, contact details and notes. Irreversible. LIVE: the people who worked there survive, but their position rows are NOT cleaned up — such a person still reports positions_count 1 and includes=companies still resolves the deleted company's name, while get_company on that id answers 404. Delete the stale positions yourself with delete_person_position if the dangling link matters.

- **GET** `search_companies` — Find companies by name. The duplicate check to run before create_company. Supports the same includes/methods as list_companies.
- **GET** `count_companies` — Total number of companies as {"total_entries": N}, optionally narrowed by tag_id or tag_name.
- **GET** `get_company_stats` — Key aggregate figures for companies, optionally narrowed by tag_id or tag_name.
- **POST** `merge_company` — Merge duplicate companies INTO the company given by {id}. Records in looser_ids are consumed and removed; their people, deals, notes and contact details move to the survivor. Irreversible.

- **GET** `list_company_external_emails` — E-mails filed into the CRM that belong to this company. Read-only.
- **GET** `count_company_external_emails` — Number of filed e-mails for this company, as {"total_entries": N}.
- **GET** `list_deal_types` — List the account's deal pipelines ("deal types") WITH their stages. Each entry has id, name, singular_item_name / plural_item_name (the account's own wording, e.g. "Offer"), default_view, position, the capability flags has_monetary_value, has_probability, has_target_date, has_muted_until_date, and deal_type_stages (the ordered stages). ALWAYS call this before creating or moving a deal — it supplies the deal_type_id and deal_type_stage_id values and tells you which optional deal fields the pipeline actually uses.

- **GET** `get_deal_type` — Retrieve one pipeline with its stages by id — same shape as one entry of list_deal_types.
- **GET** `list_deals` — List deals (opportunities). Returns unwrapped deal objects: id, name, company_id, user_id, group_id, value / value_type / value_sum / value_count (decimal STRINGS), currency (EUR|CHF|USD), current_state (LIVE: open0|open25|open50| open75|won|lost — no underscore, unlike the API docs), deal_type_id (pipeline), deal_type_stage_id (stage, may be null), probability (0-100), target_date, finished_at, previous_deal_id, description (the free-text background), created_at, updated_at. includes: tags, people, company, task_lists, tasks, pending_tasks, cal_events, upcoming_cal_events, custom_fields — or "all". methods: responsible_user_natural_name, people_ids, absolute_url, stream_updated_at. Sort with order: value-desc for the biggest deals, state-asc, date-desc, etc.

- **GET** `get_deal` — Retrieve one deal by id. includes=all also returns the linked company, the assigned people, tasks, calendar events, tags and custom fields.
- **POST** `create_deal` — Create a deal. Wrap everything under the `deal` key. Call list_deal_types first. Writable fields: name (REQUIRED), deal_type_id (pipeline), deal_type_stage_id (stage inside it), company_id, user_id (responsible), group_id, value (decimal STRING, e.g. "1000.00"), value_type (total|monthly|hourly|daily), value_count (decimal string, only relevant when value_type is not total), currency (EUR|CHF|USD, default EUR), probability (integer 0-100), target_date (YYYY-MM-DD, when the deal should be won), finished_at, current_state (LIVE format open0|open25|open50|open75|won|lost — the API docs print open_0|open_25|..., but records come back without the underscore), background (free text; LIVE: reads return this as `description`), person_ids_set (comma-separated person ids to attach, e.g. "42,4711"). Also accepts tags_attributes and custom_fields_attributes. Whether value, probability and target_date are used at all depends on the pipeline's capability flags.

- **PUT** `update_deal` — Update a deal — the tool you use to MOVE a deal through its pipeline (set deal_type_stage_id) or to close it (current_state won|lost, optionally finished_at). Send only the changed fields inside the `deal` object; the schema is identical to create_deal (name, deal_type_id, deal_type_stage_id, company_id, user_id, group_id, value, value_type, value_count, currency, probability, target_date, finished_at, current_state, background, person_ids_set). LIVE: the response body of a PUT is EMPTY — re-read with get_deal to see the result, and use the underscore-free current_state format (open75, not open_75).

- **DELETE** `delete_deal` — Permanently delete a deal and its notes. Irreversible — to record a loss, prefer update_deal with current_state 'lost' so the history stays intact.
- **GET** `search_deals` — Find deals by name. Supports the same includes/methods as list_deals.
- **GET** `count_deals` — Total number of deals as a total_entries object, optionally narrowed by tag_id or tag_name.
- **GET** `get_deal_stats` — Aggregate deal figures — total count plus the summed deal value (the `sum` field). The cheapest way to answer "what is our pipeline worth?" without pulling every deal. Optionally narrowed by tag_id or tag_name.

- **GET** `get_deal_maildrop` — The maildrop e-mail address of this deal — forwarding or BCC-ing mail there files it onto the deal as a protocol.
- **GET** `list_deal_external_emails` — E-mails filed into the CRM that belong to this deal. Read-only.
- **GET** `count_deal_external_emails` — Number of filed e-mails for this deal, as a total_entries object.
- **GET** `list_projects` — List projects. A project is the delivery-side counterpart of a deal: same pipeline mechanics, no monetary fields. Returns id, name, company_id, deal_id (the deal it came from), user_id, group_id, current_state (LIVE format open0|open25|open50|open75|won|lost — the API docs print open_0|open_25|...), target_date, finished_at, background. includes: tags, people, company, task_lists, tasks, pending_tasks, cal_events, upcoming_cal_events, custom_fields — or "all".

- **GET** `get_project` — Retrieve one project by id. includes=all returns company, people, tasks, calendar events, tags and custom fields.
- **POST** `create_project` — Create a project. Wrap everything under the `project` key. Writable fields: name (REQUIRED, max 120), target_date (REQUIRED, YYYY-MM-DD — when the project should be finished; may be null), company_id, deal_id (the deal this project originates from), user_id (responsible), group_id, current_state (LIVE format open0|open25|open50|open75|won|lost), finished_at, background. Also accepts tags_attributes and custom_fields_attributes.

- **PUT** `update_project` — Update a project. Send the changed fields inside the `project` object — same schema as create_project (name, target_date, company_id, deal_id, user_id, group_id, current_state, finished_at, background).
- **DELETE** `delete_project` — Permanently delete a project and its notes. Irreversible — prefer update_project with current_state 'won' or 'lost' to close it.
- **GET** `search_projects` — Find projects by name. Supports the same includes/methods as list_projects.
- **GET** `count_projects` — Total number of projects as a total_entries object, optionally narrowed by tag_id or tag_name.
- **GET** `get_project_stats` — Key aggregate figures for projects, optionally narrowed by tag_id or tag_name.
- **GET** `get_project_maildrop` — The maildrop e-mail address of this project — mail sent there is filed onto the project as a protocol.
- **GET** `list_project_external_emails` — E-mails filed into the CRM that belong to this project. Read-only.
- **GET** `count_project_external_emails` — Number of filed e-mails for this project, as a total_entries object.
- **GET** `list_tasks` — List tasks (to-dos) of the account. Returns id, name, user_id (owner), attachable_type / attachable_id (the linked Person, Company, Deal or Project), task_list_id, badge, precise_time, finished, fuzzy. Narrow with state=open|closed, before / after (YYYY-MM-DD) and user_id — e.g. "what is due for user 7 this week?" is state=open plus before/after. includes: user, comments. methods: responsible_user_natural_name.

- **GET** `get_task` — Retrieve one task by id, optionally with its user and comments.
- **POST** `create_task` — Create a task. Wrap everything under the `task` key. Writable fields: user_id (REQUIRED — the responsible user; resolve it with search_users), name (REQUIRED — the to-do text), attachable_type (Person|Company|Deal|Project) and attachable_id to link the task to a record, task_list_id (a list inside that record), badge (task|call|email|visit| preparation|important, default task), precise_time (ISO 8601 due date/time), fuzzy (true when only the day matters, not the clock time), finished (true creates an already-closed task). Example: api.create_task({ task: { user_id: 7, name: "Call back about the offer", attachable_type: "Person", attachable_id: 42, badge: "call", precise_time: "2026-07-28T09:00:00Z", fuzzy: true } })

- **PUT** `update_task` — Update a task — including closing it with finished: true. Send the changed fields inside the `task` object; same schema as create_task (user_id, name, attachable_type, attachable_id, task_list_id, badge, precise_time, fuzzy, finished).
- **DELETE** `delete_task` — Permanently delete a task. Irreversible — to mark it done use update_task with finished: true, which keeps it in the history.
- **GET** `list_protocols` — List protocols — the notes and logged interactions of the account. Narrow to one record with person_id, company_id, deal_id or project_id (this is how you read "the history of contact X"). Protocols are polymorphic; after unwrapping, `kind` tells you the subtype: protocol_object_note (a manual note), protocol_email, protocol_external, protocol_user_note or protocol_attachment. Fields include id, name, content, badge, confidential, format, user_id, created_at. includes: comments. methods: responsible_user_natural_name, absolute_url, conversion_step_names.

- **GET** `get_protocol` — Retrieve one protocol by id. The unwrapped object carries `kind` with its subtype (protocol_object_note, protocol_email, protocol_external, protocol_user_note, protocol_attachment).
- **POST** `create_protocol` — Create a protocol — the standard way to log a call, meeting or note on one or more records. Wrap the note under the `protocol` key. Writable fields: content (REQUIRED — the note text), badge (note|call|email| meeting|other for a ProtocolObjectNote; email for a ProtocolEmail), name (a short headline, max 100), type (ProtocolObjectNote|ProtocolExternal| ProtocolEmail, default ProtocolObjectNote), format (plaintext|markdown — plaintext unless you set markdown), confidential (true limits visibility to power users), person_ids / company_ids / deal_ids / project_ids (ARRAYS of ids; one protocol can hang on several records at once), conversion_step_names. Optionally attach a file in the SAME call by also passing the `attachment` object with filename, content_type and base64 `data`. Example: api.create_protocol({ protocol: { content: "Discussed the renewal.", badge: "call", person_ids: [42], deal_ids: [7] } })

- **PUT** `update_protocol` — Update a protocol. Send the changed fields inside the `protocol` object — same schema as create_protocol (content, badge, name, type, format, confidential, person_ids, company_ids, deal_ids, project_ids). An `attachment` object may be added as well.
- **DELETE** `delete_protocol` — Permanently delete a protocol and its attachments. Irreversible — this removes documented customer history, so confirm before calling.
- **GET** `count_protocols` — Total number of protocols in the account, as a total_entries object.
- **GET** `list_protocol_attachments` — Files attached to one protocol — metadata only (id as UUID, filename, content_type, size, attachment_category_id, user_id); the inlined base64 `data` is stripped, fetch it with get_protocol_attachment. This is the preferred route: the nested /people|companies|deals|projects/{id}/protocols/{id}/attachments variants are deprecated and not modeled here.

- **POST** `create_protocol_attachment` — Attach a file to an existing protocol. Wrap the upload under the `attachment` key: filename (REQUIRED), content_type (REQUIRED, e.g. "application/pdf"), data (base64-encoded file bytes), attachment_category_id or attachment_category_name (the category is created if the name is unknown). A 507 response means the account's storage quota is exhausted.

- **GET** `get_protocol_attachment` — Retrieve one attachment of a protocol INCLUDING its bytes in the base64 `data` field — the response can be several hundred KB.
- **DELETE** `delete_protocol_attachment` — Permanently delete one attachment of a protocol. Irreversible.
- **GET** `list_attachments` — All attachments of the account across every record. Returns METADATA only — id (a UUID), filename, content_type, size in bytes, attachable_type / attachable_id, user_id. LIVE: the API inlines the whole file as base64 in every list row (a 413 KB PDF becomes 573 KB of base64); this tool strips that field. Call get_attachment when you actually need the bytes. Use list_protocol_attachments when you already know the protocol, or search_attachments to look one up by filename.

- **GET** `get_attachment` — Retrieve one attachment by id (a UUID) INCLUDING its bytes: the `data` field carries the whole file base64-encoded, so the response can be several hundred KB — only call this when you really need the content, and never in a loop. The metadata (filename, content_type, size, attachable_type/_id) comes alongside.

- **POST** `create_attachment` — Upload a file. Wrap it under the `attachment` key: filename (REQUIRED), content_type (REQUIRED), data (base64-encoded bytes), attachable_type (Protocol|Comment|ExternalEmail) plus attachable_id to bind it to a record, and attachment_category_id or attachment_category_name. Prefer create_protocol (with its `attachment` parameter) or create_protocol_attachment when the file belongs to a note — those keep the file and its context in one call. 507 means the storage quota is full.

- **DELETE** `delete_attachment` — Permanently delete an attachment. Irreversible.
- **GET** `search_attachments` — Find attachments whose filename contains or matches the given input — the way to locate a document when you only remember its name. Returns metadata only (the base64 `data` field is stripped); fetch the bytes with get_attachment.
- **GET** `count_attachments` — Total number of attachments AND their combined file size (total_entries plus total_size in bytes) — use it to check storage consumption before uploading.
- **GET** `list_external_email_attachments` — Files attached to a filed e-mail (external_email).
- **POST** `create_external_email_attachment` — Attach a file to a filed e-mail. Wrap it under the `attachment` key: { filename, content_type, data (base64) }.
- **GET** `get_external_email_attachment` — Retrieve the metadata of one attachment on a filed e-mail.
- **DELETE** `delete_external_email_attachment` — Permanently delete one attachment of a filed e-mail. Irreversible.
- **GET** `list_external_emails` — All e-mails filed into the CRM account (through a maildrop address or an e-mail integration). Read-only: the API cannot send or create them. Fields include subject, content_html, confidential and the record links. For one record use list_person_external_emails / list_company_external_emails / list_deal_external_emails / list_project_external_emails.

- **GET** `get_external_email` — Retrieve one filed e-mail by id, including subject and content_html.
- **GET** `count_external_emails` — Total number of filed e-mails in the account, as a total_entries object.
- **GET** `list_comments` — Comments of the account. A comment is a threaded reply on an Attachment, CalEvent, Protocol, ExternalEmail or Task — the internal discussion layer. Returns id, name (the comment text), user_id, attachable_type, attachable_id.
- **GET** `get_comment` — Retrieve one comment by id.
- **POST** `create_comment` — Add a comment to a record. Wrap it under the `comment` key. Writable fields: name (REQUIRED — the comment text), attachable_type (Attachment|CalEvent|Protocol|ExternalEmail|Task), attachable_id, user_id (the author; defaults to the API key's user). Use this to reply on an existing note instead of creating another protocol.

- **PUT** `update_comment` — Update a comment. Send the changed fields inside the `comment` object — same schema as create_comment (name, attachable_type, attachable_id, user_id).
- **DELETE** `delete_comment` — Permanently delete a comment. Irreversible.
- **GET** `list_tags` — List tag assignments of the account. Each tag is an assignment of a name to ONE record, so the same word appears once per tagged record. Narrow with person_id, company_id, deal_id or project_id to see the tags of a single record. Returns id, name, attachable_type, attachable_id, api_input.

- **GET** `list_tag_names` — The distinct tag NAMES used in the account, as a plain array of strings. Call this before tagging so you reuse an existing label instead of inventing a near-duplicate.
- **POST** `create_tag` — Tag any record. Wrap it under the `tag` key. Writable fields: name (REQUIRED, max 60), attachable_type (REQUIRED — Person|Company|Deal|Project), attachable_id (REQUIRED — the record's id), api_input (marks the tag as machine-created). Example: api.create_tag({ tag: { name: "top-customer", attachable_type: "Person", attachable_id: 42 } })

- **GET** `list_person_tags` — Tags assigned to one person.
- **POST** `create_person_tag` — Tag a person. Wrap it under the `tag` key — only `name` is needed here, attachable_type and attachable_id are set automatically from the route.
- **GET** `get_person_tag` — Retrieve one tag assignment of a person.
- **PUT** `update_person_tag` — Rename a person's tag assignment. Send the new value inside the `tag` object ({ name }).
- **DELETE** `delete_person_tag` — Remove a tag from a person. The tag name survives on other records.
- **GET** `list_person_contact_details` — All contact details of a person in ONE polymorphic collection. After unwrapping, `kind` names the type: tel (phone), email, homepage (website), im (messenger) or sm (social profile). Each entry has id, atype (the sub-type such as office or mobile), name (the actual number/address/URL) and primary ordering info. Postal addresses are NOT here — use list_person_addrs.

- **POST** `create_person_contact_detail` — Add ONE contact detail to a person. The WRAPPER KEY picks the type — pass exactly one of tel, email, homepage, im or sm; each object needs { atype, name } (attachable_type/_id are filled in from the route). atype values — tel: office, office_hq, mobile, fax, private, voip, skype, other. email: office, office_hq, private, invoice, other (plus optional doi flag for double-opt-in). homepage: office, private, other. im: aim, ghangouts, icq, jabber, msn, signal, skype, telegram, threema, whatsapp, wechat, yahootalk, other. sm: the social network name. `name` always carries the value itself (the number, the address, the URL). Example: api.create_person_contact_detail({ person_id: 42, email: { atype: "office", name: "jolly@example.com" } })

- **GET** `get_person_contact_detail` — Retrieve one contact detail of a person; `kind` in the result names its type.
- **PUT** `update_person_contact_detail` — Update one contact detail of a person. Wrap the changed fields in the key matching its type (tel, email, homepage, im or sm) — same schema as create_person_contact_detail.
- **DELETE** `delete_person_contact_detail` — Delete one contact detail (phone, e-mail, website, messenger or social profile) of a person. Irreversible.
- **GET** `list_company_contact_details` — All contact details of a company — same polymorphic collection as for people (`kind` is tel, email, homepage, im or sm).
- **POST** `create_company_contact_detail` — Add ONE contact detail to a company. Pass exactly one of tel, email, homepage, im or sm, each { atype, name } — see create_person_contact_detail for the atype values.
- **GET** `get_company_contact_detail` — Retrieve one contact detail of a company; `kind` names its type.
- **PUT** `update_company_contact_detail` — Update one contact detail of a company. Wrap the changed fields in the key matching its type (tel, email, homepage, im or sm).
- **DELETE** `delete_company_contact_detail` — Delete one contact detail of a company. Irreversible.
- **GET** `search_contact_details` — Reverse lookup: find the PEOPLE or COMPANIES that own a given contact detail. `name` (REQUIRED) is the search term — an e-mail address, phone number or URL — and `type` selects the record class (people, the default, or companies). Returns person/company objects, so `kind` is person or company. This is the tool for "who is calling from +49221123456?" and for de-duplication before an import.

- **GET** `list_person_addrs` — Postal addresses of a person: street, zip, city, state_code, country_code, atype and the `primary` ordering flag.
- **POST** `create_person_addr` — Add a postal address to a person. Wrap it under the `addr` key. Writable fields: city (REQUIRED unless country_code is given), street (including house number, c/o or building), zip, state_code (e.g. NW, BY — must match the country), country_code (e.g. DE, AT), atype (work_hq|work|invoice|delivery| private|other), primary (true puts it first), api_input.

- **GET** `get_person_addr` — Retrieve one postal address of a person.
- **PUT** `update_person_addr` — Update a person's postal address. Send the changed fields inside the `addr` object — same schema as create_person_addr (street, zip, city, state_code, country_code, atype, primary).
- **DELETE** `delete_person_addr` — Delete a postal address of a person. Irreversible.
- **GET** `list_company_addrs` — Postal addresses of a company (headquarters, invoice and delivery addresses).
- **POST** `create_company_addr` — Add a postal address to a company. Wrap it under the `addr` key: { street, zip, city, state_code, country_code, atype (work_hq|work|invoice|delivery|private|other), primary }. city is required unless country_code is given.
- **GET** `get_company_addr` — Retrieve one postal address of a company.
- **PUT** `update_company_addr` — Update a company's postal address. Send the changed fields inside the `addr` object — same schema as create_company_addr.
- **DELETE** `delete_company_addr` — Delete a postal address of a company. Irreversible.
- **GET** `list_person_positions` — The positions of a person — the links between a person and the companies they work for, carrying job title (name), department, company_name, primary_function and former. This is how person and company are connected; a person can hold several positions. includes: person, company.

- **POST** `create_person_position` — Link a person to a company. Wrap it under the `position` key. Writable fields: company_id (REQUIRED — the employer; person_id comes from the route), name (the job title, e.g. "Head of Sales"), department, company_name (free-text employer when no company record exists), primary_function (true marks the main employer — only one position may be primary), former (true for past employments).

- **GET** `get_person_position` — Retrieve one position of a person, optionally with the person and company embedded.
- **PUT** `update_person_position` — Update a position — e.g. mark it `former: true` when someone leaves, or change the job title. Send the changed fields inside the `position` object; same schema as create_person_position.
- **DELETE** `delete_person_position` — Remove the link between a person and a company. Irreversible — prefer update_person_position with former: true to preserve the employment history.
- **GET** `list_person_assis` — The assistants ("assis") of a person — name, email, tel and the primary flag. Useful for gatekeeper contacts who are not full CRM records themselves.
- **POST** `create_person_assi` — Add an assistant to a person. Wrap it under the `assi` key: name (REQUIRED), email, tel, primary (true marks the main assistant).
- **GET** `get_person_assi` — Retrieve one assistant of a person.
- **PUT** `update_person_assi` — Update an assistant. Send the changed fields inside the `assi` object — same schema as create_person_assi (name, email, tel, primary).
- **DELETE** `delete_person_assi` — Delete an assistant of a person. Irreversible.
- **GET** `list_historic_events` — All historic events of the account — the dated milestones attached to people: birthday, anniversary, first_met, renewal or other. Narrow the range with date_from AND date_to (both are required as soon as one is given) to answer "whose birthday is next week?". Returns atype, name (the date), desc.

- **GET** `list_person_historic_events` — The historic events (birthday, anniversary, first_met, renewal, other) of one person.
- **POST** `create_person_historic_event` — Add a dated milestone to a person. Wrap it under the `historic_event` key. Writable fields: atype (REQUIRED — birthday|anniversary|first_met|renewal|other), name (REQUIRED — the DATE of the event, YYYY-MM-DD), desc (an extra description, used mainly with atype "other").

- **GET** `get_person_historic_event` — Retrieve one historic event of a person.
- **PUT** `update_person_historic_event` — Update a historic event. Send the changed fields inside the `historic_event` object — same schema as create_person_historic_event (atype, name, desc).
- **DELETE** `delete_person_historic_event` — Delete a historic event of a person. Irreversible.
- **GET** `list_custom_field_types` — The account's custom field DEFINITIONS: id, name, category (Person|Company|Deal| Project), ftype (string|select|decimal|date|url), position and, for selects, the allowed options. Call this first — a custom field value can only be written with the custom_fields_type_id from here, and the ftype tells you the expected format. The VALUES live on the records: people have their own value routes below; for companies, deals and projects read them with includes=custom_fields and write them as custom_fields_attributes on update_company / update_deal / update_project. get_custom_field_values, set_custom_field_value and find_records_by_custom_field wrap both cases.

- **GET** `get_custom_field_type` — Retrieve one custom field definition by id.
- **POST** `create_custom_field_type` — Define a new custom field for the whole account — a schema change that affects every user, so confirm before calling. Wrap it under the `custom_fields_type` key. Writable fields: name (REQUIRED), category (REQUIRED — Person|Company|Deal| Project), ftype (REQUIRED — string|select|decimal|date|url), position (REQUIRED — sort order), options (REQUIRED for ftype select — the array of allowed values).

- **PUT** `update_custom_field_type` — Change a custom field definition (name, category, ftype, position, options). Account-wide schema change — narrowing the options of a select can orphan existing values.
- **DELETE** `delete_custom_field_type` — Delete a custom field definition. Irreversible and account-wide: every value stored in that field is lost.
- **GET** `list_person_custom_fields` — The custom field VALUES of one person: id, custom_fields_type_id, name (the value). Use includes=custom_fields_type to get the definition (label, ftype) in the same call instead of joining against list_custom_field_types.
- **POST** `create_person_custom_field` — Set a custom field value on a person. Wrap it under the `custom_field` key. Writable fields: custom_fields_type_id (REQUIRED — from list_custom_field_types), name (REQUIRED — the VALUE, formatted according to the type's ftype: plain text, one of the select options, a decimal, a YYYY-MM-DD date or a URL), api_input. attachable_type / attachable_id are set from the route.

- **GET** `get_person_custom_field` — Retrieve one custom field value of a person.
- **PUT** `update_person_custom_field` — Change a custom field value of a person. Send the new value inside the `custom_field` object — same schema as create_person_custom_field (custom_fields_type_id, name, api_input).
- **DELETE** `delete_person_custom_field` — Delete a custom field value of a person (the definition stays). Irreversible.
- **GET** `list_cal_events` — Calendar events of the account: id, name, location, starts_at, ends_at, all_day, status, description, user_id, group_calendar_id and the attachable_type / attachable_id link to a Person, Company, Deal or Project. Narrow with state=upcoming|old or the before / after date bounds (YYYY-MM-DD). includes: user, group_calendar, people, users, cal_event_attendees, comments.

- **GET** `get_cal_event` — Retrieve one calendar event by id, optionally with its attendees and linked record.
- **POST** `create_cal_event` — Create a calendar event. Wrap it under the `cal_event` key. Writable fields: name (REQUIRED), starts_at (REQUIRED, ISO 8601), ends_at (REQUIRED), user_id (REQUIRED — the owning user), group_calendar_id (REQUIRED — pick one with list_group_calendars), status (REQUIRED — new|confirmed|tentative| cancelled), location (free text, address or room), description (agenda details), all_day (true for full-day events), email_invitations (true makes the CRM send invitation mails to all attendees — a real outbound e-mail, so ask first), attachable_type (Person|Company|Deal|Project) and attachable_id to link the event to a record. Add participants afterwards with create_cal_event_attendee.

- **PUT** `update_cal_event` — Update a calendar event (reschedule, cancel via status, change location). Send the changed fields inside the `cal_event` object — same schema as create_cal_event. Setting email_invitations true triggers real invitation mails.
- **DELETE** `delete_cal_event` — Permanently delete a calendar event and its attendees. Irreversible — prefer update_cal_event with status 'cancelled' when the appointment should stay visible.
- **GET** `list_cal_event_attendees` — Participants of a calendar event: person_id or user_id, email and status (new, invited, no_invite, accepted, rejected, perhaps, canceled). includes: user, person, cal_event.
- **POST** `create_cal_event_attendee` — Add a participant to a calendar event. Wrap it under the `cal_event_attendee` key. Writable fields: person_id OR user_id (one of them must reference a real record — person_id for a CRM contact, user_id for a colleague), status (REQUIRED — new|invited|no_invite|accepted|rejected|perhaps|canceled), email (the address the invitation goes to if the event has email_invitations enabled). Use status "no_invite" to add someone without sending mail.

- **GET** `get_cal_event_attendee` — Retrieve one attendee of a calendar event.
- **PUT** `update_cal_event_attendee` — Update an attendee — typically to record their response via status (accepted, rejected, perhaps, canceled). Send the changed fields inside the `cal_event_attendee` object.
- **DELETE** `delete_cal_event_attendee` — Remove a participant from a calendar event. Irreversible.
- **GET** `list_group_calendars` — The shared calendars of the account (id, name, color_type, read_only). Every calendar event needs a group_calendar_id, so call this before create_cal_event. includes: cal_events.
- **GET** `get_group_calendar` — Retrieve one shared calendar by id, optionally with its events.
- **POST** `create_group_calendar` — Create a shared calendar visible to the whole account. Wrap it under the `group_calendar` key. Writable fields: name (REQUIRED), color_type (REQUIRED — one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve), read_only (REQUIRED — some calendars are created with the account and cannot be renamed or removed).

- **PUT** `update_group_calendar` — Rename or recolor a shared calendar. Send the changed fields inside the `group_calendar` object (name, color_type, read_only).
- **DELETE** `delete_group_calendar` — Delete a shared calendar. Irreversible and account-wide — its events go with it. Read-only system calendars cannot be removed.
- **GET** `list_avatars` — All avatars (profile pictures) of the account with their owning record — metadata only (filename, content_type, size, attachable_type/_id); the base64 `data` field is stripped here and returned by get_avatar.
- **GET** `get_avatar` — Retrieve one avatar by id, including the image bytes in the base64 `data` field alongside filename, content_type and the owning record.
- **DELETE** `delete_avatar` — Delete an avatar. Irreversible; the record keeps its default placeholder image.
- **GET** `count_avatars` — Number of avatars and their combined file size (total_entries plus total_size in bytes).
- **GET** `list_person_avatars` — The avatar(s) of one person.
- **POST** `create_person_avatar` — Upload a profile picture for a person. Wrap it under the `avatar` key: filename (REQUIRED), content_type (REQUIRED, e.g. image/jpg), data (base64-encoded image bytes). 507 means the storage quota is exhausted.
- **GET** `list_company_avatars` — The avatar(s) / logo(s) of one company.
- **POST** `create_company_avatar` — Upload a logo for a company. Wrap it under the `avatar` key: { filename, content_type, data (base64) }.
- **GET** `list_activities` — The activity stream — the read-only audit log of who created, changed or deleted what. Each entry has verb (create, update, ...), attachable_type / attachable_id (the touched record), user_id, name, occurred_at, confidential, fav and `change` (a hash of the changed values, sometimes empty). LIVE: a SCOPE IS MANDATORY even though the API docs mark every parameter optional — pass at least one of person_id, company_id, deal_id, project_id, user_id or account_id, otherwise the API answers HTTP 400 bad-request. For the whole account use account_id (it is on every record as `account_id`). Activities cannot be written through the API — log customer interactions with create_protocol instead.

- **GET** `get_activity` — Retrieve one activity-stream entry by id, including its `change` hash.
- **GET** `count_activities` — Number of activity entries. LIVE: a scope is MANDATORY — pass one of person_id, company_id, deal_id, project_id or account_id, otherwise the API answers HTTP 400 bad-request.
- **GET** `list_filter_groups` — The saved segments ("filter groups") the account built in the UI: id, name and model_type (Person, Company, Deal or Project). Since the bracket-nested filter syntax is not available through this DADL, a filter group is the way to reuse a complex, human-curated segment — list them here, then fetch the members with get_filter_group.

- **GET** `get_filter_group` — The RECORDS matching a saved filter group. The result type follows the group's model_type, so `kind` is person, company, deal or project. includes and methods accept the same values as the corresponding list_* tool for that model type.

- **GET** `list_filter_group_ids` — Only the IDs of the records matching a filter group, plus the group's name and record_type. Much cheaper than get_filter_group when you just need to iterate or intersect sets.
- **GET** `count_filter_group` — How many records match a saved filter group, as a total_entries object.
- **GET** `list_hooks` — The account's webhooks: model, event, state, target_url, object_type, includes, methods, plus failed / failed_at delivery diagnostics. includes: filter_groups.
- **GET** `get_hook` — Retrieve one webhook by id, including its delivery failure counters.
- **POST** `create_hook` — Register a webhook. Wrap it under the `hook` key. CentralStationCRM will POST to your URL whenever the chosen event happens — set this up only when the user asked for an integration, and verify the target URL belongs to them. Writable fields: model (REQUIRED — Person|Company|Deal|Project|Protocol), event (REQUIRED — create|update|destroy), target_url (REQUIRED — where the payload is delivered), object_type (REQUIRED — the payload shape: nested|api|lean| hook_with_structure_and_record), hook_type (REQUIRED — currently only "url"), state (active|disabled), includes (comma-separated sub-records to embed in the payload, for the nested/api/hook_with_structure_and_record shapes), methods (array of computed fields, e.g. salutation_official). One hook covers exactly one model + event pair — register several for several events.

- **DELETE** `delete_hook` — Delete a webhook — deliveries stop immediately. Irreversible; there is no update endpoint, so recreate the hook to change it.

## What composite workflows does the CentralStationCRM DADL provide?

- **FN** `find_person_by_contact` — Resolve a person from an e-mail address or a phone number and return the full record. Tries the dedicated people search first (email, then phone) and falls back to the polymorphic contact-detail reverse lookup, which also finds numbers stored in an unusual format. Returns the person object with everything embedded (includes=all, methods=all), or null when nobody matches — the tool to call before creating a contact, and the one to answer "who is calling?".

- **FN** `get_person_360` — Everything the CRM knows about one person in a single call: the person record with all child objects embedded (positions, companies, deals, projects, tags, tasks, contact details, addresses, custom fields, historic events, assistants) plus the most recent protocols (notes, calls, meetings) as a separate `protocols` array. Use this for briefings before a call instead of chaining get_person and list_protocols yourself.

- **FN** `log_note` — Log an interaction on one or more records in a single call — the shortcut for "note down what was discussed". Wraps create_protocol: pass the text plus any combination of person_id, company_id, deal_id and project_id, and optionally a badge (note, call, email, meeting, other). Returns the created protocol.

- **FN** `move_deal_to_stage` — Move a deal to another stage of its pipeline, optionally setting probability and current_state, and log the move as a note. Reads the deal first so the returned object reflects the new state and so the stage can be validated against the pipeline's stages. Returns { deal, stage_name } — or throws when the stage does not belong to the deal's pipeline.

- **FN** `fetch_all_people` — Page through list_people until the account is exhausted or max_pages is reached, and return one flat array. Respects the API maximum of 250 records per page and stops as soon as a short page arrives. Pass includes/methods to embed child records, or stream_updated_at_after for an incremental sync. Keep max_pages modest — the API allows 50 requests per 10 seconds and the composite sandbox allows 50 calls in total.

- **FN** `get_custom_field_values` — Read the custom field values (the account's own fields, e.g. a customer number on companies) of one person, company, deal or project, joined with their definitions. Returns { record_type, record_id, record_name, fields: [{ custom_fields_type_id, label, ftype, options, custom_field_id, value }] } in the account's field order; date and decimal fields add `typed_value`. Fields the record does not carry come back with value null, so the result also shows what could be filled — pass include_unset: false to get only the set ones. record_type is Person, Company, Deal or Project (lower case and plural work too). Costs two API calls.

- **FN** `set_custom_field_value` — Set, change or clear ONE custom field value on a person, company, deal or project — the write path for fields such as a customer number on companies, which the API only reaches through nested attributes. Name the field by custom_fields_type_id or by field_name (its label, case-insensitive; see list_custom_field_types). The composite reads the record, then creates the value, changes it by its value id, or removes it when value is empty or null, and reads the record again — so it never trips the API's one-value-per-field rule (a second value for the same field answers 422). Select fields only take one of their options. Dates go in as YYYY-MM-DD (DD.MM.YYYY is converted); decimals as a number string, a decimal comma is converted to a dot and any other text is refused before the call (the API answers 422 for it). Returns { record_type, record_id, custom_fields_type_id, label, ftype, action (created|updated|removed|unchanged), previous, value, custom_field_id }. Writes an entry to the record's activity stream like any update.

- **FN** `find_records_by_custom_field` — Find the people, companies, deals or projects whose custom field matches a value — e.g. "which company has customer number C-1001?" — or, without value, every record that has the field set at all (an audit of one field across the account). Name the field by custom_fields_type_id or field_name (its label, case-insensitive). Pages the matching list_* tool with includes=custom_fields, 250 records per page, up to max_pages (default 10, at most 40). Matching ignores case and surrounding spaces; match "contains" accepts partial values. Date fields also match on the parsed date (2027-01-16 finds a value entered as 16.01.2027), decimal fields compare numerically (13,75 finds 13.75). Returns { record_type, custom_fields_type_id, label, scanned, complete, matches: [{ id, name, value, custom_field_id }] } — complete false means max_pages ran out before the last page. Fetch full records with get_person / get_company / get_deal / get_project.


## Which DADLs are related to CentralStationCRM?

- [Zammad](https://www.dadl.ai/d/zammad/) — Zammad helpdesk REST API -- tickets, articles, users, organizations, groups, roles, knowledge base, SLAs, calendars, object manager (custom fields), macros, triggers, overviews, reports, time accounting, and full admin surface. Supports X-On-Behalf-Of impersonation on every endpoint.
- [Zulip](https://www.dadl.ai/d/zulip/) — Zulip REST API — messages, channels (streams), topics, users, user groups, subscriptions, reactions, drafts, scheduled messages, saved snippets, presence, real-time events, invitations, attachments, custom emoji, linkifiers, and server settings. Self-hosted or Zulip Cloud.
- [Billomat](https://www.dadl.ai/d/billomat/) — Billomat REST API — German cloud invoicing & accounting: invoices, estimates, credit notes, recurring invoices, delivery notes, order confirmations, dunning reminders, clients, contacts, articles, payments, expenses, taxes
- [Alertmanager](https://www.dadl.ai/d/alertmanager/) — Prometheus Alertmanager API v2 -- alerts, silences, receivers, alert groups, status, and operational health
- [BookStack](https://www.dadl.ai/d/bookstack/) — BookStack wiki and documentation platform REST API -- shelves, books, chapters, pages (HTML/Markdown content), attachments, image gallery, comments, cross-entity search, exports (HTML/Markdown/PDF/ZIP), ZIP imports, users, roles, content permissions, recycle bin, audit log and tags. Hierarchy: shelves > books > chapters > pages.
- [Checkmk](https://www.dadl.ai/d/checkmk/) — Checkmk REST API (version 1.0, Checkmk 2.4) -- live host and service status via Livestatus queries, downtimes, acknowledgements and comments, the Event Console (list, acknowledge, change state and archive events) and BI (packs, rules, aggregations and live aggregation state), plus the full Setup side: hosts, clusters, folders, service discovery, rules and rulesets, host/service/contact groups, users, roles, passwords, time periods, host tags, notification rules, site connections and change activation.


---

**Canonical URL:** https://www.dadl.ai/d/centralstationcrm/
**Raw DADL:** https://github.com/DunkelCloud/dadl-registry/blob/main/centralstationcrm.dadl
