Skip to main content

Qontak One | Billing | Integrate User Quota to Quota Management API

Product Requirements Document · NEW PRD v1.0


HEADER BLOCK

FieldValue
PMAddo Hernando
PRD Version1.2
StatusDRAFT
PRD TypeNEW
EpicBIF-8713
SquadBifrost
RFC LinkTBD — pending PRD approval
Figma MasterN/A — no new UI screens
AnchorNo — standalone, single-squad
Labelsepic:qontak-one | module:billing | feature:user-quota-integration
Last Updated2026-06-23

Table of Contents


3. One-liner + Problem

One-liner: Integrate Launchpad's user management with the centralized Quota Management API to enforce real-time quota checks, seat deductions, and refunds for all Qontak One clients globally.

Problem: Today, user seat limits in Launchpad are not connected to any centralized quota enforcement system, meaning a client admin can invite users beyond their purchased seat count without any real-time check or block. With the transition to Qontak One, quota tracking must be standardized across the platform to ensure a single source of truth for user seat consumption and prevent revenue leakage. Without this integration, there is no idempotent transaction tracking for user seat events, no automated balance recovery when users are deleted, and no mechanism to keep consultant provisioning from consuming client-paid quota.

Existing quota logic: Launchpad already has a validate_user_quota.go billing service that calls the Modpanel GetUnifiedBilling API to check seat counts before user creation. This integration replaces that Modpanel-based check with direct calls to the centralized Quota Management API in qontak-billing, giving Bifrost a single authoritative ledger for all quota transactions.


4. Target Users + Persona Context

PersonaRoleGoalPainWorkaround
Primary — Qontak One Client AdminAdmin user of a Qontak One account managing team membersInvite users up to their purchased seat limit and have the system block attempts beyond that limitNo real-time enforcement exists — can invite users beyond the purchased quota, leading to billing discrepancies discovered only after the factManually tracks seat count against the account's plan limit; contacts support when discrepancies appear
Secondary — Activation Team MemberInternal Mekari staff provisioning Consultant Mode access for clients via ModpanelAccess a client's Qontak One account for setup without consuming the client's purchased user seatsConsultant Mode accounts currently have no explicit bypass mechanism — provisioning them risks counting against the client's seat quotaCommunicates manually with the client to track and reconcile any over-counted consultant seats

(See Constraints for plan availability and feature flag scope.)


5. Non-Goals

  1. This does not expose the quota balance or remaining seat count in the Launchpad client-facing UI — balance visibility is owned by the Modpanel/billing layer.
  2. This does not enforce quota checks for any resource other than user seats (e.g. WhatsApp channel quota, message quota, storage quota are out of scope).
  3. This does not block an admin's access to the invite form before the check — the check happens at creation time, not at form load.
  4. This does not implement real-time push notifications or emails to admins when quota is approaching the limit or has been exceeded.
  5. This does not handle automatic quota top-up or upgrade prompts beyond displaying a "Quota Exceeded / Upgrade Required" message on block.
  6. This does not retroactively reconcile user counts for CIDs that were over-provisioned before this integration goes live — the backfill script only syncs accurate current usage forward into the quota system.
  7. This does not apply to non-Qontak One plans — standard Qontak clients are unaffected.

Scope Changes

  • Backend — Launchpad user creation flow (pre-creation quota check + post-creation deduction), user deletion flow (refund call), Consultant Mode bypass logic, background retry worker for deduction failures, and one-time backfill rake task

6. Constraints

FieldValue
PlatformBackend only — no new UI screens
Tech stackLaunchpad: Go 1.23+, PostgreSQL via SQLC, Redis, gocraft/work job queue. Quota Management API: Go 1.23+, same stack in qontak-billing. Moderator-be (Modpanel): Ruby 3.3 / Rails 7.2.
PerformanceQuota check and deduction calls must not block user creation UX for > 500ms (engineering to validate in RFC)
ConcurrencyLaunchpad already serialises concurrent invitations for the same CID via Redis pessimistic lock lock:user_invite:{company_id} (10 s TTL) — quota check will execute inside this lock
Data limitsunique_code for deduction: create_user_{user_id}; for refund: delete_user_{user_id} — idempotency enforced by Quota Management API via billing_unique_logs table
Plan scopeQontak One clients only — not applicable to standard Qontak plans
Feature flaguser_quota_integration_enabled | default: OFF. Enabled per CID via qontak-preferences service; checked using IsEnabledFunc(ctx, { FeatureName, UniqueID: companySsoID }) before any quota API interaction.
AuthenticationQuota Management API — check and deduction calls require Bearer JWT token + X-Api-Key header. Refund calls require X-Api-Key header only. Launchpad must be provisioned with a service account JWT and internal API key.
Read/writeSystem calls Check API before creation. System calls Deduction API on successful creation. System calls Refund API on deletion. Background gocraft/work job retries failed deductions. Clients cannot directly call Quota Management API.
Existing quota validationinternal/app/service/billings/validate_user_quota.go in Launchpad (Modpanel-backed check) must be replaced or bypassed for Qontak One CIDs once this integration is live. Both must not run simultaneously for the same CID.

7. API & Webhook Behavior

Quota Management API base path: POST /iag/v1/quota-managements/{operation} All three endpoints are in qontak-billing. billing_code, deduction_code, and refund_code values specific to user seats are TBD — see Open Questions §15 items 7–9.


Behavior 1: Check Quota Balance before user creation

FieldDetail
Entity affectedQuota balance record for a Qontak One company
Triggered byClient admin initiates a user invitation in Launchpad (POST /iag/v1/users/sso_invite) — inside the existing Redis lock lock:user_invite:{company_id}
EndpointPOST /iag/v1/quota-managements/check-quota
AuthBearer JWT (service account) + X-Api-Key header
Request body{ "company_id": "<company UUID — see OQ-9>", "billing_code": "<TBD — user-seat billing code>", "extra_attrs": { "expectation_deduction": {} } }
Responseis_sufficient is returned inside extra_attrs: { "extra_attrs": { "is_sufficient": true|false, "is_unlimited": bool, "quota_info": { "total_remaining_credit_quota": float64, "total_remaining_balance_quota": float64 } } }
Expected behavior• If extra_attrs.is_sufficient: true: user creation flow proceeds normally
• If extra_attrs.is_sufficient: false: user creation is blocked; UI shows "Quota Exceeded / Upgrade Required" message
• If extra_attrs.is_unlimited: true: treat as sufficient regardless of balance
Failure behavior• If Quota Management API is unreachable or returns 5xx: allow with logging (resolved OQ-1 — see §15); log user_quota_check_error with company_id, error, fail_safe_action: "allow"
• If CID is a Consultant Mode provisioning (/private/users/create_consultant endpoint): skip check entirely — is_consultant flag will be set to true by that endpoint

Behavior 2: Deduct Quota on successful user creation

FieldDetail
Entity affectedQuota balance for the company; billing_logs + billing_unique_logs tables
Triggered byNew standard user record successfully saved to Launchpad database (after DB write, before response to caller)
EndpointPOST /iag/v1/quota-managements/deduction
AuthBearer JWT (service account) + X-Api-Key header
Request body{ "company_id": "<company UUID>", "billing_code": "<TBD>", "deduction_code": "<TBD — user-seat deduction code>", "unique_code": "create_user_{user_id}", "quantity": 1, "extra_attrs": { "transaction_id": "{user_id}" } }
Response{ "company_id": "...", "billing_code": "...", "unique_code": "...", "credited_to": "<quota type deducted>", "value_before": float64, "value_after": float64 }
IdempotencyIf the same unique_code is submitted twice, the API returns credited_to: "already-deducted" and makes no balance change — safe to retry
Expected behavior• On success: 1 unit deducted from company's user quota balance; user_quota_deducted logged
• User creation is never rolled back due to a deduction failure
Failure behavior• On 5xx / timeout: push deduction request to gocraft/work background job with exponential backoff retry
• On exhausted retries: log user_quota_deduction_failed for manual investigation

Behavior 3: Refund Quota on user deletion

FieldDetail
Entity affectedQuota balance for the company; billing_logs + billing_unique_logs tables
Triggered byAdmin successfully deletes a standard user from Launchpad (PUT /iag/v1/users/{sso_id}/delete) — after confirmed DB deletion
EndpointPOST /iag/v1/quota-managements/refund
AuthX-Api-Key header only (no Bearer token required for refund endpoint)
Request body{ "company_id": "<company UUID>", "billing_code": "<TBD>", "refund_code": "<TBD — user-seat refund code>", "unique_code": "delete_user_{user_id}", "quantity": 1 }
Response{ "company_id": "...", "billing_code": "...", "unique_code": "...", "refunded_to": "<initial|additional|already-refunded>", "value_before": float64, "value_after": float64 }
Idempotencyunique_code is optional but strongly recommended — if submitted twice, returns refunded_to: "already-refunded" with no balance change
Expected behavior• On success: 1 unit restored to company's user quota balance; user_quota_refunded logged
Failure behavior• On API failure: log user_quota_refund_failed with user_id, company_id, and reason — retry behavior to be defined in RFC (see OQ-3)

8. System Flow + User Stories + ACs

8.1 System Flow

Flow: User Quota Integration — Check, Deduct, Refund, and Consultant Bypass Type: State Lifecycle / API Sequence

Standard User Creation Flow:

  1. Client admin initiates a user invitation in Launchpad (POST /iag/v1/users/sso_invite)
  2. System acquires existing Redis lock lock:user_invite:{company_id} (pessimistic concurrency control already in place)
  3. System checks: is this company on user_quota_integration_enabled (via qontak-preferences)?
  4. If NO → proceed with creation, no quota interaction
  5. If YES → call Quota Management check-quota API (POST /iag/v1/quota-managements/check-quota)
  6. If extra_attrs.is_sufficient: false (and not is_unlimited: true) → block creation; return "Quota Exceeded / Upgrade Required" to UI
  7. If extra_attrs.is_sufficient: true → create user record in Launchpad database
  8. After successful DB save → call Deduction API (POST /iag/v1/quota-managements/deduction) with unique_code: create_user_{user_id}, quantity: 1
  9. If Deduction API returns success → quota deducted, flow complete
  10. If Deduction API returns 5xx / timeout → push to gocraft/work background job for retry; user creation is not rolled back

Consultant Mode Flow (Internal via Modpanel): 11. Activation Team requests Consultant Mode access via Modpanel (moderator-be) 12. Modpanel calls Launchpad POST /private/users/create_consultant endpoint (with Basic Auth) 13. Launchpad sets is_consultant = true on the created user record — this flag is the bypass signal 14. Quota Check and Deduction API calls are skipped entirely — the /private/users/create_consultant endpoint never enters the quota check path

User Deletion Flow: 15. Admin initiates user deletion in Launchpad (PUT /iag/v1/users/{sso_id}/delete) 16. System checks: does the user have is_consultant = true? 17. If YES → delete user, skip Refund API call 18. If NO → delete user from Launchpad database 19. After confirmed deletion → call Refund API (POST /iag/v1/quota-managements/refund) with unique_code: delete_user_{user_id}, quantity: 1 20. On success → 1 unit restored to quota balance

graph TD
C1[Activation Team Requests Access via Modpanel] --> C2["Modpanel calls POST /private/users/create_consultant"]
C2 --> C3["Launchpad sets is_consultant = true on user record"]
C3 --> C4[Quota Check and Deduction Skipped — not in quota path]

A[Client Admin initiates POST /iag/v1/users/sso_invite] --> LOCK[Acquire Redis lock — lock:user_invite:company_id]
LOCK --> FF{Is CID on user_quota_integration_enabled?}
FF -- No --> FE[Create User — No Quota Interaction]
FF -- Yes --> F["Call POST /iag/v1/quota-managements/check-quota"]
F --> G{"extra_attrs.is_sufficient?"}
G -- No --> H[Block Creation — Show Quota Exceeded Message]
G -- Yes --> I[Create Standard User in Launchpad DB]
I --> J["Call POST /iag/v1/quota-managements/deduction — unique_code: create_user_{user_id}"]
J --> K{Deduction Response}
K -- Success --> L[Quota Deducted]
K -- "5xx / Timeout" --> M[Push to gocraft/work background job]
M --> J

N["Admin initiates PUT /iag/v1/users/{sso_id}/delete"] --> O{"is_consultant = true?"}
O -- Yes --> P[Delete User — Skip Refund API]
O -- No --> Q[Delete Standard User in Launchpad DB]
Q --> R["Call POST /iag/v1/quota-managements/refund — unique_code: delete_user_{user_id}"]
R --> S[Quota Refunded]

8.2 User Stories

User StoryImportanceMockup / Technical NotesAcceptance Criteria
[UQI-S01] — Pre-validate User Quota Before Invitation

As a Launchpad System, I want to check the user quota balance before processing a new user invitation, so that client admins are prevented from exceeding their paid user seat limits in real-time.
Must HaveBefore State: Launchpad calls Modpanel GetUnifiedBilling via validate_user_quota.go to check seat counts. No centralized deduction/refund ledger exists.

After Delta: For Qontak One CIDs with user_quota_integration_enabled = ON, replace the Modpanel-based check with a call to POST /iag/v1/quota-managements/check-quota. The existing Redis pessimistic lock lock:user_invite:{company_id} already serialises concurrent invitations — quota check executes inside that lock.

Endpoint called: POST /iag/v1/quota-managements/check-quota
Auth: Bearer JWT + X-Api-Key
Note: is_sufficient is nested inside extra_attrs in the response body — not a top-level field.

Figma: N/A
— Happy Path —
• AC-1: Given a client admin initiates a user invitation and user_quota_integration_enabled = ON for the CID, when the system calls POST /iag/v1/quota-managements/check-quota and extra_attrs.is_sufficient: true, then the user creation flow proceeds normally.
• AC-2: Given a client admin initiates a user invitation and the response returns extra_attrs.is_sufficient: false (and is_unlimited is not true), then the system blocks the invitation and displays a "Quota Exceeded / Upgrade Required" message in the UI.
• AC-3: Given the response returns extra_attrs.is_unlimited: true, then the system treats quota as sufficient regardless of balance and proceeds with creation.

— Error / Unhappy Path —
• ERR-1: Given the Quota Check API returns a 5xx or is unreachable, when the system cannot determine quota availability, then the invitation is allowed (fail-open) and user_quota_check_error is logged with company_id, error, and fail_safe_action: "allow" — consistent with resolved OQ-1.

— Permission Model —
• CAN: System (internal, triggered by client admin invitation action)
• CANNOT: No external actor can bypass this check for non-Consultant Mode users
• Unauthorized: N/A — internal system call

— UI States —
• Loading: N/A — quota check is synchronous inside the existing lock
• Error: Fail-open — invitation allowed with logging
• Success: N/A — user creation proceeds if sufficient
[UQI-S02] — Deduct Quota on Successful User Creation

As a Launchpad System, I want to deduct one user quota upon successful standard user creation, so that the company's available seat balance is accurately updated in the centralized billing system.
Must HaveBefore State: No deduction call exists — user creation has no effect on any quota balance.

After Delta: After a standard user is saved to the Launchpad database, the system calls POST /iag/v1/quota-managements/deduction. On 5xx/timeout the request is enqueued in a gocraft/work background job for async retry — user creation is never rolled back.

Endpoint called: POST /iag/v1/quota-managements/deduction
Auth: Bearer JWT + X-Api-Key
Request payload:
{ "company_id": "<UUID>", "billing_code": "<TBD>", "deduction_code": "<TBD>", "unique_code": "create_user_{user_id}", "quantity": 1, "extra_attrs": { "transaction_id": "{user_id}" } }

Background Job: gocraft/work (Go, Redis-backed) — same job infrastructure as SyncUserDetailJob in worker_service.go
— Happy Path —
• AC-1: Given a new standard user is successfully saved to the Launchpad database, when the system calls the Deduction API with unique_code: create_user_{user_id} and quantity: 1, then 1 unit is deducted from the company's user quota balance and user_quota_deducted is logged with company_id, user_id, and unique_code.
• AC-2: Given the same unique_code is submitted twice (retry scenario), when the API processes the second call, then it returns credited_to: "already-deducted" and no double-deduction occurs — idempotency is enforced via the billing_unique_logs table in qontak-billing.

— Error / Unhappy Path —
• ERR-1: Given the Deduction API returns a 5xx or timeout after the initial call, when the error is caught, then the system pushes the deduction request to a gocraft/work background job with exponential backoff retry — user creation is not rolled back and user_quota_deduction_retry is logged with company_id, user_id, unique_code, and attempt count.
• ERR-2: Given the background job exhausts all retries without a successful deduction, when the final retry fails, then user_quota_deduction_failed is logged with user_id, company_id, and reason for manual investigation.

— Permission Model —
• CAN: System (internal, triggered on successful user creation DB write)
• CANNOT: No external actor can trigger or cancel a deduction directly
• Unauthorized: N/A — internal system event

— UI States —
• Loading: N/A — backend only
• Error: N/A — retried in background; user creation not affected
• Success: N/A — transparent to client admin
[UQI-S03] — Refund Quota on User Deletion

As a Launchpad System, I want to refund one user quota when an existing standard user is deleted, so that the company regains their available seat capacity immediately.
Must HaveBefore State: No refund call exists — deleting a user has no effect on the quota balance.

After Delta: After a standard user is confirmed deleted from the Launchpad database, the system calls POST /iag/v1/quota-managements/refund.

Endpoint called: POST /iag/v1/quota-managements/refund
Auth: X-Api-Key header only (no Bearer token required for this endpoint)
Request payload:
{ "company_id": "<UUID>", "billing_code": "<TBD>", "refund_code": "<TBD>", "unique_code": "delete_user_{user_id}", "quantity": 1 }

Note: unique_code is optional in the API schema but must be supplied here for idempotency — duplicate refund calls return refunded_to: "already-refunded" with no balance change.
— Happy Path —
• AC-1: Given an admin successfully deletes a standard user from Launchpad, when the deletion is confirmed in the database, then the system calls the Refund API with unique_code: delete_user_{user_id} and quantity: 1, 1 unit is restored to the company's user quota balance, and user_quota_refunded is logged.
• AC-2: Given the Refund API returns refunded_to: "already-refunded" (duplicate call), then no balance change occurs and no error is raised — idempotent behaviour is correct.

— Error / Unhappy Path —
• ERR-1: Given the Refund API returns an error, when the call fails, then user_quota_refund_failed is logged with user_id, company_id, and reason — retry behaviour to be defined in RFC (see OQ-3).

— Permission Model —
• CAN: System (internal, triggered on confirmed user deletion)
• CANNOT: No external actor can call the Refund API directly
• Unauthorized: N/A — internal system event

— UI States —
• Loading: N/A — backend only
• Error: N/A — logged for manual review
• Success: N/A — transparent to admin
[UQI-S04] — Consultant Mode Quota Exemption

As a Launchpad System, I want to bypass quota checking and deduction for Consultant Mode users provisioned via Modpanel, so that the Activation Team can access a client's CID for setup without consuming the client's paid user seats.
Must HaveBefore State: No explicit bypass mechanism exists — provisioning a consultant via Modpanel risks counting against the client's seat quota.

After Delta: Consultant Mode provisioning is routed through a separate endpoint in Launchpad — POST /private/users/create_consultant (Basic Auth, called from moderator-be). Users created via this endpoint have is_consultant = true set on their database record. The quota check/deduction path in POST /iag/v1/users/sso_invite is never entered for consultant creation.

Bypass mechanism: is_consultant BOOLEAN field already exists in the users table (db/schema.sql). The CountUser SQL query used for quota counting already excludes consultants (AND (is_consultant IS NULL OR is_consultant = false)). No code change is needed in create_consultant.go for bypass — the bypass is structural.

Deletion bypass: The delete.go handler already checks is_consultant before applying company membership validation. The Refund API call in the deletion flow must also gate on is_consultant = false.

Figma: N/A
— Happy Path —
• AC-1: Given the Activation Team initiates a Consultant Mode access request from Modpanel, when Modpanel calls POST /private/users/create_consultant, then Launchpad creates the user with is_consultant = true and neither the Quota Check nor the Deduction API is called — the company's seat balance is unaffected.
• AC-2: Given a Consultant Mode user (is_consultant = true) is deleted from Launchpad, when the deletion flow executes, then the Refund API call is skipped entirely — the balance does not change.

— Error / Unhappy Path —
• ERR-1: Given a provisioning request arrives at POST /private/users/create_consultant but fails to set is_consultant = true (e.g. database error during user write), then the user creation must fail atomically — no partial record with an incorrect quota deduction can result. Log user_quota_consultant_flag_missing.

— Permission Model —
• CAN: Activation Team (via Modpanel internal tooling — moderator-be)
• CANNOT: Client admins cannot self-provision via the /private/users/create_consultant endpoint (Basic Auth, internal only)
• Unauthorized: N/A — internal tooling only

— UI States —
• Loading: N/A — backend only
• Error: N/A — creation fails atomically
• Success: N/A — transparent to Activation Team
[UQI-S05] — Backfill Existing User Usage

As a Backend Developer, I want a one-time script to backfill current user usage into the Quota Management API, so that existing Qontak One clients have an accurate starting balance without manual intervention at the time of integration go-live.
Must HaveBefore State: Quota Management API has no record of existing user seat consumption for Qontak One CIDs — balances would start at zero if not backfilled.

After Delta: A one-time Go task iterates through all Qontak One CIDs, counts active non-consultant users per CID, and pushes the aggregate usage to the Quota Management API.

Counting logic already exists: The CountUser SQL query in Launchpad (db/query/users.sql) already excludes both consultant users (is_consultant IS NULL OR is_consultant = false) and soft-deleted users (deleted_at IS NULL). The backfill script can reuse this query directly.

User states to include: Only status = 'active' users (not pending, not deleted). Consultant users (is_consultant = true) must be excluded — consistent with resolved OQ-6.

Figma: N/A — internal tooling

Execution: One-time Go command to be run as a migration step during deployment. Must log discrepancies and failed syncs per CID.
— Happy Path —
• AC-1: Given the backfill script is executed, when it processes a CID, then it accurately counts the number of active non-consultant users (reusing the CountUser query logic) and syncs this exact number as consumed quota to the Quota Management API via the Deduction endpoint.
• AC-2: Given the script processes all CIDs, when it completes, then it outputs a summary log of: total CIDs processed, total CIDs with discrepancies, total failed syncs, and a list of CID IDs that failed for manual follow-up.

— Error / Unhappy Path —
• ERR-1: Given the backfill script encounters a failed API call for a specific CID, when the sync fails, then it logs user_quota_backfill_failed with company_id and reason, skips to the next CID, and continues — it does not halt the entire run.

— Permission Model —
• CAN: Engineering team members (Go command via deployment pipeline)
• CANNOT: No client-facing interface exposes this action
• Unauthorized: N/A — internal tooling

— UI States —
• Loading: N/A — CLI output only
• Error: Logs failed CIDs with reason; does not stop execution
• Success: Outputs per-CID sync confirmation to console

AC ids are numbered per story (each story restarts at AC-1). Composite ids minted here: UQI-S01/AC-1, UQI-S01/AC-2, UQI-S01/AC-3, UQI-S01/ERR-1, UQI-S02/AC-1, UQI-S02/AC-2, UQI-S02/ERR-1, UQI-S02/ERR-2, UQI-S03/AC-1, UQI-S03/AC-2, UQI-S03/ERR-1, UQI-S04/AC-1, UQI-S04/AC-2, UQI-S04/ERR-1, UQI-S05/AC-1, UQI-S05/AC-2, UQI-S05/ERR-1.


9. Rollout

FieldValue
Feature flaguser_quota_integration_enabled — default: OFF (checked via qontak-preferences service per CID, using company sso_id as the preference UniqueID)
Stage 1Internal Bifrost test CIDs only (≤5 CIDs, manually enabled) — validate check, deduction, refund, and Consultant bypass
Stage 2Closed pilot: 5–10 Qontak One CIDs — confirm deduction reliability and backfill accuracy
Stage 3All Qontak One CIDs in batches (25% → 50% → 100%) after backfill script confirmed successful
GAAll Qontak One CIDs with user_quota_integration_enabled ON by default
Backward compatYes — CIDs with flag OFF continue using existing Modpanel-based validate_user_quota.go check; existing user creation flows are unaffected
MigrationBackfill Go command (UQI-S05) must run before or at GA to seed accurate usage counts into the Quota Management API

10. Observability

Key Events:

Event NameTriggerProperties
user_quota_check_passedQuota check returns is_sufficient: true for a user invitationcompany_id, remaining_quota
user_quota_check_failedQuota check returns is_sufficient: false; invitation blockedcompany_id, remaining_quota
user_quota_check_errorQuota Check API is unreachable or returns 5xxcompany_id, error, fail_safe_action
user_quota_deductedDeduction API call succeeds after user creationcompany_id, user_id, unique_code
user_quota_deduction_retryDeduction API returned 5xx; pushed to gocraft/work jobcompany_id, user_id, unique_code, attempt
user_quota_deduction_failedgocraft/work job exhausted retries; deduction unconfirmedcompany_id, user_id, unique_code, error
user_quota_refundedRefund API call succeeds after user deletioncompany_id, user_id, unique_code
user_quota_refund_failedRefund API call fails after user deletioncompany_id, user_id, unique_code, error
user_quota_consultant_bypassis_consultant = true detected; quota interaction skippedcompany_id, user_id, source: create_consultant_endpoint
user_quota_backfill_completedBackfill script successfully synced quota for a CIDcompany_id, synced_user_count
user_quota_backfill_failedBackfill script failed to sync quota for a CIDcompany_id, error
FieldDetail
Dashboard ownerBifrost team
Alert 1user_quota_deduction_failed count > 0 in any 1hr window → Slack: #bifrost-alerts (every failed deduction is a potential revenue leak)
Alert 2user_quota_check_error rate > 5% of total check calls in 30min window → Slack: #bifrost-alerts (Quota Management API may be degraded)

10.1 Post-Launch Monitoring Cadence

FieldDetail
Review cadenceWeekly for first 4 weeks post-GA, then monthly
OwnerBifrost PM
Review scopeuser_quota_deducted, user_quota_refunded, user_quota_deduction_failed, user_quota_refund_failed, user_quota_check_error
Trigger threshold 1user_quota_deduction_failed count > 0 in any 7-day window → immediate investigation (zero tolerance for unconfirmed deductions post-GA)
Trigger threshold 2user_quota_check_error rate > 1% sustained over 48 hours → Quota Management API health check and Bifrost Eng escalation
Rollback considerationIf user_quota_deduction_failed rate exceeds 5% of total creations and cannot be resolved within 24 hours, PM disables user_quota_integration_enabled globally pending root cause fix

11. Success Metrics

Reliability:

MetricDefinitionBaselineTarget
Deduction success rate% of standard user creations followed by a confirmed successful Quota Management deduction (including gocraft/work background retries)N/A — new feature≥ 99.9% within 30 days of GA
Refund success rate% of standard user deletions followed by a confirmed successful Quota Management refundN/A — new feature≥ 99.5% within 30 days of GA

Correctness:

MetricDefinitionBaselineTarget
Quota enforcement accuracy% of user invitations that are correctly blocked when CID quota is at zeroN/A — new feature100% (zero leakage) within 14 days of GA
Consultant bypass accuracy% of Consultant Mode provisioning events (via POST /private/users/create_consultant) that correctly skip quota interactionN/A — new feature100% (zero false deductions)

Migration:

MetricDefinitionBaselineTarget
Backfill coverage% of active Qontak One CIDs successfully backfilled before GA0%100% before Stage 3 begins

12. Launch Plan & Stage Gates

StageAudienceDurationSuccess Gate to AdvanceOwner
Internal AlphaBifrost test CIDs (≤5)1 week0 P0 bugs. Quota check, deduction, and refund all confirmed working. Consultant bypass verified via is_consultant flag.Bifrost PM + QA
Closed Pilot5–10 Qontak One CIDs1 weekDeduction success rate ≥ 99.9%. Refund success rate ≥ 99.5%. 0 user_quota_deduction_failed events.Bifrost PM
Backfill ExecutionAll active Qontak One CIDsPre-Stage 3 (one-time)100% of CIDs backfilled. 0 unresolved user_quota_backfill_failed events.Bifrost Eng Lead
Staged RolloutAll Qontak One CIDs in batches (25% → 50% → 100%)2 weeksDeduction failure rate ≤ 0.1% sustained for 1 week per batch. No quota enforcement bypass detected.Bifrost PM + Eng Lead
GAAll Qontak One CIDsOngoingAll staged rollout gates sustained for 2 consecutive weeks.Bifrost PM

13. Dependencies

DependencyOwning TeamDeliverable NeededBlocking?
Quota Management API — POST /iag/v1/quota-managements/check-quotaBifrost (qontak-billing)Stable, documented endpoint available in staging; confirmed billing_code value for user seatsYES
Quota Management API — POST /iag/v1/quota-managements/deductionBifrost (qontak-billing)Stable endpoint; confirmed billing_code + deduction_code values; idempotency via unique_code + billing_unique_logsYES
Quota Management API — POST /iag/v1/quota-managements/refundBifrost (qontak-billing)Stable endpoint; confirmed billing_code + refund_code valuesYES
Service account credentials for Quota Management APIBifrost / PlatformBearer JWT (service account) + X-Api-Key header provisioned for Launchpad in all environmentsYES
sync_consultant_to_launchpad feature flag in moderator-beModpanel teamFeature flag must be ON for Qontak One CIDs so Consultant Mode provisioning routes through POST /private/users/create_consultant in Launchpad (not legacy Chat/CRM direct)YES
qontak-preferences feature flag entry for user_quota_integration_enabledBifrostPreference entry registered and testable in stagingYES

14. Key Decisions + Alternatives Rejected

Decisions Made:

DateDecisionRationale
2026-06-23Use user_id as the idempotency key (unique_code) for both deduction and refund callsuser_id is unique per Launchpad user and permanent — ensures idempotent retries for both deduction and refund without risk of double-counting. The billing_unique_logs table in qontak-billing enforces uniqueness at the DB level.
2026-06-23Use gocraft/work background job (not Sidekiq) for deduction API failuresLaunchpad is a Go service using gocraft/work with Redis as the job store — the same infrastructure that powers SyncUserDetailJob. There is no Sidekiq in Launchpad; introducing it would add an unjustified dependency.
2026-06-23Block creation on deduction failure is wrong — use async retryBlocking creation on a Quota Management API failure would degrade the user invitation UX for an internal billing failure; user is already created in the DB, so the deduction must succeed eventually via async retry
2026-06-23Consultant Mode bypass is structural, not flag-basedThe /private/users/create_consultant endpoint (called from Modpanel) sets is_consultant = true on the user record. The quota check/deduction path (/iag/v1/users/sso_invite) is never entered for consultant creation. No runtime flag check is needed — the bypass is guaranteed by endpoint routing.
2026-06-23Run backfill as a one-time Go command at deployment, not as an ongoing syncQuota Management API is the live system of record post-integration; ongoing sync would create a second source of truth and risk double-counting
2026-06-23Fail-open (allow with logging) when Quota Check API is unreachableBlocking all invitations on an API outage is too disruptive for an internal billing failure — consistent with the async retry pattern for Deduction

Alternatives Rejected:

AlternativeWhy RejectedDate
Enforce quota via a nightly batch job rather than real-time API checkDoes not prevent over-provisioning in real-time — admins could exceed quota intraday before the batch catches up, creating the same revenue leakage problem this integration aims to solve2026-06-23
Block user creation entirely when Deduction API returns 5xxToo disruptive — a transient billing service failure should not prevent legitimate user operations; async retry preserves quota accuracy without degrading the invitation experience2026-06-23
Skip the backfill and let quota balances start from zero at go-liveExisting Qontak One CIDs with current users would appear to have full available seats even though seats are already consumed — first-day quota checks would give incorrect results and allow over-provisioning2026-06-23
Use a runtime Consultant Mode flag in the request payload for bypass detectionLaunchpad already has structural separation via the /private/users/create_consultant endpoint and the is_consultant DB column — adding a runtime flag would duplicate this and introduce a new attack surface if the flag could be spoofed by external callers2026-06-23

15. Open Questions

#TypeQuestionOwnerDeadline
1Open QuestionWhat is the fail-safe behavior when the Quota Check API is unreachable — block the invitation (safe but disruptive) or allow with logging (risky but non-blocking)?Bifrost PM + EngBefore RFC
2Open QuestionWhat is the retry strategy for the Deduction gocraft/work background job — max attempts, backoff curve, and dead-letter queue handling?Bifrost EngBefore RFC
3Open QuestionDoes the Refund API call on user deletion also need gocraft/work background retry, or is a single attempt with logging sufficient?Bifrost EngBefore RFC
4AssumptionThe Quota Management API billing_unique_logs table correctly deduplicates concurrent calls for the same unique_code — if not, rapid create-retry flows could double-deductBifrost EngBefore Stage 1
5RiskIf sync_consultant_to_launchpad feature flag in moderator-be is OFF for a given CID, Modpanel routes consultant provisioning to Chat/CRM directly — Launchpad is never called and is_consultant is never set. These users would not exist in Launchpad at all, so no quota impact. But if the flag is subsequently toggled ON mid-tenancy, historical consultant users would have no is_consultant flag in Launchpad.Bifrost PM + Modpanel teamBefore RFC
6Open QuestionShould the backfill script exclude users in any state other than "active" (e.g. deactivated, suspended) or only exclude Consultant Mode users?Bifrost PMBefore RFC
7Open QuestionWhat is the billing_code value to use for user seat quota in the Quota Management API? This is required in all three API calls.Bifrost (qontak-billing team)Before RFC
8Open QuestionWhat are the deduction_code and refund_code values for user seat operations in the Quota Management API? Both are required fields in their respective request payloads.Bifrost (qontak-billing team)Before RFC
9Open QuestionWhich company identifier should be passed as company_id in Quota Management API calls — Launchpad's internal companies.id (UUID), the Mekari SSO company sso_id (UUID), or the external_company_id (integer cast to string)? The billing API expects a UUID string.Bifrost EngBefore RFC
10Open QuestionShould the expectation_deduction field in the check-quota extra_attrs be populated with any data for user seat checks, or passed as an empty object {}? The Quota Management API requires this field.Bifrost EngBefore RFC
11Open QuestionLaunchpad already uses validate_user_quota.go (Modpanel-backed) for non-Qontak One CIDs. For Qontak One CIDs going through this new integration, should the Modpanel-based check be explicitly disabled (removed from the code path under feature flag) or does the feature flag on the new path implicitly bypass it?Bifrost EngBefore RFC

PRD CHANGELOG

VersionDateBySectionTypeSummary
1.02026-06-22ClaudeAllCREATEDInitial NEW PRD generated from Confluence page (ID 51184074959); all 5 user stories from source preserved and expanded to full Qontak PRD format with Gherkin ACs, observability events, and rollout plan.
1.12026-06-23ClaudeS14, S15MODIFIEDSet all S14 decision and alternative dates to 2026-06-23. Resolved S15 OQ1 (allow with logging on Quota Check API failure) and OQ6 (backfill excludes Consultant Mode + deactivated/suspended users). OQ2, OQ3, OQ4, OQ5 remain open pending Engineering/cross-team input.
1.22026-06-23ClaudeS3, S6, S7, S8, S13, S14, S15MODIFIEDEnriched with findings from qontak-launchpad, qontak-billing, and moderator-be repos. Key changes: (1) Corrected background job technology from "Sidekiq" to gocraft/work (Launchpad is Go). (2) Filled in exact Quota Management API endpoint paths, request/response JSON schemas, and auth headers (was deferred to RFC). (3) Clarified Consultant Mode bypass is structural via endpoint routing + is_consultant DB field — not a runtime flag. (4) Noted existing validate_user_quota.go (Modpanel-based) that this integration replaces. (5) Documented existing Redis pessimistic lock lock:user_invite:{company_id}. (6) Added OQ7–OQ11 for billing_code, deduction_code, refund_code, company_id UUID type, and expectation_deduction payload. (7) Updated backfill story with knowledge that CountUser SQL already excludes consultants and soft-deleted users.