Flow · 13 steps

A person, a supplier, and a right-to-erasure request

The CRM is the one registry every picker reads: a purchase order names a supplier by id, an order names its customer by id, and neither holds a name of its own. This walk registers a person, registers a company with the supplier role and makes the person its main contact, shows the parties list a picker searches, edits both, archives and restores the company, and then honours an erasure request — the interesting part. A person's identifying details are sealed in the journal under a key held outside it; erasing destroys the key in the same transaction as the fact, so history is never rewritten, every document that named the psn_ id still resolves, and what those events said is gone.

Services involved

A filled dot is a read; a ring is a call that changes something.

  • CRM Manager: https://crm.getmanifold.io

Every call carries Authorization: Bearer $MANIFOLD_TOKEN unless its step says otherwise (getting a token). Run a flow in your sandbox, not your live organization: see Before you start.

1 Register a person

Email is required and unique per organization after normalisation. reference is the caller's key for the act — an import row, a checkout, a minted id — and a retry with it is acknowledged with the person already registered. The service seals the profile before the fact is written; the journal never sees the email.

POST /people CRM Manager

Request · application/json
{
  "reference": "crm-walk:ada",
  "email": "ada@northwind.example",
  "firstName": "Ada",
  "lastName": "Lovelace",
  "phone": "+61 400 000 001",
  "roles": [
    "customer"
  ],
  "tags": [
    "trade"
  ]
}

Answers 201. What the answer holds: the operation's example.

From your answer, later steps send id as {personId}.

Records crm.person-created.

2 Read the person back

The profile is unsealed on read while the key exists: the name, email, phone and saved addresses come back in the clear, beside the settings the journal keeps openly (tags, roles, marketing).

GET /people/{personId} CRM Manager

What the answer holds: the operation's example.

3 Register a supplier

A company with the supplier role is what a purchase order's supplier picker offers. Locations carry a billing and a shipping address under a name each; a company is not a natural person, so its details stay in the clear.

POST /companies CRM Manager

Request · application/json
{
  "reference": "crm-walk:northwind",
  "name": "Northwind Metalworks",
  "roles": [
    "supplier"
  ],
  "paymentTermsDays": 30,
  "locations": [
    {
      "name": "Works",
      "shippingAddress": {
        "address1": "1 Foundry Lane",
        "city": "Geelong",
        "countryCode": "AU"
      }
    }
  ]
}

Answers 201. What the answer holds: the operation's example.

From your answer, later steps send id as {companyId}.

Records crm.company-created.

4 Make the person its main contact

A contact is a person already registered, with a title at the company; main: true displaces any previous main contact. Idempotent by content — saying it twice appends once.

PUT /companies/{companyId}/contacts/{personId} CRM Manager

Request · application/json
{
  "personId": "{personId}",
  "title": "Accounts",
  "main": true
}

What the answer holds: the operation's example.

Records crm.company-contact-set.

5 Search the parties a purchase order may name

One list of pickable rows — id, kind, display name, roles, status — narrowed to the companies that supply. This is the read behind every supplier and customer picker.

GET /parties?kind=company&role=supplier CRM Manager

What the answer holds: the operation's example.

6 Correct the person's details

Every field is optional; a field sent replaces what was there and null clears one. The event carries the whole record, sealed again under the same key.

PUT /people/{personId} CRM Manager

Request · application/json
{
  "phone": "+61 400 000 002",
  "locale": "en-AU"
}

What the answer holds: the operation's example.

Records crm.person-updated.

7 Change the supplier's terms

Company details are replaced field by field the same way; contacts are their own facts and are untouched by this.

PUT /companies/{companyId} CRM Manager

Request · application/json
{
  "paymentTermsDays": 45,
  "tags": [
    "preferred"
  ]
}

What the answer holds: the operation's example.

Records crm.company-updated.

8 Archive the supplier

It leaves the default listings and every picker, keeps its id, history and contacts. A purchase order that named it still resolves through ids=.

DELETE /companies/{companyId} CRM Manager

What the answer holds: the operation's example.

Records crm.company-archived.

9 Restore it

Back to active; a retry on an active company appends nothing.

POST /companies/{companyId}/restore CRM Manager

What the answer holds: the operation's example.

Records crm.company-restored.

10 Remove the contact

The person stays registered; only the link to the company goes.

DELETE /companies/{companyId}/contacts/{personId} CRM Manager

What the answer holds: the operation's example.

Records crm.company-contact-removed.

11 Honour an erasure request

a channel's customer redaction. The key that sealed every profile this person ever had is destroyed in the same transaction the fact is written in; the events stay, unreadable. reference is the request being honoured, and the idempotency key. Needs the admin job.

POST /people/{personId}/erase CRM Manager

Request · application/json
{
  "reference": "crm-walk:erase-ada",
  "reason": "subject-request"
}

What the answer holds: the operation's example.

Records crm.person-erased.

12 The tombstone

The row still answers — the id is referenced elsewhere — as status: erased, profile: null, tags dropped, marketing unsubscribed, named "Erased person". The email is released: the same address may register a new person who shares nothing with this one.

GET /people/{personId} CRM Manager

What the answer holds: the operation's example.

13 A document that named them still renders

ids= resolves any status, so an order whose customer was erased shows "Erased person" rather than a broken reference.

GET /parties?ids=%7BpersonId%7D CRM Manager

What the answer holds: the operation's example.

What a recorded fact is: Facts and the journal. Why a repeated step changes nothing: Idempotency and retries.