Platform
People CRM
Frontpage People CRM: the per-site person graph, identity rules, status and follow-up, hide versus forget, CLI verbs, and the public v1 API.
What this is
People CRM is Frontpage’s first-party CRM. It is a per-site person graph: one row per identifiable human on that site, plus an immutable timeline of what they did and what the owner wrote down. The purpose is operational, not marketing. You use it to answer “who is this, where are they, and what happens next?” without turning inbox rows into a second product.
It is a platform capability, like Analytics or SEO. It is not a module. There is no
widget to drop on a page and no Modules-hub card. Do not invent markup for it. Do not enable it
through frontpage modules.
frontpage people list
frontpage people get <id>
frontpage people add --email alex@example.com --name Alex
frontpage people set <id> --status working
frontpage people note <id> --body "Left a voicemail"
frontpage people follow-up <id> --at 2026-09-12T00:00:00Z
frontpage people hide <id>
Requires frontpage-host 0.1.18 or later. Older global installs report
Unknown command: people. Upgrade with npm i -g frontpage-host@latest.
Why three systems exist
Agents and developers keep mixing these up. They are siblings, not replacements.
| System | What it stores | What it is for | Command / API |
|---|---|---|---|
| Inbox | Raw submissions (each form row, signup, order, review-ask) | The ledger. Nothing is merged. Rows are not deleted. | frontpage inbox … |
| People CRM | One person per site, joined on email or phone | The working list: status, follow-up, notes, hide | frontpage people … |
| Destinations | Outbound copies to HubSpot, Pipedrive, or Privyr | The owner’s external CRM. Frontpage does not read those back. | Forms destinations |
- Inbox stays the source of truth for “what was submitted.” People never replaces it.
- Connecting Privyr or HubSpot does not create Frontpage people, and Frontpage people do not sync outward.
- Same email on two Frontpage sites is two people. There is no account-wide join and no cross-site People view.
- Frontpage billing customers are not People. Do not mint a person from a Stripe customer id.
Who can touch whose people
The People list, CLI, API, and editor tools are included on Growth. Forms and payments still
create people on every plan. People uses the same Public CLI fence as every other site-scoped
command (push,
inbox, seo, …). A key cannot target another tenant’s graph by guessing a slug,
a site UUID, or a person UUID.
| Who you are | What happens |
|---|---|
| No key, or a bad key | HTTP 401. CLI exit 4. Nothing is read or written. |
Valid key, but you are not a member of --site |
HTTP 404 Site not found. Existence is hidden. Same response for an unknown slug,
a soft-deleted site, or a purged site.
|
Member with role viewer | list and get work. Every mutate verb
(add, set, note, rm-note,
follow-up, hide, unhide, status create/archive) returns
HTTP 403. CLI exit 4.
|
Member with role editor or admin | Read and write People on that site only. |
Person UUID copied from site B, used with --site of site A |
HTTP 404 Person not found. Every People query is
(claim.siteId, person_id). The client cannot pass a site UUID.
|
-
--siteis a subdomain slug only (example:acme). Not a custom domain, notacme.frontpage.host, not a site UUID, not a person UUID. See Flags for the exact slug rules. -
The server binds work to the site that slug maps to after membership is checked.
JSON bodies that include
siteIdare ignored. Lib calls always useclaim.siteIdfrom that authorize step. - API keys never get platform-admin fleet override. A Frontpage staff key still needs real membership on the target site.
-
Keys are account-wide. One
fp_live_…acts as that user on every site they belong to. There is no per-site key and no editor-only key in v1. Sharing a key shares People (and push, inbox, settings) on all of those sites. - If someone is a real editor or admin of the victim site, they can mutate People there. That is membership, not a fence hole.
- Same email on two sites is two people. There is no cross-site People view and no account-wide join.
-
The Public CLI does not expose site members, invites, or ownership transfer. You cannot add or
remove collaborators through
frontpage. That stays in the editor. -
Cookie routes (
/api/sites/people…) use the signed-in editor session andgateSite, not an API key. Do not call those from the CLI.
Full key and 401 / 403 / 404 table: Isolation & keys.
Targeting a site (--site)
Every People verb is site-scoped. The CLI must know which subdomain you mean before it talks to the API.
-
--site <slug>,-s <slug>, or--site=<slug>on this command. Wins for this process only. Does not rewrite.frontpage/state.json. -
Otherwise the
slugin.frontpage/state.jsonfromfrontpage use <slug>in the current working directory. -
If neither is set, the CLI exits 2:
No site selected. Run: frontpage use <subdomain> or pass --site <subdomain>.
# Folder already linked
cd ~/sites/acme
frontpage use acme
frontpage people list
# One-off, any folder
frontpage people list --site acme
frontpage people add --site bakery --email alex@example.com
# Rejected locally (exit 2), never sent
frontpage people list --site acme.frontpage.host
frontpage people list --site https://acme.frontpage.host
frontpage people list --site www.acme.com
The slug is trimmed and lowercased. It must match
^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$ and must not contain
., /, or :. That is the same rule as
frontpage use. A syntactically valid slug you do not belong to still returns
404, not 403.
Identity (the join key)
A person is identified by email or phone, never by name. Name is a label. Two “Alex” rows with different emails are two people. A name-only add is rejected.
- One stored email and one stored phone per person. Both are written normalized.
-
Email: trim, lowercase, then this shape:
/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/. Spaces, missing@, or a TLD shorter than 2 characters fail. Invalid input is treated as missing, not stored as a unique key. - Phone: see the format table below. Unparseable phone is missing. It is not stored.
- On a given site, an email belongs to at most one person, and a phone belongs to at most one person.
-
Lookup order: email first, else phone. If both are present and they point at two different people, email
wins. Frontpage writes an
identity.conflictevent instead of merging or stealing the other identifier. -
Empty fields can be filled later. An identifier that already belongs to someone else on the same site
returns
409(“already belongs to someone else”). - A person must keep at least one identifier. Clearing both email and phone in one patch fails.
Email format
Flag: --email <string>. The CLI sends the raw string. The server normalizes
before write.
| You type | Stored as | Notes |
|---|---|---|
Alex@Example.com | alex@example.com | Trim + lowercase |
ada@bakery.com | ada@bakery.com | Surrounding whitespace dropped |
not-an-email | (missing) |
On add, if there is also no valid phone: 400
An email or phone is required.
On set --email: 400 That email is not valid. |
"" (empty) on set | cleared | Allowed only if a phone remains |
There is no separate “normalized” column. What you see in JSON is what is unique.
Plus-addressing (ada+vip@example.com) is a different person from
ada@example.com. Frontpage does not collapse Gmail dots.
Phone format
Flag: --phone <string>. Punctuation is fine. The server strips every
non-digit, then applies these rules. Stored value is always E.164 with a leading
+.
- Trim. Empty string is missing.
- Keep only digits. If the digit count is under 8 or over 15, reject (missing).
-
If the raw string starts with
+(before stripping), store+plus those digits. Country code is whatever you typed. This is how international numbers work. - Else if there are exactly 10 digits, store
+1plus those digits (US default). - Else if there are 11 digits and they start with
1, store+plus those digits. - Else if there are 11 or more digits, store
+plus those digits (country code already in the digits). - Otherwise reject. An 8 or 9 digit local number without a leading
+is not stored.
| You type | Stored as | Why |
|---|---|---|
(555) 123-4567 | +15551234567 | 10 US digits, no leading + |
555-123-4567 / 5551234567 | +15551234567 | Same rule |
15551234567 | +15551234567 | 11 digits starting with 1 |
+1 (555) 123-4567 | +15551234567 | Leading + is honored first. Do not get a second 1 |
+44 7911 123456 | +447911123456 | International: start with + |
+4479829126 | +4479829126 | 8 to 15 digits after stripping, leading + |
447911123456 (no +) | +447911123456 | 12 digits, treated as already including country code |
123, call me, 555-1234 | (missing) | Too few digits, or 7 US local without + |
12345678 (8 digits, no +) | (missing) | Must start with + when it is not a US 10 / NANP 11 |
On add, a garbage phone with no valid email is 400
An email or phone is required.
On set --phone, a non-empty string that does not parse is 400
That phone number is not valid. set --phone "" clears phone if an email remains.
Name format
Flag: --name <string>. Trimmed. Max 200 characters (longer input is cut).
Empty after trim is stored as null. Name is never a join key and never unique.
On mint, the name you pass is stored. On a later attach (same email or phone already on the
site), an existing name is kept. A later ingest or add does not overwrite it. Rename with
set --name.
# Valid
frontpage people add --email Alex@Example.com
# stored as alex@example.com
frontpage people add --phone "(555) 123-4567" --name Alex
# stored as +15551234567
frontpage people add --phone "+44 7911 123456" --name Nia
# stored as +447911123456
# Rejected (CLI exit 2 if you omit both flags; API 400 if both normalize to missing)
frontpage people add --name Alex
# Two sites, same email = two people
frontpage people add --email alex@example.com --site bakery
frontpage people add --email alex@example.com --site salon Status
Each person has one status slug. This is not a HubSpot lifecycle, not a deal object, not MQL/SQL, not a probability, and not a kanban product. It is a single label the owner can change.
Defaults are seeded on first status read for that site: new (New), working (Working),
won (Won), lost (Lost). Owners may add, rename, or archive labels. Cap is 12 live
statuses. You cannot archive the last remaining live status. Archived slugs cannot be assigned.
The CLI can set --status <slug>. It does not list or create labels. Use the v1
status routes below for that. The value must be a live slug on that site
(defaults: new, working, won, lost). An unknown
or archived slug is rejected. Status slugs are lowercase; when you create a label via v1, the slug
is derived as lowercase letters and digits with other characters turned into
_, trimmed, max 40 characters.
Auto-status (dumb, deterministic)
When a form, signup, booking, or payment lands on a person, status follows a small rule table. There is
no builder, no score, and no “if they opened the email.” Manual set --status always wins
over a later rule that does not fire.
| Event type | If mint / first identifier | If the person already exists |
|---|---|---|
| Any mint (including manual add) | new | n/a |
form.submitted, signup.subscribed, signup.resubscribed | new | new only if current is empty or new. Does not pull Won or Working backward |
booking.requested | working | working only if current is empty or new |
payment.paid, order.paid, square_order.paid | won | won, including from lost |
| Refunds, fulfill, Square created, owner notes | no auto change | no auto change |
- Nothing auto-sets
lost. Lost is an owner decision. - Nothing auto-pulls
wonbackward. A later form submit does not demote a paid customer. - These rules run when a form, signup, booking, or payment lands on a person.
Follow-up
Follow-up is a single timestamp on the person (followUpAt), not a task table and not a reminder
email. Set it or clear it. There is no recurrence.
- Due means
follow_up_atis before the start of tomorrow UTC. - That definition is intentional: a time later today is already due. A time tomorrow 00:00 UTC or later is upcoming.
- List with
--follow-up due|upcoming|none. - Summary
duecounts visible (not hidden) people whose follow-up is due.
frontpage people follow-up <id> --at 2026-09-12T00:00:00Z
frontpage people follow-up <id> --clear
frontpage people list --follow-up due Hide is not forget
Owner “delete” in v1 is hide. It sets hiddenAt. Default lists omit hidden rows.
Unique email and phone stay held so a later ingest (or add) on the same site with the same identifier
un-hides and attaches instead of minting a duplicate.
frontpage people list --hiddenlists only hidden rows.unhiderestores the row without changing email, phone, or status.-
Hide is not a legal wipe. Do not promise that hide erases PII, and do not invent a
people forgetcommand. - Inbox rows are still not hard-deleted. Hide does not remove a form submission.
Timeline
Each person has a timeline of what they did and what the owner wrote down. Visitor events are never edited or deleted. Notes the owner adds can be removed.
Visitor types:
form.submitted, booking.requested, signup.subscribed,
signup.resubscribed, payment.paid, payment.refunded,
order.paid, order.refunded, order.fulfilled,
square_order.created, square_order.paid.
Owner / system types (live today via CLI and v1):
person.added, note.added, person.updated,
status.changed, follow_up.set, follow_up.cleared,
person.hidden, person.unhidden, identity.conflict.
- Visitor event cards are never edited or deleted.
-
Only owner notes (
source === manualandtype === note.added) can be removed (frontpage people rm-note). -
add --note "…"mints the person and writesnote.addedin one step.noteafter the fact appends anothernote.added. -
set --notes "…"patches the person’snotescolumn (a sticky blurb). That is different from timeline notes. Prefernotefor a dated call log.
CLI map
Requires frontpage-host 0.1.18 or later. Unknown leftover flags fail closed (exit 2).
Viewer can list and get. Editor can mutate. Treat payloads as PII: do not paste emails, phones, or
note bodies into public logs.
Global flags that apply to every verb:
--site <slug> / -s / --site=<slug>,
--json, --quiet. See Flags.
--yes does nothing here (it is only for unpublish / revert).
| Verb | What it does | Required | Optional flags |
|---|---|---|---|
list | One page of people plus a site-wide summary | none (bare frontpage people is list) | list filters below |
get | One person plus recent timeline (newest first, default cap 50) | person id | --id if you do not use the positional id |
add | Mint, or attach if that email/phone already exists on this site. Always source: manual | --email and/or --phone | --name, --note |
set | Patch only the flags you pass | person id, plus at least one field flag | --name, --email, --phone, --notes, --status |
note | Append a dated note.added on the timeline | person id and --body (or --note) | |
rm-note | Delete one owner note | person id and --event (or --event-id) | |
follow-up | Set or clear followUpAt | person id and either --at or --clear | |
hide | Set hiddenAt. Unique email/phone stay held | person id | |
unhide | Clear hiddenAt | person id |
There is no people statuses CLI verb, no forget, no merge,
no email, and no sms. Status labels are v1-only (see below).
Person id
Every verb except list and add needs the person UUID. That is the
id field from list, add, or get. It is not
an email, not a phone, and not a name.
- Positional:
frontpage people get 81d0affb-1008-471d-9160-a35842de70a7 - Flag:
frontpage people get --id 81d0affb-1008-471d-9160-a35842de70a7 -
If you pass both,
--idwins and the leftover positional is treated as an unknown argument (exit 2). - A UUID that does not exist on this site returns 404
Person not found.
List flags
Filters combine with AND. Default list omits hidden rows. summary is always
site-wide (all visible people, plus a hidden count), not just this page.
An unknown --source, --sort, or --follow-up value is
ignored by the server (you get the default list, not an error). Prefer the values below.
| Flag | Allowed values | Default | What it does |
|---|---|---|---|
--q / --query | any string | none |
Case-insensitive substring match on name, email, or phone (including the stored
+ form). Useful: --q +1555 or --q alex@ |
--status | exact live slug, e.g. new, working, won, lost | all statuses | Exact match. Not a prefix. Custom slugs work if they exist on that site |
--source | form | booking | signup | payment | order | square | all |
Person has at least one timeline event in that family. Sites with only owner-added
people return empty for these. manual is not a list filter
|
--form-slug | form instance slug, e.g. contact, lead-gen | none |
Has a form or booking event whose payload formSlug equals this string.
|
--follow-up | due | upcoming | none | all | due: follow_up_at before start of tomorrow UTC (a time later
today is already due). upcoming: tomorrow 00:00 UTC or later.
none: no follow-up set
|
--hidden | boolean flag, no value | off (visible only) |
When present, list only hidden rows. There is no
--hidden false. Omit the flag for the default
|
--sort | last_seen | first_seen | name | follow_up | last_seen | follow_up sorts nulls last. Pass the printed next: /
nextCursor back as --cursor when paging
|
--last-seen-days | positive integer | none | lastSeenAt within the last N × 86400 seconds. 0 or a
non-number is ignored
|
--limit | integer 1 to 200 | 50 | Page size. Values above 200 are clamped to 200. Values below 1 become 1 |
--cursor | opaque string from the previous page’s nextCursor | first page |
Do not invent a cursor. When human output still has more pages it prints
next: <cursor> |
Add flags
| Flag | Required | Value |
|---|---|---|
--email | yes, unless --phone is present | See email format. At least one of email or phone must survive normalize |
--phone | yes, unless --email is present | See phone format |
--name | no | Display label, max 200 after trim |
--note | no |
Timeline note.added written in the same request as the mint. Trimmed.
Empty note is ignored and the event type becomes person.added instead
|
If that email or phone already exists on this site, add attaches (and un-hides
if the row was hidden). You do not get a second person. JSON includes
created / duplicate when you use --json.
Set flags
Only flags you pass are patched. The CLI omits keys you did not set. Do not reconstruct a
JSON body that includes "email": null unless you intend to clear email.
set --status working must not send empty identifier keys.
| Flag | Value | Clear |
|---|---|---|
--name | string, max 200 | --name "" stores null |
--email | valid email after normalize | --email "" if a phone remains. Invalid non-empty → 400 |
--phone | valid phone after normalize | --phone "" if an email remains. Invalid non-empty → 400 |
--notes | sticky blurb on the person row (not a timeline event) | --notes "" stores null. Prefer note for a dated call log |
--status | live slug on this site | cannot clear; omit the flag to leave status alone |
Clearing both identifiers in one patch fails with
A person must keep an email or a phone.
Moving an email or phone onto a row that already has it returns 409
That email already belongs to someone else. (or phone).
Note, follow-up, hide
| Flag | Verb | Value |
|---|---|---|
--body / --note | note |
Non-empty after trim. Required. --body and --note are aliases;
--body is checked first
|
--event / --event-id | rm-note |
Timeline event UUID from get. Only
source === manual and type === note.added can be removed.
Visitor events (payments, forms) cannot
|
--at | follow-up |
Timestamp parsed by new Date(string). Prefer ISO-8601 with a timezone,
e.g. 2026-09-12T00:00:00Z. Date-only 2026-09-12 is UTC midnight.
A string the engine cannot parse is 400 Follow-up must be a valid timestamp. |
--clear | follow-up |
Boolean flag, no value. Sets followUpAt to null. If you pass both
--clear and --at, clear wins
|
hide and unhide take no extra flags. Both are idempotent: hiding an
already hidden row does not write a second person.hidden event.
Public v1 API
Base: https://app.frontpage.host/api/v1/sites/:slug/people.
Authenticate like every other public CLI call (API key). Viewer for reads, editor for writes.
Reads are RL-READ. Mutations are RL-WRITE-LIGHT (30 / min). See
Rate limits.
Successful bodies are the usual v1 envelope: the fields below sit next to slug (and
requestId on errors). Audit actions: site.people.add,
site.people.set, site.people.note, site.people.status_upsert,
site.people.status_archive, plus hide / unhide / follow-up.
| Method | Path | Role | Body / query |
|---|---|---|---|
GET | /people | viewer | q, status, source, formSlug,
followUp, hidden=1, sort, lastSeenDays,
limit, cursor |
POST | /people | editor | { email?, phone?, name?, note? } (email or phone required). 201 |
GET | /people/:id | viewer | Person + timeline |
PATCH | /people/:id | editor | Only keys you send: name, email, phone, notes, status |
POST | /people/:id/hide | editor | Empty object |
POST | /people/:id/unhide | editor | Empty object |
PATCH | /people/:id/follow-up | editor | { at: iso | null } |
POST | /people/:id/events | editor | { body: "note text" } only. 201. Owner notes only |
DELETE | /people/:id/events/:eventId | editor | Owner note.added only |
GET | /people/statuses | viewer | Seeds defaults on first call |
POST | /people/statuses | editor | { label, slug? }. Rename if slug exists; else create (cap 12 live) |
DELETE | /people/statuses/:statusSlug | editor | Archive (not hard-delete). Cannot archive the last live status |
Cookie twins (editor browser session, same rules, not public-key):
/api/sites/people, /item, /hide, /unhide,
/follow-up, /notes, /statuses. Do not call these from the CLI.
Response shapes
List (abbreviated). summary is site-wide, not just this page:
{
"slug": "acme",
"people": [
{
"id": "81d0affb-1008-471d-9160-a35842de70a7",
"siteId": "…",
"email": "alex@example.com",
"phone": "+15551234567",
"name": "Alex",
"status": "working",
"followUpAt": "2026-09-12T00:00:00.000Z",
"hiddenAt": null,
"firstSeenAt": "2026-09-10T04:09:50.919Z",
"lastSeenAt": "2026-09-10T04:09:50.919Z",
"sourceFirst": "manual",
"notes": null,
"attrs": {},
"lastEvent": { "type": "note.added", "summary": "Called them", "occurredAt": "…" }
}
],
"nextCursor": null,
"summary": {
"total": 1,
"due": 1,
"hidden": 0,
"byStatus": [
{ "slug": "new", "label": "New", "count": 0 },
{ "slug": "working", "label": "Working", "count": 1 },
{ "slug": "won", "label": "Won", "count": 0 },
{ "slug": "lost", "label": "Lost", "count": 0 }
],
"bySource": [],
"byFormSlug": []
}
} get adds events (newest first, default cap 50). Human CLI output is one line per
person, then one indented line per event.
Worked example
export FRONTPAGE_API_KEY=fp_live_…
frontpage use acme
# Who is already on the graph?
frontpage people list --json
# Capture someone you just spoke to (email or phone required)
frontpage people add --email alex@example.com --name Alex --note "Called from the homepage form"
# Move them, book a follow-up, add a dated note
frontpage people set <id> --status working
frontpage people follow-up <id> --at 2026-09-12T00:00:00Z
frontpage people note <id> --body "Left a voicemail"
# Morning list
frontpage people list --follow-up due --json
# Wrong person? Hide. Same email later will un-hide, not duplicate.
frontpage people hide <id>
frontpage people list --hidden Errors you will actually see
| Situation | HTTP / CLI | Message (typical) |
|---|---|---|
Missing --email and --phone on add | exit 2, no request | CLI usage line |
| Both identifiers normalize to missing | 400 / exit 2 | An email or phone is required. |
| Unknown leftover flag | exit 2, no request | Unknown flag |
No --site and no frontpage use | exit 2 | No site selected. |
--site acme.frontpage.host | exit 2 | Must be a subdomain slug, not a domain or URL |
| Bad or missing API key | 401 / exit 4 | Unauthorized |
| Viewer trying to add/set/hide | 403 / exit 4 | Forbidden (member without privilege) |
| Slug you do not belong to | 404 / exit 4 | Site not found. |
| Person UUID not on this site | 404 / exit 4 | Person not found. |
| Email or phone already on another row | 409 / exit 3 | That email already belongs to someone else. |
Invalid set --email / --phone | 400 / exit 2 | That email is not valid. / That phone number is not valid. |
| Clear last identifier | 400 / exit 2 | A person must keep an email or a phone. |
Unknown or archived --status | 400 / exit 2 | Unknown or archived status. |
Bad --at | 400 / exit 2 | Follow-up must be a valid timestamp. |
| Remove a payment / form event | 400 / exit 2 | Only owner notes can be removed. |
| Rate limit | 429 / exit 2 | rate_limited. Honor Retry-After |
Reads are RL-READ (120 / min per key). Mutations are
RL-WRITE-LIGHT (30 / min per key). See
Rate limits and
Errors & exit codes.
Agent rules
- Read this page and
frontpage help peoplebefore inventing verbs, flags, or body fields. - Unknown leftover flags fail closed. Do not “helpfully” pass through extra flags.
- Do not use
inbox … --mark-readunless the user asked. Inbox is a different system. - Do not paste People payloads into public logs, issues, or chat that leaves the owner’s machine.
- Do not add People rows to a customer site unless the user asked. Prefer a throwaway or the site they named.
- Do not treat hide as legal delete. Do not invent
forget,merge,email, orsms. - Do not write HubSpot/Pipedrive/Privyr tokens or webhooks into People. Destinations stay on forms.
-
Forms, signups, bookings, and payments create people automatically. Use
addfor people who have not done those things yet. - Status-only PATCH: send
{ "status": "working" }only. Never send null identifiers by accident. - People mutations do not change staging HTML and do not require
publish.
Common mistakes
| Mistake | What actually happens |
|---|---|
| Add with name only | Rejected. Email or phone is required |
| Use People as a replacement for inbox | Inbox still holds every raw row. People is the merged working list |
| Assume a form submit stays only in inbox | Inbox still stores the raw row. People also gets (or updates) the person |
| Hide, then add the same email again | The hidden row un-hides. You do not get a second person |
| Move an email onto someone else | 409 conflict. Identifiers are not stolen |
PATCH status with email: undefined in a reconstructed body | Treated as a clear if the key is present. Omit the key |
| Delete a payment event from the timeline | Rejected. Only manual notes are removable |
Install an old frontpage binary | Unknown command: people. Need frontpage-host@0.1.18+ |
Isolation (why a CRM bug must not 409 a visitor)
People lives in the product app only. The public site gateway never talks to People. A bad People write cannot fail a visitor form post. Destinations keep dispatching on their own path. Do not invent a People module, and do not route visitor HTTP through People.
Cross-tenant isolation is the same membership fence as the rest of v1. Developers cannot
push another tenant’s files, cannot add or hide people on a site they do not
belong to, and cannot drive People with a stolen person UUID on the wrong
--site. What they can do is use a shared account-wide key on every site
that key’s owner already belongs to. Treat keys like passwords.
Also see
- Content & settings for inbox, SEO, media
- Forms destinations for HubSpot / Pipedrive / Privyr
- Rate limits for
RL-READ/RL-WRITE-LIGHT - Command reference for the shipped command list
- Isolation & keys for site fencing