StabisoDocs

Sales and customers

Contacts, parent companies and your own fields

The people at customers and vendors are contacts of their own: each with a first and last name, a role, e-mail, phone and mobile, the language to write to them in and whether they want sales e-mails. A customer or vendor may belong to a parent company, and an organization adds fields of its own to its companies, contacts and deals. Imported records keep the id they had in the system they came from. The design is ADR-0085. Nothing here posts, and none of it is a key detail a second person approves: these records describe who is who, they never decide an amount, a tax or who is paid.

Contacts

GET   /v1/contacts                       ?entity&party_id&q&email&erased
POST  /v1/contacts                       {entity, party, kind?, first_name?, last_name, role?, email?, phone?, mobile?, language?, email_opt_out?, custom?}
GET   /v1/contacts/{contact_id}
PATCH /v1/contacts/{contact_id}          what changes ("" clears a field but the last name)
POST  /v1/contacts/{contact_id}/erase

A contact is a person at one customer or vendor of one company (party, by code or id; kind when a code names both). It never moves to another party. The card of a customer or vendor lists its contacts (contacts), and the first ones can come with the party when it is created (contacts of POST /v1/entities/{entity}/parties, a name there is split: the last word is the last name). The list of a party is no longer changed by PATCH of the party (422): each contact is added, changed and erased by itself.

Invoices go by default to the contacts whose role says billing or accounting (Buchhaltung, comptabilité, contabilità …), after the e-invoice address and before the customer's e-mail address; every contact's address is one a document may be sent to. email_opt_out is for sales and marketing e-mails; invoices and reminders still go.

Erasing (POST …/erase) empties the 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 that someone was there (its name reads ""). Only a person erases (an agent: 403 contact_erase_agent), and an erased contact never changes again. The audit trail of a contact records which fields changed, never their values, so that erasing a contact erases it everywhere. The database keeps it too: a contact is never deleted, never moved to another party, and once erased stays erased (migration 0125).

Deals and activities name a contact

A deal with a customer names its contact person among the customer's contacts (contact_id of POST /v1/opportunities and PATCH; "" clears it); its contact_name, contact_email and contact_phone are then the contact's, and the deal keeps no copy of them. A deal with a prospect still names the person on the deal (contact_name …). A new customer on the deal drops a contact that is not one of its people. A won deal still takes a contact.

An activity names the person it was with (contact_id of POST /v1/activities); one of a deal takes the deal's contact by default. GET /v1/activities?contact_id= is a person's timeline.

Parent company

parent (code or id) of a customer or vendor names the company it belongs to: a party of the same company and kind, never itself or one of its own subsidiaries (a loop; the database refuses it too). The card shows the parent and the subsidiaries; GET /v1/entities/{entity}/parties?parent= lists a company's subsidiaries. "" clears it.

Your own fields

GET   /v1/custom-fields                  ?record&retired
POST  /v1/custom-fields                  {record, key, label, type, choices?, position?}   (admin)
PATCH /v1/custom-fields/{field_id}       {label?, choices?, position?, retired?}            (admin)

A field is for company (customers and vendors), contact or deal records, with a key the API uses and a label the screens show, of a type: text (up to 1,000 characters), number (kept exactly, as its text: "25.5"), date (2026-10-23), yes_no (true or false) or choice (one of its choices, kept in the choice's own spelling). The key and type never change. Up to 25 fields are in use per kind of record. On a new field, Suggest with AI proposes the type and, for a choice, the choices that fit its label; nothing is saved until you save.

A record's values are in its custom, by key: custom of a party, a contact or a deal, on create and change. A change sends only the keys that change; null removes a value. An unknown key or a value of the wrong type is refused (422 custom_field_unknown, custom_field_value).

A field is retired, never deleted (PATCH {retired: true}): it leaves the forms and takes no new values; the values records hold stay readable. A choice a record holds stays in the list (409 custom_field_choice_in_use).

Merging duplicates

A customer entered twice is merged into the one that stays (POST /v1/entities/{entity}/parties/{party}/merge with {"into": "C-CH"}, on the card Merge into…), but only while it has no business records of its own: no document, payment, order, subscription, price list, mandate or other record of the books names it, no won deal and no request waiting for approval (409 party_merge_has_records). Its contacts, deals, activities and subsidiaries move, its own field values fill the ones the customer that stays has none of, and it stays as an inactive customer with merged_into on its card, for good. Vendors are not merged.

A contact entered twice is merged into another contact of the same customer or vendor (POST /v1/contacts/{contact_id}/merge with {"into": "<id>"}): the one that stays takes the details it has none of, its deals and activities move, and the duplicate is erased with merged_into_id. Only a person merges (403 merge_agent); ADR-0089 has the rules.

Imported records

A record imported from another system (a Salesforce export) keeps its id there in the import's own links (external_record, ADR-0084), so that an import run again finds the record it made.

Test copies and exports

A masked test copy replaces each contact's name, e-mail and phones with mocks and masks the text of its own fields. The organization's export has the contacts (crm_contact) and the fields (custom_field) with what each column means.

Who does what

Who What
Anyone with read read contacts and fields
Anyone with draft (an agent too) add and change contacts, set parent companies, own field values
A person with draft erase a contact, merge duplicates
admin add, change and retire fields
Stabiso, the Enterprise System of Record · generated from the code and its guides · release 6f03a5e846b3