frontpage-host 0.1.28 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -119,7 +119,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
119
119
  ## Modules & articles
120
120
 
121
121
  - Forms: `data-fp-form` markup in pages + `frontpage modules` for enable/config. No submit JS / honeypot / `action`. Do not put a CRM webhook or token in HTML.
122
- - Connect-once modules: Store Stripe connect is `frontpage store connect`. `--no-open` for CI. `request-setup` remains valid when you want the **editor amber bar** instead. Payments and the store share one Stripe account — payments disconnect fails if the store still uses it. Auto-active modules (forms/callbar/…) need markup, not request-setup. Typed settings: `frontpage modules callbar|forms|booking|signups get|set` (forms set needs `--instance`; do not target `default`). Form destinations (HubSpot / Pipedrive / Privyr): connect once, then attach each form. `frontpage modules forms destinations|connect-destination|attach-destination|detach-destination|remove-destination --yes|request-destination-setup`. Token write is **admin**; `request-destination-setup` lights the editor amber bar (**editor** key). Do not use `request-setup forms` for a CRM (no-op). Connecting does not attach every form. Privyr: paste the Incoming Webhook URL from Automations → Lead Sources → Webhooks (`https://www.privyr.com/api/v1/incoming-leads/…`), not a token and not Outgoing Webhooks. HubSpot: Development → Keys → Service keys or Development → Legacy apps → Auth → Show token. Pipedrive: profile → Company settings → Personal preferences → API. Full docs: https://frontpage.host/developers/modules#forms-destinations. `frontpage modules <key>` prints that key's CLI usage (exit 2). Index: https://frontpage.host/developers/modules. Store: https://frontpage.host/developers/store
122
+ - Connect-once modules: Store Stripe connect is `frontpage payments connect`. `--no-open` for CI. `request-setup` remains valid when you want the **editor amber bar** instead. Payments and Store share one Stripe account. Pause Payments through its availability action; generic module writes cannot bypass invoice/recurring stop rules. Auto-active modules (forms/callbar/…) need markup, not request-setup. Typed settings: `frontpage modules callbar|forms|booking|signups get|set` (forms set needs `--instance`; do not target `default`). Form destinations (HubSpot / Pipedrive / Privyr): connect once, then attach each form. `frontpage modules forms destinations|connect-destination|attach-destination|detach-destination|remove-destination --yes|request-destination-setup`. Token write is **admin**; `request-destination-setup` lights the editor amber bar (**editor** key). Do not use `request-setup forms` for a CRM (no-op). Connecting does not attach every form. Privyr: paste the Incoming Webhook URL from Automations → Lead Sources → Webhooks (`https://www.privyr.com/api/v1/incoming-leads/…`), not a token and not Outgoing Webhooks. HubSpot: Development → Keys → Service keys or Development → Legacy apps → Auth → Show token. Pipedrive: profile → Company settings → Personal preferences → API. Full docs: https://frontpage.host/developers/modules#forms-destinations. `frontpage modules <key>` prints that key's CLI usage (exit 2). Index: https://frontpage.host/developers/modules. Store: https://frontpage.host/developers/store
123
123
  - Google reviews: platform nodes only (`data-fp-google-reviews` / `data-fp-google-ask` + required `data-fp-requires="google_reviews"`). `data-layout` is `feed` (default), `badge`, or `link`. Never invent stars, quotes, a 1–10, or a Maps URL. Do not pick a Place ID via Autocomplete and do not write `placeId` through `modules config`. Owner-supplied Place ID / Maps URL: `frontpage modules google_reviews connect --place-id …` or `--maps-url …` (server resolves + seeds). Curation: `list-reviews`, `hide --id`, `show --id`, `set-stars`, `set-sort`, `set-weight`, `reset-weights`, `refresh`, `disconnect`. Usage: `frontpage modules google_reviews`. Docs: https://frontpage.host/developers/modules/google-reviews. Ask-widget scores: `frontpage inbox google_reviews`.
124
124
  - Articles: `frontpage articles …` for lifecycle; bodies via sync under `content/` or `articles content get|set`. Section landing is an owned `src/pages/<section>/index.astro`.
125
125
  - Migrate a folder into Articles: prefer `frontpage prompt "migrate … into articles"` — do not invent a mass delete+write migration.
@@ -129,7 +129,7 @@ Sites are Astro 6 + Tailwind v4. More detail: `frontpage help site-building` or
129
129
  - Analytics: bare `frontpage analytics` = dashboard; also `summary|query|funnel|timeseries|compare|sql` for structured reads (SQL is guard + RLS site-scoped).
130
130
  - People CRM: `frontpage people list|get|add|set|note|rm-note|follow-up|hide|unhide|tag|export`. Site-scoped person graph (email or phone). Inbox stays the raw ledger. Hide is not forget. List export is CSV (not a one-person legal dump). Full guide: https://frontpage.host/developers/people
131
131
  - Routines: `frontpage routines list|get|add|set|pause|resume|rm|run|runs`. One routine can hold several hours and several days. `8am,12pm,4pm` on `tue,thu,fri` stores hours `[8, 12, 16]` and weekdays `[2, 4, 5]`. Ask for an IANA timezone. Do not guess UTC. Full guide: https://frontpage.host/developers/routines
132
- - Store catalog: `frontpage store items|variants|categories|settings|orders|payments|connect|status|disconnect`. Integer cents only (`$49.99` is `4999`). `--price-cents` is the usual cost. Optional `--discount-cents` is the sale the buyer pays (must be lower). Create a category before assigning it. Photos are a site path `/__media/uploads/<this-slug>/…` or an HTTPS URL already saved on this site. `frontpage store connect [--no-open]` opens Frontpage then Stripe. Checkout is store-wide (`settings set-checkout`); do not use leftover `items --checkout` to pick cart vs Pay now. Service fee is `settings set-checkout-fee`. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one `src/pages/products/item.astro` (or shop) with `data-fp-store-product`. If they said no, skip that file and do not bind `href` or `url`. Description is a small markdown subset; product-page bind is `<div data-fp-store-bind="description">`. Every store control carries `data-fp-requires="store"`. Do not bake titles or prices. Full guide: https://frontpage.host/developers/store
132
+ - Store catalog: `frontpage store items|variants|categories|settings|orders|import`. Integer cents only (`$49.99` is `4999`). `--price-cents` is the usual cost. Optional `--discount-cents` is the sale the buyer pays (must be lower). Create a category before assigning it. Photos are a site path `/__media/uploads/<this-slug>/…` or an HTTPS URL already saved on this site. `frontpage payments connect [--no-open]` opens Frontpage then Stripe. Store always uses Add to cart. Use Payments for standalone Pay now and invoices. Service fee is `settings set-checkout-fee`. The cart starts hidden. Product pages are the default: unless they said they do not want product pages, write one `src/pages/products/item.astro` (or shop) with `data-fp-store-product`. If they said no, skip that file and do not bind `href` or `url`. Description is a small markdown subset; product-page bind is `<div data-fp-store-bind="description">`. Every store control carries `data-fp-requires="store"`. Do not bake titles or prices. Full guide: https://frontpage.host/developers/store
133
133
 
134
134
  ## Agent prompt loop
135
135
 
@@ -256,3 +256,11 @@ frontpage doctor --json
256
256
  ## People groups, stages, rules, and imports
257
257
 
258
258
  Use `frontpage people overview`, `groups`, `stages`, `membership`, `rules`, `attachments`, `import`, and `company`. Use `frontpage mail campaign` for one-off group/stage/list audiences in Emails. Every editor action is available through these commands or `frontpage people workspace OPERATION --file payload.json`. Full operations and examples: [PEOPLE.md](./PEOPLE.md); public guide: https://frontpage.host/developers/people. Automated emails select a saved template or series. Campaigns use their own drafts.
259
+
260
+
261
+ ## Customer Inbox
262
+
263
+ Internal, allowlisted sites only. Use `frontpage customer-inbox get|put|post RESOURCE`, stable write keys and current versions. Full guide: [CUSTOMER-INBOX.md](./CUSTOMER-INBOX.md). `export CONVERSATION_ID --out FILE` writes authorized paginated history and file metadata; `download FILE_ID --out FILE` downloads through a file grant. Outputs never overwrite existing files. `frontpage usage` reports shared site credits, mail and storage. Human owners may handle conversations; website agents use `manage_customer_inbox` for setup/settings/widget/internal knowledge only, never a human CLI key to read or join customer threads.
264
+
265
+
266
+ Payments, invoices and recurring billing: see packaged [PAYMENTS.md](./PAYMENTS.md), `frontpage help payments`, and https://frontpage.host/developers/payments. Version 0.2.0 removes writable currency, Store checkout mode and Store-owned money commands; Store stays cart-only. Preview/review before sends; keep stable operation keys on retries. Autopay requires the customer’s own verified authorization.
@@ -0,0 +1,237 @@
1
+ # Customer Inbox
2
+
3
+ Customer Inbox uses the site's existing AI credits and storage allowance. It is available on internal plans and approved pilot sites. The support agent uses fixed GPT-6 Luna and bills AI usage at 10× raw provider cost. During human handling, customer messages use ordinary routing. Each authenticated owner email uses a small metered Luna Decisions call to keep private guidance, ownership controls and customer-facing replies distinct; the full support agent stays paused. UI and CLI controls need no intent call.
4
+
5
+ Run `frontpage help customer-inbox`. Use the site's normal CLI key:
6
+
7
+ ```sh
8
+ frontpage customer-inbox get status --site my-site
9
+ frontpage customer-inbox get settings --site my-site
10
+ frontpage customer-inbox put settings --file ./settings.json --request-key settings_revision_1 --site my-site
11
+ ```
12
+
13
+ Reads use `get`. Writes use `put` or `post`, a UTF-8 JSON object file, and an 8–128 character request key. Reuse the same key and body after a failed or lost response. Changed bodies need new keys. The service rejects a key reused for a different request. Read current versions before changing records.
14
+
15
+ ## Setup and settings
16
+
17
+ | Resource | Method | JSON body |
18
+ | --- | --- | --- |
19
+ | `status` | GET | None |
20
+ | `activation` | PUT | `{"enabled":true}`; admin only |
21
+ | `address` | GET | Returns the current address, domain and policy version |
22
+ | `address` | PUT | `{"alias":"acme","policyVersion":"2026-10-13.1"}`; admin only |
23
+ | `address` | POST | `{"retire":true}`; admin only |
24
+ | `settings` | GET | Returns `version`, `value`, fixed model and multiplier |
25
+ | `settings` | PUT | `{"expectedVersion":0,"value":{"instructions":"Ask me if unsure."}}` |
26
+
27
+ Setup binds private guidance to the verified owner who requested it. That identity comes from Core, never model/customer fields. The agent manages new conversations by default after setup; the platform's rollout switches remain off until pilot verification passes. Set `value.enabled:false` for manual handling. Settings changes cancel stale automatic jobs and queued automatic sends.
28
+
29
+ Settings allow `enabled`, `instructions`, `tools`, `maxSteps` (1–12), `maxOutputTokens` (256–2400), and `maxInputBytes` (4000–64000). Supported tools: `knowledge_read`, `knowledge_update`, `customer_read`, `customer_update`, `notes_read`, `note_add`, `ask_owner`, `reply_customer`, `wait`, `finish`. Keep `wait` and `finish` enabled. Defaults: 8 steps, 1200 output tokens per call, 32000 bytes of public text. Model, billing multiplier and recipients are not caller-selectable fields. The worker also enforces platform concurrency, deadlines and input limits.
30
+
31
+ Aliases use 3–48 ASCII letters, numbers and single hyphens. The service normalizes case and checks collisions in one transaction. Reserved platform names and routing namespaces cannot be claimed. Retired addresses remain reserved; they never move to another site.
32
+
33
+ ## Internal knowledge and approved sources
34
+
35
+ | Resource | Method | JSON body |
36
+ | --- | --- | --- |
37
+ | `knowledge` | GET | Recent notes; optional `?q=search` |
38
+ | `knowledge/<id>` | GET | One full note |
39
+ | `knowledge/<id>/history` | GET | Immutable revisions for that note |
40
+ | `knowledge` | PUT | `{"title":"Evening visits","body":"Ask the owner before promising an evening visit.","expectedVersion":0,"reason":"Owner policy"}` |
41
+ | `knowledge/<id>` | PUT | Current title/body, `expectedVersion`, `reason`, optional `locked` or `restoreVersion` |
42
+ | `sources` | GET | Approved source records |
43
+ | `sources` | PUT | `{"url":"https://example.com/help","enabled":true,"expectedVersion":0}`; fetches public text |
44
+
45
+ Supply `body` on a source write to save an owner-approved excerpt instead of fetching. Published sources refresh when the agent reads a source older than 24 hours. The fetcher reuses Core's DNS-pinned public HTTP infrastructure, rejects private addresses, checks redirects and bounds content. Sources over 16000 text characters require an owner-approved excerpt. No scripts run. A source version/digest fences knowledge updates against a changed source.
46
+
47
+ Knowledge search uses bounded literal keywords from natural-language queries and ranks notes by matched terms. Read the full matching note before updating it; update its current ID/version instead of creating a conflicting duplicate.
48
+
49
+ Lock a knowledge note with `locked:true`. The support agent cannot change locked notes or locks. Restore an older note with `restoreVersion`; the service creates a new revision and preserves the old history. The agent can change only internal knowledge, using verified owner guidance or a current approved source. It cannot edit public pages or articles.
50
+
51
+ ## Human conversation handling
52
+
53
+ | Resource | Method | JSON body |
54
+ | --- | --- | --- |
55
+ | `conversations` | GET | Paginated list; `?limit=50&after=<id>` |
56
+ | `conversations/<id>` | GET | Thread and latest public message |
57
+ | `conversations/<id>/messages` | GET | `?after=0&limit=50&audience=all`; viewers get public history only |
58
+ | `conversations/<id>/routing` | GET | Current handler/version, People link and delivery states |
59
+ | `conversations/<id>/reply` | POST | `{"text":"Tuesday at 6:30 works. Keep staffing private.","handlerVersion":0,"privateGuidance":true}` |
60
+ | `conversations/<id>/control` | POST | `{"action":"takeover","handlerVersion":0}` or `{"action":"return","handlerVersion":1}` |
61
+ | `conversations/<id>/reply` | POST | `{"text":"Your quote is ready.","handlerVersion":1}`; requires current owner control |
62
+ | `files?messageId=<id>` | GET | Bound message files; editor/admin only |
63
+ | `files/<id>` | GET | Short-lived download grant; editor/admin only |
64
+
65
+ Private guidance stays in the same internal thread. The agent asks by a private email and stores its question in the history. Replying by email or UI gives context and leaves the agent responsible for the customer. The agent uses that context to answer and can update unlocked internal knowledge.
66
+
67
+ General owner facts, such as support hours and standard policies, update related unlocked knowledge without an explicit save request. One-time customer exceptions and private staffing details stay in the conversation. The agent reads the full note and records verified evidence before changing it. Late private guidance can still update knowledge after the customer has an answer; that work cannot send a duplicate customer reply. Locks, manual handling, paused automation and work limits still apply.
68
+
69
+ Search results contain previews. The support agent reads the complete matching note or approved source before deciding an answer is missing. A verified owner reply can provide lasting facts on a different topic from the customer's question; the same job processes those facts before replying or finishing. The worker verifies the message kind and owner identity. Customer text cannot claim that authority. A specific offer to one customer stays in that thread even when the same owner reply also contains a general policy.
70
+
71
+ Email replies route through opaque, recipient-bound Reply-To addresses linked to the site, conversation, purpose, owner and handler version. These are local addresses on one domain, not a new subdomain per thread. Subjects and message text never select the customer thread. The service checks the exact verified sender, current owner membership, route and receiving-server DMARC proof before interpreting owner mail.
72
+
73
+ Every owner email and its files enter private custody first. A Luna Decisions call reads the fresh text and bounded, labeled thread context to distinguish guidance, takeover, return, customer reply or unclear input. Use ordinary language to ask to handle the customer, or ask the agent to resume. Questions, quoted commands, negations and future conditions do not transfer control. UI/CLI `control` remains a typed, model-free action. No phrase whitelist controls ownership. A valid but uncertain control answer can use one bounded Luna tool-call fallback. It can choose only private guidance, takeover, return or unclear, with an exact fresh-text excerpt; it cannot authorize a customer reply. Both calls require current credits and distinct once-only receipts. Invalid responses, unknown paid calls and provider outages stay private rather than triggering another inference.
74
+
75
+ Guidance keeps the agent responsible. Takeover sends the owner a separate customer-visible transcript; reply through that relay address so Inbox preserves the history. A customer-facing email creates a separate public message only after a settled decision and current owner-session check. The original intake remains private for audit. Mixed or uncertain input, unavailable inference and exhausted credits stay private with an attention notice. Retry recoverable budget/receipt pauses using the existing job operation; an unknown paid attempt is never inferred again. A fresh reply to the original guidance address can also request return, while stale customer-relay sessions cannot publish.
76
+
77
+ Private owner help, customer relay and customer email use separate threading chains. Inbox stores Resend's actual Message-ID and sends In-Reply-To/References; it never invents provider message IDs. Missing metadata uses scoped reconciliation without another send. Mail clients still control their own grouping. Owners and customers do not share a CC help thread. If the owner already addressed the customer, Inbox records the canonical public reply but does not send a second customer email.
78
+
79
+ Return checks current settings, site eligibility, unanswered work and site credits before support can resume. It cancels stale automatic work rather than replaying an old answer.
80
+
81
+ Set `enabled:false` in settings to use manual handling for new conversations. The service binds that relay to the configured verified owner. Existing agent threads switch to manual handling on the next customer message or public owner reply. Re-enable the agent before returning a thread to it. Private guidance alone always stays private and never transfers control.
82
+
83
+ Set `guidanceToMe:true` in a versioned settings update to bind the signed current owner's verified address. Arbitrary recipient addresses and user IDs remain forbidden. The service saves only the resolved owner identity, not this setup flag.
84
+
85
+ ## Security review and recovery
86
+
87
+ | Resource | Method | JSON body |
88
+ | --- | --- | --- |
89
+ | `blocks` | GET | Site-scoped block evidence references; human editor/admin only |
90
+ | `blocks/<id>/unblock` | POST | `{"version":1}` |
91
+ | `jobs` | GET | Job state/reason and current `updatedAt` |
92
+ | `jobs/<id>/resume` | POST | `{"updatedAt":"<current ISO time>"}` for recoverable budget, receipt, rate or service pauses |
93
+ | `receipts` | GET | Held inbound mail; admin only |
94
+ | `receipts/<id>` | POST | `{}` queues a held capture retry; admin/pilot only |
95
+
96
+ Only a validated clear attack can create an automatic site-scoped block. Authenticated customer mail binds a verified email identity; unverified mail stays thread-scoped. Blocks never ban a People contact across sites or a shared IP. Unblock preserves evidence and does not replay the attack. Uncertain, malformed, refused or unavailable screening pauses the agent and leaves human handling available. Known blocks and rate/size checks stop work before another paid call.
97
+
98
+ The fixed policy requires allow probability at least 0.90 and confidence at least 0.80, with no attack category. Automatic blocking requires malicious probability and confidence at least 0.98 and an attack category. Other results pause for human review; a review does not label the customer malicious. These are platform controls, not customer settings. Review email notices deduplicate per conversation and reason group per hour. Saved receipt retries wait at least 30 seconds so one failed receipt cannot monopolize recovery.
99
+
100
+ Receipt recovery resubmits saved provider usage to the existing ledger. It never repeats inference. An interrupted request with unknown completion requires review. Resume cannot bypass screening, replay a blocked attack, or pretend unknown usage is zero. Exact input/output/cache/reasoning counts stay unchanged; the ledger records raw cost, fixed multiplier, customer debit, job/call/conversation references and immutable prices.
101
+
102
+ ## Website-agent management boundary
103
+
104
+ The website agent uses `manage_customer_inbox` and `get_customer_inbox_docs`, with a signed manager principal bound to the owner run. It manages settings, tools, aliases and internal knowledge. It cannot read live threads or files, send messages, provide private guidance, take over/return, or review customer block evidence. Server checks enforce this even through indirect paths. Never give the website agent a human CLI/API key. Its manager tool uses no human keychain credential.
105
+
106
+ The developer guide lives at `https://frontpage.host/developers/customer-inbox`. Changes to that site use the Frontpage CLI pull/push/deploy/publish process.
107
+
108
+ ## Owner workspace and widget
109
+
110
+ Open `/workspace/sites/<siteId>/customer-inbox` on an approved internal site. The workspace uses the same versioned service operations as the CLI. Conversation rows show the latest public message. Customer messages, agent replies, owner replies, private guidance, notes and handling changes have distinct labels. Private guidance stays in the thread and does not transfer ownership.
111
+
112
+ Use `frontpage customer-inbox get widget --site my-site` as a human admin to get the script URL, site ID and server-side app identity key. Install the returned script through the normal Frontpage CLI file workflow or your app's template:
113
+
114
+ ```html
115
+ <script defer src="https://app.frontpage.host/customer-inbox/widget.js" data-site="SITE_UUID"></script>
116
+ ```
117
+
118
+ The current scaffold and gateway already allow this HTTPS script and its requests. Installation uses their existing script support; it needs no gateway change, scaffold upgrade or fleet rebuild. The widget stays separate from the site bundle. It appears only when the service accepts that site's activation, rollout gates and approved origin.
119
+
120
+ Update settings using the current `expectedVersion`:
121
+
122
+ ```json
123
+ {
124
+ "expectedVersion": 4,
125
+ "value": {
126
+ "widget": {
127
+ "enabled": true,
128
+ "title": "Message our team",
129
+ "greeting": "How can we help?",
130
+ "color": "#7254d6",
131
+ "requireName": true,
132
+ "requireEmail": true,
133
+ "origins": ["https://my-site.frontpage.host", "https://app.example.com"]
134
+ },
135
+ "ownerCopies": false,
136
+ "helpRules": {
137
+ "humanRequest": true,
138
+ "missingAnswer": true,
139
+ "frustration": true,
140
+ "repeatedFailure": true
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ Use exact HTTPS origins without paths or credentials. This controls cross-origin browser access, not owner permissions. Required intake fields collect details; they do not prove email ownership. The customer must confirm the emailed link before email continuation or authenticated People access. Confirmation rotates the visitor credential. Recovery uses a new email proof and returns no history based on a typed address alone.
147
+
148
+ The agent reads help rules in its support prompt and uses `ask_owner`. Work caps remain server-enforced. Owner copies are private emails of future agent replies. Replying gives private guidance unless the owner explicitly requests takeover. Neither copies nor guidance put owner and customer on a shared CC thread.
149
+
150
+ ### Signed app identity
151
+
152
+ Keep the `identityKey` returned by `widget` on your app server. Never include it in HTML, browser storage or a client bundle. Sign a short-lived assertion after authenticating your app user:
153
+
154
+ ```js
155
+ import { createHmac } from "node:crypto";
156
+ const now = Math.floor(Date.now() / 1000);
157
+ const claims = {
158
+ siteId: process.env.INBOX_SITE_ID,
159
+ origin: "https://app.example.com",
160
+ subject: authenticatedUser.id,
161
+ email: authenticatedUser.email,
162
+ name: authenticatedUser.name,
163
+ iat: now,
164
+ exp: now + 120
165
+ };
166
+ const encoded = Buffer.from(JSON.stringify(claims)).toString("base64url");
167
+ const identity = encoded + "." + createHmac("sha256", process.env.INBOX_IDENTITY_KEY)
168
+ .update(encoded).digest("hex");
169
+ ```
170
+
171
+ The service verifies the signature, site, origin and expiry. Assertions can last at most five minutes. They grant customer identity only, never owner or manager authority. Every app-session request must include the current signed identity matching that session's subject and email. Put a server-rendered assertion in `data-identity`, or preferably provide `data-identity-url="/api/support-identity"`. That same-origin HTTPS endpoint authenticates the current app user and returns `{"identity":"<signed assertion>"}`. The widget refreshes it; after logout or account changes, old app sessions cannot be read without the matching identity. Anonymous widgets need no assertion. Each approved origin has its own visitor session; email continues the bound conversation.
172
+
173
+ ## Conversation state, exports and shared usage
174
+
175
+ | Resource | Method | Body or behavior |
176
+ | --- | --- | --- |
177
+ | `conversations?state=active` | GET | Open threads; also `closed`, `trash` or `all` |
178
+ | `conversations/<id>/messages?latest=true&limit=50&audience=all` | GET | Latest page in message order |
179
+ | `conversations/<id>/messages?before=<sequence>&limit=50&audience=all` | GET | Earlier page; do not combine with `after` or `latest` |
180
+ | `conversations/<id>/state` | POST | `{"action":"close","expectedVersion":0}`; actions: `close`, `reopen`, `trash`, `restore`, `read` |
181
+ | `conversations/<id>/state` | POST | Mark read with current state version and `"sequence":42` |
182
+ | `conversations/<id>/revoke-sessions` | POST | `{}`; revoke existing visitor credentials |
183
+
184
+ Trash retains history and file references. Restore reopens the thread; it does not erase prior handling decisions. State writes reject stale versions. Closing cancels queued sends and refuses unresolved provider outcomes. New customer input reopens a closed thread; trashed threads require owner restoration. Visitor revocation also disables recovery of that session.
185
+
186
+ Export the full authorized history and file manifest, one JSON record per line:
187
+
188
+ ```sh
189
+ frontpage customer-inbox export CONVERSATION_UUID --out ./conversation.jsonl --site my-site
190
+ frontpage customer-inbox download FILE_UUID --out ./attachment.png --site my-site
191
+ frontpage usage --site my-site --json
192
+ ```
193
+
194
+ Export uses paginated reads rather than loading all messages into memory. Download uses an authorized file grant. Both commands refuse to overwrite an existing output file. Full private-history export and file access require human editor/admin authority. Viewers can read public history through the normal message resource.
195
+
196
+ Inbox sends count in the site's existing email allowance. Provider-accepted send receipts settle that usage once, including private owner notifications and copies. AI calls debit the site's existing credit ledger at 10× cost, with token counts unchanged. Storage uses the existing Blob scanner and counts actual attachments, originals and previews; conversation text adds no separate meter.
197
+
198
+ ## File custody and cleanup
199
+
200
+ Email capture keeps the original MIME message, attachments and provider receipt. Email and chat image uploads keep the original file and a smaller agent preview. Repeated upload/message receipts retain the same file/message IDs. Validated raster customer images up to 12 megapixels use the existing public bucket. Larger or undecodable files remain downloadable and require human review. Other files and private mail originals use authenticated downloads over encrypted bytes in that same bucket. No Blob firewall changes or separate bucket are part of this release.
201
+
202
+ The existing backup process enumerates the whole bucket. The existing reaper scans Inbox files in bounded pages and asks Inbox for exact retained references; it does not read message bodies. Unknown reference results protect Inbox files while legacy cleanup continues. Live history, Trash, capture holds and original/preview links retain their files. The existing archive/restore/purge coordinator claims a file in Inbox before changing bytes. A claim blocks new links and remains held after an unknown outcome. Accepted email custody, message links, Trash and capture holds protect files; abandoned chat uploads can follow the existing grace/archive rules. Unknown file records stay protected. Site finalization freezes Inbox writes before moving its bytes through the same archive process. Broader shared-reaper performance work remains a later phase.
203
+
204
+
205
+ On app logout or account change, call `window.dispatchEvent(new Event("frontpage-inbox:reset"))`. This clears the displayed history, stored chat credential and pending input. The next request fetches a fresh assertion from `data-identity-url`; authenticated app sessions require the same current app identity on every request. Do not use a long-lived static assertion for an app with account switching. Verification returns to the same origin and the stored page path. Query strings and arbitrary redirect URLs are not accepted.
206
+
207
+ `jobs/<id>` reads one human-visible recovery state. Retry only the documented recoverable pauses using its current `updatedAt`; invalid screening and unknown sends require human review. An unknown shared-media operation keeps its `claimId`, `claimAction` and `lifecycleVersion`. Operators verify live/archive byte hashes and the existing archive row before acknowledging the matching service-only `/media-finish` operation. Never clear a claim solely because time passed. These endpoints accept only the `inbox-media` service actor; owner keys and website managers cannot submit them.
208
+
209
+
210
+ ## Support agent tool contract
211
+
212
+ The support agent has its own system prompt plus the versioned private business instructions. Tool calls stay in Inbox's tenant, conversation, handler, configuration and job context. All ten tools start enabled; `wait` and `finish` cannot be removed. A disabled tool fails at the server.
213
+
214
+ | Tool | Inputs | Effect and limits |
215
+ | --- | --- | --- |
216
+ | `knowledge_read` | `query`, `id` or `sourceId` | Search internal notes or read complete approved evidence. |
217
+ | `knowledge_update` | Optional `id`; `title`, `body`, `expectedVersion`, `reason`, `evidenceKind`, `evidenceId`; source `evidenceVersion` | Update unlocked internal facts from verified guidance in this thread or an approved source revision. One-off guidance stays in the conversation. No public editing. |
218
+ | `customer_read` | None | Read only this thread's verified People contact. |
219
+ | `customer_update` | `name`/`phone`, `evidenceMessageId` | Enrich from an explicit customer statement; cannot change identity or permission. |
220
+ | `notes_read` | None | Read factual People notes for the bound contact. |
221
+ | `note_add` | `text` | Save a private factual action note in People, with an idempotent receipt. Do not store hidden reasoning. |
222
+ | `ask_owner` | `question` | Save the private question in the thread, email the verified configured owner and pause. Guidance does not take over. |
223
+ | `reply_customer` | `text` | Save a public answer and queue email to the bound verified customer when available. Never choose recipients. |
224
+ | `wait` | `reason` | Pause safely without sending. |
225
+ | `finish` | None | Complete this bounded job. |
226
+
227
+ Decisions screens malicious customer input and interprets authenticated owner-email intent. It never chooses IDs, recipients, permissions, business answers, support tools or knowledge changes. Owner intent uses bounded labeled context, one primary Luna call, at most one bounded same-Luna control fallback, and the same exact-token, raw-cost, 10× ledger receipt as support. Shared inference utilities provide the existing same-model credential/transport fallback; no expensive model substitution or customer model picker is added.
228
+
229
+ The installed widget loads two public static scripts: `/customer-inbox/widget.js` and `/customer-inbox/view.js` on `https://app.frontpage.host`. The shared renderer contains no session, owner data or credentials. If your app sets a Content Security Policy, allow those script URLs and connections to `https://frontpage-inbox-api.fly.dev`; keep the existing widget origin and identity checks. Owner Inbox pages remain authenticated.
230
+
231
+ ## Live updates and email continuation
232
+
233
+ The workspace uses a short authenticated stream through its cookie bridge; the installed widget uses authenticated fetch with its existing visitor/app-identity headers. Streams carry empty invalidation hints, never message text, private counters, recipients or file metadata. Clients re-read their authorized history/status. One restricted Postgres listener per Inbox process wakes the existing outbox publisher and fans out hints. Notifications are best-effort; durable rows, leases, recovery scans and receipt reconciliation remain authoritative. Disconnected clients refresh and use bounded fallback polling. No token enters a stream URL, and each short stream reauthorizes on reconnect.
234
+
235
+ A new unverified chat with an email requests a generic verification email automatically. It does not send private history or assume that typing an address proves identity. The owner can still answer in chat while the visitor verifies. Confirmation rotates the session and binds future public replies to that email; if the latest customer input already has an unsent public answer, Inbox queues that one answer once. It does not mail a backlog or an answer that predates a newer customer question. Verified replies mirror to email whether the page is open or closed; no presence-tracking service is needed.
236
+
237
+ The workspace shows customer name/email first, latest public message in each row, and a copyable reference in conversation details. Conversation list and history scroll independently inside a bounded viewport. Settings use shared workspace controls and a presentation-only preview built from the same renderer as the installed widget. Preview changes send no email or model request. Internal note history/locks/revert stay on the note's own page.
package/PAYMENTS.md ADDED
@@ -0,0 +1,334 @@
1
+ # Payments reference
2
+
3
+ Frontpage keeps one Stripe Express account per site. Store owns products, cart
4
+ checkout, fulfillment, buyer service fees, receipt selection and orders. Payments
5
+ owns Stripe setup, tax, payment activity, standalone Pay now and merchant invoices.
6
+ Frontpage subscription billing stays separate.
7
+
8
+ Use `frontpage help payments --json` for discoverable command help. Normal
9
+ `--site`, authentication, `--json`, `--quiet` and exit-code rules apply. The API
10
+ requires Scale, Enterprise or Internal CLI access. This does not remove product
11
+ permissions or invoice entitlements.
12
+
13
+ ## Access and output
14
+
15
+ | Operation | Minimum role | Additional rule |
16
+ | --- | --- | --- |
17
+ | Connection status | Viewer | Site membership |
18
+ | Private activity, invoices, arrangements, tax and Pay now reads | Editor | Site membership |
19
+ | Existing tax settings and registration writes | Editor | Explicit supplied jurisdiction/address and confirmation |
20
+ | Stripe browser setup, payment availability, Pay now writes, invoice sends and billing links | Admin | Confirmation where specified |
21
+ | New invoice, new arrangement or resume | Admin | Growth or higher and Payments ready |
22
+ | Historical reads, refresh, void and future cancellation after downgrade | Existing role above | No new invoice entitlement required |
23
+
24
+ All routes authorize the API-key owner against the requested site. A different
25
+ site's record returns 404. Request fields cannot grant a role, change the trusted
26
+ site or choose a connected account. JSON success uses the existing CLI envelope
27
+ `{ "ok": true, "command": "payments", "data": { ... } }`. Failure uses the
28
+ existing error envelope with request ID and exit code. Human mode prints the
29
+ returned fields, including partial outcomes. A successful HTTP request does not
30
+ mean a payment settled or an email arrived.
31
+
32
+ ## Setup, availability and history
33
+
34
+ ```sh
35
+ frontpage payments status
36
+ frontpage payments settings --json
37
+ frontpage payments connect --no-open
38
+ frontpage payments availability preview --json
39
+ frontpage payments availability off --yes
40
+ frontpage payments availability on --yes
41
+ frontpage payments activity list --json
42
+ frontpage payments activity get <record-uuid> --source order
43
+ ```
44
+
45
+ Connect returns the existing signed-in admin browser URL. Without `--no-open`,
46
+ human mode opens it. The CLI does not accept Stripe terms, finish onboarding or
47
+ create another account to get around a restriction. Recheck `status` after the
48
+ browser flow. Account existence or a return URL alone does not prove readiness.
49
+ Currency comes from Stripe and is read-only.
50
+
51
+ Payments off first blocks new charges, then stops recurring billing and voids
52
+ unpaid issued invoices. It preserves paid history and the Express account. Review
53
+ the counts with `availability preview`. `off` reads current counts before its
54
+ confirmed write; a changed set returns 409. A bounded request may return
55
+ `pause.complete:false`, remaining counts and failed record IDs. New charging stays
56
+ blocked. Resolve errors and repeat `off`; do not turn Payments on while a pause is
57
+ incomplete. A payment already processing may settle and keeps its real result.
58
+
59
+ Turning Payments on rechecks Stripe readiness. It does not enable Store, restore
60
+ void invoices, resume arrangements or collect missed periods. Store off affects
61
+ only Store; it leaves standalone payments and billing running.
62
+
63
+ Activity combines the latest 200 Store orders and 200 Pay now payments with up to
64
+ 100 invoices and 100 recurring arrangements each. `mayHaveMore` states when older
65
+ records may exist. It preserves captured currencies, amounts and payment states.
66
+ Recurring amounts are per-period amounts before tax. Use invoice/recurring list
67
+ commands for their paginated history. Get an order/payment with its explicit
68
+ source; equal IDs in different tables do not identify the same record.
69
+
70
+ ## Tax
71
+
72
+ ```sh
73
+ frontpage payments tax get --json
74
+ frontpage payments tax settings --file tax-settings.json --yes
75
+ frontpage payments tax registrations list
76
+ frontpage payments tax registrations add --country US --state CA --yes
77
+ frontpage payments tax registrations end <tax-registration-id> --yes
78
+ ```
79
+
80
+ Tax settings JSON contains `country`, `line1`, `city`, optional `state`, `postal`
81
+ and an existing supported `taxCode`. GET returns current choices and registrations.
82
+ Use the business's actual address and registrations. Frontpage does not choose
83
+ jurisdictions or provide tax advice. End uses the exact registration ID returned
84
+ by Stripe. These operations reuse current connected-account tax helpers. They do
85
+ not expose Account Session secrets or a second tax calculator. An invoice preview
86
+ blocks if tax settings cannot be confirmed; it does not silently drop tax.
87
+
88
+ ## Invoice input and review
89
+
90
+ For an existing CRM person:
91
+
92
+ ```json
93
+ {
94
+ "recipient": { "personId": "<crm-person-uuid>" },
95
+ "description": "Website maintenance",
96
+ "amountCents": 12500,
97
+ "kind": "once",
98
+ "collection": "manual",
99
+ "dueDays": 14
100
+ }
101
+ ```
102
+
103
+ To add or match a CRM customer when sending, replace `recipient` with:
104
+
105
+ ```json
106
+ {
107
+ "name": "Customer name",
108
+ "email": "customer@example.com",
109
+ "address": {
110
+ "line1": "Customer billing street",
111
+ "city": "City",
112
+ "region": "CA",
113
+ "postal": "94080",
114
+ "country": "US"
115
+ }
116
+ }
117
+ ```
118
+
119
+ `address` also accepts `line2`. Supply a real billing address when automatic tax
120
+ needs it. A CRM person must be visible and have a valid billing email. Inline
121
+ matching stays within the site and does not grant marketing permission. Preview
122
+ creates no CRM person, Stripe Customer, invoice, invitation or charge.
123
+
124
+ Money fields use integer minor units in the account currency despite the existing
125
+ `Cents` field names. USD 12500 means $125.00; JPY 12500 means ¥12500. The current
126
+ amount range is 50 through 10,000,000 minor units. Description contains 1–500
127
+ characters. `dueDays` is 7, 14 or 30; omitted means 7. A one-time invoice uses
128
+ manual collection and cannot carry recurring date/cadence fields.
129
+
130
+ ```sh
131
+ frontpage payments invoices preview --file invoice.json --json
132
+ frontpage payments invoices send --file invoice.json \
133
+ --preview-hash <hash-from-preview> --operation-key <stable-uuid> --yes --json
134
+ ```
135
+
136
+ Preview returns the normalized recipient/terms, currency, subtotal, tax, total,
137
+ tax status and `previewHash`. When `billingContactChanged` is true, review the CRM email, name and address and pass `--confirm-billing-contact` to update the saved Stripe contact for future billing. This does not rewrite issued invoices or prior consent. Send repeats those exact terms and hash. It rechecks
138
+ readiness and totals before finalization/delivery. Changed terms or totals return
139
+ 409. Common flags can replace simple JSON fields: `--person` or `--email` with
140
+ `--name`, `--description`, `--amount-cents`, `--kind`, `--collection`, `--due-days`,
141
+ `--cadence`, `--first-date`, `--time-zone`. Nested addresses use `--file`.
142
+
143
+ The operation key contains 16–120 letters, digits, dots, colons, underscores or
144
+ hyphens and starts with a letter/digit. A UUID is suitable. Preserve the same key
145
+ and input after a timeout or uncertain result. Do not use a new key to force a
146
+ send. Repeated requests return the same local/Stripe invoice rather than charging
147
+ again. If Stripe's safe retry window has passed, reconcile the saved object first.
148
+
149
+ ## Invoice management
150
+
151
+ ```sh
152
+ frontpage payments invoices list --limit 50 --offset 0 --json
153
+ frontpage payments invoices list --arrangement <arrangement-uuid> --json
154
+ frontpage payments invoices get <invoice-uuid>
155
+ frontpage payments invoices refresh <invoice-uuid>
156
+ frontpage payments invoices resend <invoice-uuid> --operation-key <stable-uuid> --yes
157
+ frontpage payments invoices void <invoice-uuid> --yes
158
+ ```
159
+
160
+ List accepts limit 1–100 and offset 0–100000; it returns `nextOffset` or null.
161
+ Records include immutable recipient/terms, actual currency/subtotal/tax/total,
162
+ paid/remaining amounts, due/issued/paid dates, Hosted Invoice Page/PDF, delivery
163
+ and any last error. Expected platform fee and actual collected fee are separate.
164
+ An unpaid invoice does not claim a collected fee. Source account/setup secrets
165
+ and customer-access nonces are not returned.
166
+
167
+ Resend only accepts an open invoice and uses its existing saved billing customer.
168
+ It never creates a replacement invoice. Void requires confirmation and cannot
169
+ restore an invoice after Stripe confirms it is void. A payment race preserves an
170
+ actual paid result. An unsent draft's cancellation is recorded separately from
171
+ Stripe's real draft state. There is no manual mark-paid, refund or raw charge API.
172
+
173
+ Stripe sends invoice emails and hosts payment/recovery. `delivery:accepted` means
174
+ the send API accepted the request, not that the email arrived or was opened.
175
+ Stripe test mode may suppress email delivery; provider acceptance is the most
176
+ that a test-mode report may claim.
177
+
178
+ ## Recurring manual invoices and autopay
179
+
180
+ Use the same recipient, description, amount and due window. Set `kind:recurring`,
181
+ `cadence`, `firstDate` and `timeZone`. Collection is `manual` or `autopay`.
182
+
183
+ ```json
184
+ {
185
+ "recipient": { "personId": "<crm-person-uuid>" },
186
+ "description": "Monthly maintenance",
187
+ "amountCents": 12500,
188
+ "kind": "recurring",
189
+ "collection": "manual",
190
+ "cadence": "monthly",
191
+ "firstDate": "2026-12-01",
192
+ "timeZone": "America/Chicago",
193
+ "dueDays": 14
194
+ }
195
+ ```
196
+
197
+ Supported cadences are `weekly`, `fortnightly`, `monthly`, `quarterly`, `yearly`.
198
+ The first date is YYYY-MM-DD in an explicit IANA zone. Billing begins at 09:00 in
199
+ that zone. Choose a future date. Stripe owns later native calendar periods,
200
+ including short months/leap years. No Frontpage recurring scheduler, backfill or
201
+ proration is added.
202
+
203
+ New recurring arrangements use Stripe's native platform fee: 2% of the invoice
204
+ total, including tax, unless the existing server fee setting is zero. The saved
205
+ arrangement freezes that server-controlled rate before billing starts. For
206
+ example, a $100 invoice plus $9.88 tax has a $2.20 platform fee. The fee is
207
+ deducted from the business's proceeds, not added as a customer surcharge.
208
+ Stripe's own processing and Billing/Invoicing charges are separate. Store,
209
+ Pay now and one-time invoice fee calculations keep their existing basis.
210
+ Reconciliation records expected and actual fees; it does not set the fee or
211
+ collect recurring payments. Stripe handles collection even if a webhook is late.
212
+
213
+ ```sh
214
+ frontpage payments recurring preview --file recurring.json --json
215
+ frontpage payments recurring create --file recurring.json \
216
+ --preview-hash <hash> --operation-key <stable-uuid> --yes
217
+ frontpage payments recurring list --limit 50 --offset 0 --json
218
+ frontpage payments recurring get <arrangement-uuid>
219
+ frontpage payments recurring refresh <arrangement-uuid>
220
+ frontpage payments recurring cancel <arrangement-uuid> --yes
221
+ frontpage payments recurring resend-invitation <arrangement-uuid> \
222
+ --operation-key <stable-uuid> --yes
223
+ ```
224
+
225
+ Manual collection creates a native future Stripe schedule. It does not issue an
226
+ immediate first invoice. Stripe creates one invoice per period and emails the
227
+ hosted payment link. Autopay create saves a pending authorization and sends an
228
+ invitation. The customer reviews the amount, tax, cadence and first date, agrees,
229
+ and finishes Stripe-hosted setup on the same connected account/customer. Only
230
+ verified setup can start the schedule. A saved method, setup return query or owner
231
+ flag alone never supplies consent. Setup itself is not a payment.
232
+
233
+ Arrangement cancellation stops future billing without changing issued invoices
234
+ or adding a final prorated charge. Payments off pauses arrangements by stopping
235
+ their native future schedule. Only paused arrangements may resume. Preview the
236
+ same recipient/amount/cadence/collection with a new future date, then:
237
+
238
+ ```sh
239
+ frontpage payments recurring resume <paused-arrangement-uuid> \
240
+ --first-date 2026-12-01 --preview-hash <hash-from-preview> \
241
+ --operation-key <stable-uuid> --yes
242
+ ```
243
+
244
+ Resume creates a linked future arrangement with no missed-period invoices. A
245
+ second different resume request is refused. Autopay seeks fresh customer consent.
246
+ Changed terms require cancellation and a new reviewed request; there is no hidden
247
+ edit/proration action. Invitation resend applies only while awaiting authorization.
248
+ It expires/replaces an unfinished old setup and keeps its own retry key.
249
+
250
+ ## Customer billing access
251
+
252
+ ```sh
253
+ frontpage payments customers send-billing-link <customerMapId> \
254
+ --operation-key <stable-uuid> --yes
255
+ frontpage payments customers billing-link <customerMapId> \
256
+ --operation-key <stable-uuid> --json
257
+ ```
258
+
259
+ Use `customerMapId` from the site's invoice/arrangement. It is not a raw Stripe
260
+ customer ID. Send uses the mapped customer's current saved Stripe billing email;
261
+ the caller cannot choose another recipient. Links expire after 30 minutes. A raw
262
+ returned link is sensitive: do not log/share it. The website agent only sends
263
+ links; it cannot request raw customer access.
264
+
265
+ Customer access needs no owner login. It stays scoped to site/account/customer
266
+ and uses a small customer page plus the connected-account Stripe portal. The
267
+ customer can pay an invoice, change the saved method, cancel future billing or
268
+ choose manual payment for future invoices. These actions do not pay an existing
269
+ balance, authorize another arrangement or expose Frontpage subscription billing.
270
+ Authorization invitations expire after 24 hours; an expired link requires a new
271
+ invitation. A completed authorization exposes Manage billing.
272
+
273
+ ## Standalone Pay now
274
+
275
+ ```sh
276
+ frontpage payments buttons list --limit 50 --offset 0 --json
277
+ frontpage payments buttons get consultation
278
+ frontpage payments buttons create --id consultation --title Consultation --price-cents 5000
279
+ frontpage payments buttons update consultation --price-cents 7500
280
+ frontpage payments buttons update consultation --file button-details.json
281
+ frontpage payments buttons archive consultation --yes
282
+ ```
283
+
284
+ These reuse current item/variant pricing and visitor `/__pay`. Creation registers
285
+ the exact ID atomically with the item in existing Store settings; it returns
286
+ markup for an explicit `data-fp-pay` button. Existing standalone references come
287
+ from the site's authorized markup. A Store card/CTA, no-shipping flag or old
288
+ instant setting is not proof of Pay now ownership. No new button table or fleet
289
+ conversion exists. Price updates are separate from detail updates. Open amount
290
+ creation uses `--amount open --min-cents N` and a minimum variant price. Item IDs
291
+ remain existing 2–40-character kebab IDs. Lists are bounded with `nextOffset`.
292
+
293
+ ## REST paths
294
+
295
+ All paths below use `/api/v1/sites/{slug}/payments`. GET query fields match the
296
+ documented filters. POST/PATCH fields match the reviewed domain inputs above.
297
+ Financial consequences need `confirm:true`; stable retryable sends additionally
298
+ need `operationKey`. This is a resource API, not arbitrary action dispatch.
299
+
300
+ | Method | Resource | Result |
301
+ | --- | --- | --- |
302
+ | GET | `/status`, `/settings` | Safe current connection/readiness and read-only currency |
303
+ | POST | `/connect` | Signed-in admin browser handoff |
304
+ | GET / POST | `/availability` | Pause counts/hash or confirmed availability outcome |
305
+ | GET | `/activity`, `/activity/{order\|payment}/{id}` | Current source-aware activity/detail |
306
+ | GET | `/tax`, `/tax/registrations` | Existing tax state/options/registrations |
307
+ | POST | `/tax/settings`, `/tax/registrations` | Existing tax settings/registration write |
308
+ | POST | `/tax/registrations/{id}/end` | End the specified registration |
309
+ | GET / POST | `/buttons` | List / create an explicit Pay now item |
310
+ | GET / PATCH | `/buttons/{id}` | Read / update that explicit Pay now item |
311
+ | POST | `/buttons/{id}/archive` | Confirmed archive |
312
+ | POST | `/invoices/preview`, `/invoices/send` | Read-only review / send |
313
+ | GET | `/invoices`, `/invoices/{id}` | Paginated invoices / detail |
314
+ | POST | `/invoices/{id}/{refresh\|resend\|void}` | Reconcile, resend or confirmed void |
315
+ | POST | `/recurring/preview`, `/recurring` | Read-only review / create request |
316
+ | GET | `/recurring`, `/recurring/{id}` | Paginated arrangements / detail |
317
+ | POST | `/recurring/{id}/{refresh\|cancel\|resume\|resend-invitation}` | Scoped lifecycle action |
318
+ | POST | `/customers/{customerMapId}/{billing-link\|send-billing-link}` | Private customer access |
319
+
320
+ Common failures: 400 invalid input/retired write; 401 no authentication; 403 role,
321
+ CLI tier or product entitlement; 404 wrong site/record; 409 stale review, conflict,
322
+ unfinished pause or unknown provider outcome; 429 existing rate limit; 503 missing
323
+ rate-limit service, tax-read failure or provider failure. Keep the request ID when
324
+ reporting a failure. Never retry a financial send with a new key just because an
325
+ HTTP response was lost.
326
+
327
+ ## Breaking release
328
+
329
+ Version 0.2.0 removes Store `connect/status/disconnect/payments`, writable currency
330
+ and checkout mode from CLI/public API/agent contracts. Use `payments` for money
331
+ operations. Store remains cart-only and keeps existing resource paths under
332
+ `/shop`. Saved products/orders, Express accounts, current visitor hooks,
333
+ `/__pay`, Connect webhooks and old browser callbacks remain intact. Old binaries
334
+ may fail on retired endpoints; no compatibility alias or data conversion runs.