Use Topline
Use Topline: Customer Profiles
Enable and operate reusable PestRoutes customer profiles with observed-history capture, freshness, pricing, API, MCP, and cost boundaries.
Customer profiles
Customer profiles are an opt-in, Topline-hosted projection of a customer's PestRoutes account. A missing customer_profile_products row and a row with status = 'disabled' mean the same thing: no profile reads, backfills, incremental work, schedules, or model calls are started.
Product modes
on_demandactivates the product without starting work. The first refresh/backfill for a customer creates its baseline.portfolioqueues an initial portfolio backfill and provisions two tenant-bound schedules: a five-minute capture poll and a separate synthesis schedule.- Capture uses the server-owned
*/5 * * * *cron. Daily synthesis uses2 2 * * *; priority synthesis uses2,7,12,17,22,27,32,37,42,47,52,57 * * * *. The two-minute offset gives capture the first chance to commit and the active-run guard prevents overlapping work. - Activation accepts
synthesisCadence = daily | five_minutesand an IANA timezone. Arbitrary cron expressions are rejected because capture fidelity and cost are product policy, not per-tenant scheduler input. - Pause disables both schedules before changing product state. Resume enables both and queues an immediate incremental synthesis. History and immutable profile versions remain available while paused.
deterministic_onlyis the supported generation mode. The schema reservesvertex_flex, but the API rejects it until the hosted Vertex worker and commercial rate are configured. Standard-priority fallback is never implicit.
Customer Profiles is never enabled by default. An authorized Topline operator must select one PestRoutes source, configure the effective customer price or non-billable pilot state, acknowledge the estimate, and explicitly enable the product. Enabling portfolio mode starts the initial backfill; a price edit by itself starts no work.
Capture and synthesis flow
Capture and synthesis are intentionally separate:
- The existing PestRoutes connector mirrors source rows into the tenant's reporting database. Customer Profiles does not bypass that connector or make a second direct vendor API claim.
- Every five minutes, the capture run compares supported mirrored rows with the latest committed customer-history state. Changed customer IDs are discovered without rereading every customer.
- For each changed customer, capture appends idempotent
created,updated,hard_deleted, orreactivatedhistory events. Capture does not rebuild the profile. - Synthesis selects only customers whose newest history event is beyond the profile's
currentEventId. It reads the customer's current model-safe projection inputs and advances the profile cursor after a successful projection. - API, chat, and MCP consumers reuse the stored profile plus a bounded activity window. They do not reprocess all PestRoutes rows for each generation.
The five-minute value is Topline observation fidelity, not database CDC and not a guarantee that every intermediate vendor state is preserved. If a row changes twice between mirror refreshes or between capture polls, only the state Topline actually observes can be recorded. Connector lag therefore remains visible operational evidence, and lastSuccessfulCaptureAt is a poll watermark rather than proof that PestRoutes itself was current.
The initial backfill establishes the currently observed baseline. Capture and synthesis share a tenant-level active-run guard, so a long backfill may defer scheduled capture; a later capture catches the latest observed state but cannot reconstruct unobserved intermediate states. Activation and run monitoring must surface this limitation rather than claiming complete pre-activation or vendor-level history.
Admin commercial activation
The target operator surface is a Customer Profiles card on each customer's Topline Admin detail page. The card may present one concise form, but entitlement, processing policy, and pricing are independent controls:
- Product entitlement: disabled, on-demand, or portfolio. Saving pricing must not start processing. Enabling processing must be a separate confirmed action.
- Generation policy: deterministic-only or Vertex Flex. Standard-priority fallback is a separate opt-in and cannot be inferred from Flex selection.
- Customer price policy: one effective-dated price per active PestRoutes customer per month. It records currency, unit price, pricing version, effective date, and approving operator. V1 has no tiers, included allowance, minimum, overage, proration, or customer-facing model-usage markup.
- Operational state: source readiness, worker readiness, initial estimate, backfill state, last successful sync, last error, and billing reconciliation state.
The recommended Admin presentation is:
- A top-level Product mode control with
Off,On demand, andPortfoliochoices. - A Customer price summary beside it, shown as
$X per active customer / month, with an Edit pricing action for the versioned rate details. - A Profile refresh selector with
Dailyand a separately pricedEvery five minutesoption. Both portfolio choices capture observed changes every five minutes; the selector controls how quickly those events are synthesized into the reusable profile. - A Generation selector showing
DeterministicorVertex Flex, plus a separately confirmed standard fallback option when supported. - A preview panel showing estimated active-customer count, unit price, estimated monthly customer charge, current pricing version, internal estimated/unknown costs, schedule, and initial-backfill impact.
- A final Enable and start backfill confirmation. Price edits alone never invoke this action.
An Admin enable action must fail closed unless exactly one active PestRoutes source is selected, the customer price policy is approved and effective (or the customer is explicitly recorded as a non-billable pilot), the requested provider route is ready, required queues/schedules are healthy, and the estimate has been acknowledged. Price changes create a new effective-dated version; they do not retroactively reprice earlier usage. Pause stops new profile work without deleting history or changing historical billing records.
The initial commercial model applies to portfolio mode. For each billing month, Topline records one invoice-close snapshot of distinct PestRoutes customers where status = 1 and _source_hard_deleted_at is null, using the latest successful profile/source sync available for that billing cutoff. The customer charge is exactly active_customer_count × unit_price_per_active_customer_month. The snapshot must retain its count, as-of time, source/profile run evidence, unit price, pricing version, and calculated charge. If a trustworthy snapshot is unavailable, billing remains pending/unpriced rather than treating the count or charge as zero. on_demand remains a pilot/operational mode until it receives a separate commercial policy.
This add-on sits beside Topline's flat platform fee, AWS pass-through, and future Chat Member/Build seat tiers. AWS allocated into internal feature cost must not be billed twice, and the flat fee must not silently include Customer Profiles unless an effective-dated tier says so. When a tier does include the add-on, the invoice snapshot records that inclusion and suppresses the separate profile line.
Enabling Vertex Flex
Vertex Flex cannot be enabled merely by selecting it in Admin. It becomes selectable only after all of the following are implemented and verified:
- The customer-profile synthesis worker uses the existing AgentCore-to-Google WIF path; no GCP service-account keys, storage, queues, or compute are added.
- The exact Gemini model and Flex service tier are supported by the runtime and produce complete provider request IDs and token-usage evidence.
- The customer pricing policy has an effective per-active-customer monthly price and pricing version.
- Internal model usage records tenant, profile run/customer, provider, model, service tier, routing type, tokens, provider cost, pricing version, and reconciliation status. These costs inform Topline margin but do not create a separate customer overage in V1.
- A bounded canary proves profile output, tenant isolation, PII minimization, usage attribution, and billing reconciliation.
- The Admin preview and confirmation flow is available, and separate deployment/customer-rollout authorization has been granted.
Until those gates pass, Admin must display Vertex Flex as Unavailable — setup required, and the API continues returning customer_profile_vertex_flex_not_available. It must never silently fall back to standard-priority inference.
History contract
The durable history is baseline plus observed changes, not a claim that PestRoutes exposes every historical version that existed before activation.
- Initial backfill writes one
baselineevent for every currently observed source row. - Later reads compare canonical row hashes and append
created,updated,hard_deleted, orreactivatedevents. - The event identity includes
observed_at, so repeated state cycles such as A → B → A are retained while a retry of the same observation remains idempotent. - Every supported materialized PestRoutes table receives
_source_row_hash; this supplies change detection whendateUpdatedis absent or unreliable. - If a mirrored record moves from one customer relationship to another, capture writes a Topline-observed
hard_deletedassociation event for the former customer and acreatedevent for the new customer. This prevents the old profile from silently retaining the moved object. - Full source payloads remain in
customer_history_events. Profile sections contain only allowlisted model-safe fields. Full event payload reads require the separately grantedcustomer_profiles.history.payload.readcapability.
Portfolio capture discovers changed customer IDs in Postgres by comparing each mirrored row with the latest committed history event. Only those customer IDs are dispatched. Synthesis then selects customers with captured events beyond their profile cursor. Each run records afterEventId and throughEventId; a truly unchanged capture is a guarded no-op with no new history event or profile version.
customer_flags is intentionally reported as unavailable. Its current catalog lacks dateUpdated, and its documented response/upsert identity has not been proven collision-free. The separate investigation ticket must repair and certify that connector contract before it can participate in the profile.
generic_flag_assignments is included for customer (CUST), subscription (SUBS), and appointment (APPT) relationships and is joined to generic_flags. Both tables expose dateUpdated. The catalog's EQAS relationship has no verified customer join, so source.generic_flag_assignments_eqas is explicitly unavailable rather than guessed. appointment_reminders is joined through appointments; raw destinations, reminder text, and response text are omitted from profile sections, while channel, status, timing, and response-presence facts remain available.
Profile contract
Each profile is a versioned JSON document with appendable sections:
{
"schemaVersion": 1,
"identity": {
"tenantId": "tenant-id",
"sourceId": "source-uuid",
"externalCustomerId": "1001"
},
"status": "ready",
"historyCoverage": {
"mode": "baseline_plus_observed_changes",
"historyStartedAt": "2026-08-29T10:00:00.000Z",
"completeObservedChangesSince": "2026-08-29T10:00:00.000Z"
},
"coverage": {
"included": ["customers", "appointments"],
"unavailable": [{ "resourceName": "customer_flags", "reason": "source_identity_contract_unverified" }]
},
"sections": {
"source.customers": {},
"source.appointments": {},
"synthesis.communication_history": {},
"synthesis.flags": {},
"synthesis.summary": {}
},
"asOf": "2026-08-29T12:00:00.000Z"
}
Source sections use the stable source.<table> namespace. New source or synthesis sections can be appended without changing existing keys. A profile version advances only when model-safe section content changes; routine source fetch timestamps do not invalidate it. The complete immutable snapshots live in customer_profile_versions, while customer_profile_sections supports targeted regeneration and reads.
Customer API
All routes require a tenant-bound browser session and the named capability. Mutations also require an Idempotency-Key header.
| Method | Path | Capability | Purpose |
|---|---|---|---|
GET | /api/customer-profile-product | customer_profiles.read | Read disabled/active product state and schedule policy. |
GET | /api/customer-profile-product/economics | feature_economics.view | Read current-month or bounded-range tenant totals and per-customer usage/cost coverage. This internal Topline optimization view is not granted to customer admins by default and is not an invoice. |
POST | /api/customer-profile-product/estimate | customer_profiles.manage | Inspect customer count and pricing availability before activation; unknown storage/compute and customer charges remain null. |
POST | /api/customer-profile-product/actions/enable | customer_profiles.manage | Explicitly enable on-demand or portfolio mode. |
POST | /api/customer-profile-product/actions/pause | customer_profiles.manage | Pause the product and recurring schedule. |
POST | /api/customer-profile-product/actions/resume | customer_profiles.manage | Resume and trigger a portfolio incremental run. |
POST | /api/customer-profile-backfills | customer_profiles.manage | Start one customer baseline or a portfolio backfill. |
GET | /api/customer-profile-backfills/{runId} | customer_profiles.read | Read backfill/run state and bounded counters/cost fields. |
POST | /api/customer-profiles/{customerId}/refresh | customer_profiles.refresh | Start initial baseline or incremental refresh for one customer. |
GET | /api/customer-profiles/{customerId} | customer_profiles.read | Read the current profile with ETag/304 support. |
GET | /api/customer-profiles/{customerId}/context | customer_profiles.read | Read the reusable profile, freshness watermarks, and bounded model-safe recent activity. Accepts recentHours (1–168, default 24) and limit (1–100, default 50), with ETag/304 support. |
GET | /api/customer-profiles/{customerId}/activity | customer_profiles.history.read | Read only the bounded model-safe activity window with the same recentHours and limit bounds. |
GET | /api/customer-profiles/{customerId}/sections | customer_profiles.read | List current sections. |
GET | /api/customer-profiles/{customerId}/sections/{sectionKey} | customer_profiles.read | Read one section. |
GET | /api/customer-profiles/{customerId}/versions | customer_profiles.read | List immutable profile versions. |
GET | /api/customer-profiles/{customerId}/history | customer_profiles.history.read | Read a stable, signed-cursor history page. |
History payloads are excluded by default. includePayload=true additionally requires customer_profiles.history.payload.read. History pagination fixes a through event ID in the signed cursor so concurrent syncs cannot reorder a traversal.
The context and activity endpoints are optimized for generation, not bulk export. Their activity window ends at the latest captured event so an unchanged response and ETag remain stable as wall-clock time advances. Results are newest-first, limited to seven days and 100 events, and each event contains allowlisted facts capped at 4 KiB. Sensitive reminder destination, text, and response fields are not returned. Use the paginated history endpoint for a complete Topline-observed traversal; use its payload option only for explicitly authorized investigations.
An enable request has this policy shape:
{
"sourceId": "pestroutes-source-uuid",
"mode": "portfolio",
"generationMode": "deterministic_only",
"synthesisCadence": "daily",
"syncTimezone": "America/Denver"
}
syncCron is accepted only when it exactly matches the server-derived cadence and should normally be omitted. This keeps the Admin toggle and hosted API on the same supported policy.
Workspace MCP
An enabled tenant can grant the Workspace MCP scope customer_profiles:read. Tool discovery then exposes:
get_customer_profile: requires the operator capabilitycustomer_profiles.readand returns the reusable profile, explicit freshness watermarks, and bounded recent activity.get_customer_activity: requirescustomer_profiles.history.readand returns the bounded model-safe activity window without raw source payloads.
The OAuth scope never creates entitlement and never widens the signed-in operator's tenant capabilities. If the product is disabled, the customer is unknown, the scope is absent, or the operator lacks the capability, the tool fails closed. Full event payloads are not exposed through these MCP tools.
Storage and cost accounting
customer_profile_productsowns tenant activation, source binding, mode, both schedule links, synthesis cadence, capture/synthesis watermarks, and generation policy.customer_profile_runsis the auditable job ledger. Capture, synthesis, and manual/backfill work record separate delta rows, projection rows, customers discovered, and customers processed. Provider cost and customer charge are distinct nullable fields; unknown values are not represented as zero.feature_economics_eventsrecords capture/synthesis discovery, attempted customer work, guarded no-ops, duration, rows/events/sections, model activity, and nullable cost evidence. Empty polls are measurable without creating per-customer metric dimensions. Portfolio work is attributable both to the Topline client and, when customer work occurs, to the source customer that caused it.customer_history_eventsis append-only.customer_profiles,customer_profile_sections, andcustomer_profile_versionsare reusable projections for future generations.customer_signals.inferencescontains only a small compatibility facade pointing to the latest profile; Topline Postgres remains the source of truth.- Fetching a selected customer context resolves that facade to the ready stored profile (bounded to 32,000 characters), so future generations reuse the projection instead of re-reading PestRoutes rows.
Fleet metrics use Environment, Feature, Operation, and Outcome only. Tenant and customer IDs stay outside CloudWatch dimensions and are queried from the durable ledger. Infrastructure and total Topline costs initially remain unpriced until an approved allocation/reconciliation process supplies them; deterministic model cost can independently be known as zero. See Feature economics for the shared contract intended for Customer Profiles, main chat, and connectors.
This guide and code availability do not activate the product. Each tenant remains disabled until an authorized product action succeeds, and deployment, commercial availability, tenant enablement, and customer rollout are separately controlled operations.