How to Integrate Quota Management
Purpose
This guide is intended to help other teams integrate their services with the standardized Quota Management System. It covers processes such as quota checking, deduction, refund, and usage logging.
Core Components
| Component | Description |
|---|---|
| Check Balance | API endpoint to verify the current quota before deduction |
| Deduct | API endpoint to reduce quota after usage is confirmed |
Integration Steps
1. Identify the Billing Component
Before integration, the engineer must obtain a billing code from the product team for the feature that will be connected to quota management. This code must be included in every request to the quota management system.
2. Authentication (SSO Token)
Before calling any Quota Management API, you must obtain an SSO access token.
This integration supports two types of tokens:
User Token Used when the product already has an existing user access token that is used to access the main application pages. This same token can be reused to call the Quota Management API.
Service-to-Service Token Used as an alternative when the product does not have, or does not manage, a user token. In this case, the token is obtained using Service-to-Service authentication.
Endpoint:
POST {{base_sso_url}}/auth/oauth2/token
| Environment | base_sso_url |
|---|---|
| Staging | api.mekari.io |
| Production | api.mekari.com |
Client Credentials
⚠️
{{client_id}}and{{client_secret}}must be requested via DM from the Bifrost team. Do not share credentials in public channels or repositories.
Sample Request:
{
"grant_type": "client_credentials",
"scope": "sso:profile",
"client_id": "{{client_id}}",
"client_secret": "{{client_secret}}"
}
Sample Response:
{
"expires_in": 3600,
"token_type": "bearer",
"access_token": "uveNyBkn36rDEEpBLfnGsZoOs9gx4PI1"
}
3. Check Quota Balance
Before deducting quota, you must call the check quota API to ensure quota availability.
Endpoint:
POST {{base_sso_url}}/internal/qontak/billing/v1/quota-managements/check-quota
Authentication: IAG (Service to Service)
Sample Request
{
"billing_code": "EmailBroadcast",
"company_id": "154982",
"extra_attrs": {
"expectation_deduction": {
"en": 1,
"other": 1
}
},
"is_scheduled": true
}
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
billing_code | string | Yes | The billing code provided by the product team |
company_id | string | Yes | The ID of the company whose quota will be checked |
expectation_deduction | object | Yes | The expected quota usage, by category (e.g., per country or other grouping). At least one field is required. |
is_scheduled | boolean | No | Set to true if the usage is scheduled (e.g., a broadcast job). Default: false |
Sample Response (Success) — Limited Quota
{
"billing_code": "EmailBroadcast",
"company_id": "154982",
"extra_attrs": {
"estimation_quota": {
"total_estimation_balance_quota": 200,
"total_estimation_credit_quota": 2
},
"expectation_deduction": {
"en": 1,
"other": 1
},
"is_sufficient": true,
"is_unlimited": false,
"quota_info": {
"total_remaining_balance_quota": 100,
"total_remaining_credit_quota": 1
},
"used_quota": {
"total_used_balance_quota": 100,
"total_used_credit_quota": 1
}
},
"is_scheduled": true
}
Sample Response (Success) — Unlimited Quota
When the component is configured as unlimited (is_unlimited: true), the system short-circuits immediately and always returns is_sufficient: true without evaluating balances or calling price APIs. estimation_quota and used_quota will be zero.
{
"billing_code": "EmailBroadcast",
"company_id": "154982",
"extra_attrs": {
"estimation_quota": {
"total_estimation_balance_quota": 0,
"total_estimation_credit_quota": 0
},
"expectation_deduction": {
"en": 1,
"other": 1
},
"is_sufficient": true,
"is_unlimited": true,
"quota_info": {
"total_remaining_balance_quota": 0,
"total_remaining_credit_quota": 0
},
"used_quota": {
"total_used_balance_quota": 0,
"total_used_credit_quota": 0
}
},
"is_scheduled": false
}
Response Fields Explanation
is_sufficient
A boolean flag that indicates whether the current available quota is sufficient to fulfill the expected deduction.
true: Quota is sufficient, you can proceed with deductionfalse: Quota is insufficient, deduction should not proceed
When
is_unlimitedistrue,is_sufficientis alwaystrueregardless of remaining balances.
is_unlimited
A boolean flag indicating whether the billing component is configured as unlimited for this organization.
true: The component has unlimited quota. Deduction will be recorded but no actual quota is decremented.false: The component has a finite quota pool subject to normal balance checking.
estimation_quota
Provides an estimation of how much quota will be consumed for the current request, based on the expected deduction provided in the request.
| Field | Description |
|---|---|
total_estimation_balance_quota | Estimated balance quota that would be consumed, if charged entirely from balance. Example: if 1 credit = 100 balance units, and you're deducting 2 credits, this might show 200. |
total_estimation_credit_quota | Estimated credit quota that would be consumed. Typically matches the number of expected deductions if charged entirely by credit. |
Note: The quota system may charge partially from balance and partially from credit depending on availability. This field helps anticipate potential usage. Both values are
0whenis_unlimited=true.
quota_info
Displays the company's current available quota, separated into balance and credit.
| Field | Description |
|---|---|
total_remaining_balance_quota | The total number of balance units currently available. These are generally smaller units used after converting from credit (e.g., 1 credit = 100 balance units). |
total_remaining_credit_quota | The total number of credits remaining. Credits are higher-level quota units and are typically used first unless otherwise specified. |
used_quota
Shows how the quota will be deducted based on the current quota availability. If credit is available, it may be used first. Any remaining deduction may be fulfilled using balance.
| Field | Description |
|---|---|
total_used_credit_quota | The amount of credit quota that will be used. 0 when is_unlimited=true. |
total_used_balance_quota | The amount of balance quota that will be used. 0 when is_unlimited=true. |
Possible Error Responses
| HTTP Status | resp_code | Error Description | Cause | Action Required |
|---|---|---|---|---|
| 404 | "404" | Component not found | billing_code not found or not registered in the system | Verify billing_code with product/billing team |
| 404 | "404" | Organization package not found | Company doesn't have any active package | Contact billing team to activate package for this company |
| 422 | "422" | Feature is not active | The billing component exists but is not active for this company | Contact billing team to activate the feature |
| 422 | "422" | Package component is not active | The package component is not active for this company | Contact billing team to activate the package component |
| 500 | "500" | Internal error | Database error or system malfunction | Retry the request. If persists, contact Bifrost team |
Sample Error Response
{
"resp_code": "422",
"resp_desc": {
"id": "feature is not active",
"en": "feature is not active"
},
"meta": {
"version": "",
"api_env": ""
}
}
4. Deduct Quota
Once quota is confirmed to be sufficient, you can proceed to deduct it.
Endpoint:
POST {{base_sso_url}}/internal/qontak/billing/v1/quota-managements/deduction
Authentication: IAG (Service to Service)
Sample Request
{
"billing_code": "EmailBroadcast",
"company_id": "154982",
"deduction_code": "id",
"quantity": 1,
"extra_attrs": {
"account_uniq_id": "string",
"agent_id": "string",
"agent_name": "string",
"broadcast_name": "string",
"channel_id": "string",
"channel_integration_id": "string",
"channel_name": "string",
"completion_at": "string",
"completion_id": "string",
"conversation_category": "string",
"conversation_id": "string",
"country": "string",
"customer_name": "string",
"customer_uniq_id": "string",
"message_broadcast_id": "string",
"origin_type": "string",
"prompt_at": "string",
"prompt_id": "string",
"recipient": "string",
"room_id": "string",
"template_name": "string"
},
"free_reason": "string",
"is_free": true,
"unique_code": "string"
}
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
billing_code | string | Yes | The billing code provided by the product team |
company_id | string | Yes | The ID of the company whose quota will be deducted |
deduction_code | string | Yes | Must match what was agreed upon with the billing and service teams (e.g., country code) |
quantity | float | No | Number of units to deduct (min: 0.01). Defaults to 1.0 when omitted. |
extra_attrs | object | Yes | Required metadata for billing/reporting (e.g., broadcast template name, agent name, room_id) |
is_free | boolean | No | Set to true if this deduction is agreed to be free. Default: false |
free_reason | string | No | Required if is_free is true. Reason why this deduction is free |
unique_code | string | No | Required if the deduction must be uniquely tracked within a certain period (idempotency key) |
Sample Response (Success)
{
"billing_code": "EmailBroadcast",
"company_id": "154982",
"credited_to": "initial",
"deduction_code": "id",
"extra_attrs": {
"recipient": "user@example.com",
"broadcast_name": "Monthly Newsletter",
"template_name": "newsletter_template"
},
"free_reason": "",
"is_free": false,
"unique_code": "broadcast-123-msg-456",
"value_before": 1000,
"value_after": 999
}
Response Fields
| Field | Type | Description |
|---|---|---|
credited_to | string | Indicates the result of the deduction. See values below. |
value_before | float | Remaining quota value before deduction |
value_after | float | Remaining quota value after deduction |
extra_attrs | object | Echo of the extra_attrs sent in the request |
credited_to possible values:
| Value | Meaning |
|---|---|
"initial" / "additional" / "postpaid" | Normal deduction from the respective quota type (actual component code is returned) |
"free" | Deduction was marked as free (is_free=true), no quota was consumed |
"already-deducted" | unique_code was already used — idempotency hit, no duplicate deduction performed |
"retry-deduction" | Optimistic locking conflict detected. The deduction job has been automatically requeued as a background job. No action needed from the caller — it will be processed shortly. |
Unlimited Quota Behavior: When the component has
is_unlimited=true, the deduction is still recorded in the billing log (for audit purposes), but no actual quota is decremented —value_beforeandvalue_afterwill be equal, and all price/margin values are set to0.credited_tois still populated based on the first available quota type.
Post-deduction Alert: After a successful deduction, the system automatically checks if the remaining quota has dropped below the component's
threshold_running_outpercentage (e.g., 40%). If so, a quota alert notification is triggered asynchronously. Your service does not need to handle this — it is managed by the billing system.
Possible Error Responses
| HTTP Status | resp_code | Error Description | Cause | Action Required |
|---|---|---|---|---|
| 404 | "404" | Component not found | billing_code not found or not registered in the system | Verify billing_code with product/billing team |
| 404 | "404" | Organization package component not found | Company doesn't have the component in their package | Contact billing team to add component to company's package |
| 404 | "404" | Component quota not found | No quota record exists for this company-component combination | Contact billing team to initialize quota |
| 422 | "422" | Feature is not active | The billing component exists but is not active for this company | Contact billing team to activate the feature |
| 422 | "422" | Package component is not active | The package component is not active for this company | Contact billing team to activate the package component |
| 422 | "422" | Billing log already exists | The unique_code has already been used for a previous deduction | This is expected behavior for idempotency. The deduction was already processed. |
| 500 | "500" | Internal error | Database error, billing service error, or system malfunction | Retry the request. If persists, contact Bifrost team |
Sample Error Response
{
"resp_code": "404",
"resp_desc": {
"id": "Tidak ditemukan",
"en": "Not found"
},
"meta": {
"version": "",
"api_env": ""
}
}
5. Quota Info
Endpoint:
GET {{base_sso_url}}/internal/qontak/billing/v1/quota-managements/info/:billing_code
Authentication: IAG (Service to Service)
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
billing_code | string | Yes | Billing component code (path parameter) |
company_id | string | No | Company ID. Required for internal/S2S requests; auto-filled from SSO token for user requests. |
Sample Response (Success)
{
"billing_code": "VOICE-RECORDING-2026-01",
"company_id": "269783",
"is_active": true,
"initial_quota": {
"initial_quota": 1,
"remaining_quota": 1,
"usage_quota": 0,
"unit_type": "credit",
"is_unlimited": false
},
"additional_quota": {
"initial_quota": 0,
"remaining_quota": 59,
"usage_quota": 1,
"unit_type": "credit",
"is_unlimited": false
},
"postpaid_quota": {
"initial_quota": 100000,
"remaining_quota": 99998,
"usage_quota": 2,
"unit_type": "credit",
"is_unlimited": false
}
}
Response Fields
Each quota type (initial_quota, additional_quota, postpaid_quota) contains:
| Field | Type | Description |
|---|---|---|
initial_quota | float | Total quota allocated at the start of the contract/month |
remaining_quota | float | Current remaining quota available for deduction |
usage_quota | float | Total quota consumed so far |
unit_type | string | Unit of measurement: credit or balance |
is_unlimited | boolean | Whether this quota type is configured as unlimited for this organization. When true, deductions are recorded but quota is never decremented. |
6. Refund Quota
Refund quota back to the organization's package component. This endpoint restores previously deducted quota by distributing it back to initial quota first (up to its limit), then to additional quota for any overflow.
Endpoint:
POST {{base_sso_url}}/internal/qontak/billing/v1/quota-managements/refund
Authentication: IAG (Service to Service)
Sample Request
{
"company_id": "154982",
"billing_code": "EmailBroadcast",
"refund_code": "id",
"unique_code": "refund-broadcast-123-msg-456",
"quantity": 1
}
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
company_id | string | Yes | The ID of the company whose quota will be refunded |
billing_code | string | Yes | The billing code associated with the deducted quota |
refund_code | string | Yes | Identifier that represents the refund context (e.g., country, category, or agreed code) |
quantity | float | Yes | Amount of quota to be refunded. Minimum value is 1 |
unique_code | string | No | Unique identifier to ensure idempotent refund processing |
Sample Response (Success)
{
"company_id": "154982",
"billing_code": "EmailBroadcast",
"refund_code": "id",
"unique_code": "refund-broadcast-123-msg-456",
"value_before": 999,
"value_after": 1000,
"refunded_to": "initial"
}
Response Fields
| Field | Type | Description |
|---|---|---|
refunded_to | string | Indicates the result of the refund. See values below. |
value_before | float | Remaining quota value before refund |
value_after | float | Remaining quota value after refund |
refunded_to possible values:
| Value | Meaning |
|---|---|
"initial" | Refund was applied to the initial quota pool |
"additional" | Refund was applied to the additional quota pool (overflow from initial) |
"already-refunded" | unique_code was already used — idempotency hit, no duplicate refund performed |
"retry-refund" | Optimistic locking conflict detected. The refund job has been automatically requeued as a background job. No action needed from the caller. |
Possible Error Responses
| HTTP Status | Response Code | Error Description | Cause | Action Required |
|---|---|---|---|---|
| 404 | "404" | Component not found | Billing code not found or not registered in the system | Verify billing_code with product/billing team |
| 400 | "400" | Feature is not active | Main component is inactive (main_component_is_active = false) | Contact billing team to activate the feature |
| 400 | "400" | Package component is not active | Package component is inactive (package_component_is_active = false) | Ensure company has active package subscription |
| 404 | "404" | Component quota not found | Quota record not found for company_id and billing_code combination | Verify company has the billing component provisioned |
| 500 | "500" | Internal error | Database error, billing service error, or system malfunction | Retry the request. If persists, contact Bifrost team |
7. Negative Balance Handling (Downgrade Event)
In certain scenarios, such as package downgrade, an organization's quota balance may become negative.
This negative balance is not processed through an API call, but is instead published asynchronously via a Kafka event. Services that depend on quota information must listen to this event to properly handle post-downgrade quota states.
Kafka Topic
billing.quota_management.negative_balance
Event Payload
{
"company_id": "154982",
"billing_code": "EmailBroadcast",
"negative_amount": 50
}
Payload Fields
| Field | Type | Description |
|---|---|---|
company_id | string | The ID of the company affected by the downgrade |
billing_code | string | The billing component whose quota becomes negative |
negative_amount | float | The amount of quota that exceeds the downgraded package limit |
Event Semantics
- This event indicates that the current quota usage exceeds the new package limit
negative_amountrepresents the excess quota that must be reconciled- No automatic quota deduction or refund is performed by this event
- Consumer services are responsible for:
- Blocking further usage
- Triggering internal reconciliation
8. Inactive Package Event (Kafka)
When a billing component transitions from active to inactive (e.g., subscription cancelled or plan downgraded to exclude the feature), the system publishes a Kafka event.
Kafka Topic
billing.quota_management.inactive_package
Event Payload
{
"company_id": "154982",
"organization_id": "org-uuid-12345",
"billing_code": "EmailBroadcast",
"is_package_inactive": true,
"quota_usage": 150.5
}
Payload Fields
| Field | Type | Description |
|---|---|---|
company_id | string | The ID of the company affected |
organization_id | string | The organization UUID |
billing_code | string | The billing component that became inactive |
is_package_inactive | boolean | Always true for this event |
quota_usage | float | Total quota consumed across all quota types (initial + additional + postpaid) |
Event Semantics
- Fired when a component goes from
is_active=truetois_active=false - Initial and postpaid quota are reset to 0 on deactivation; additional quota is preserved (can be carried over when the component becomes active again)
- Consumer services should use this to block further usage of the feature until reactivation
How is_unlimited is Determined
is_unlimited is automatically computed by the billing system — integration teams do not set it manually.
When the quota cache is refreshed (via the invalidate-cache endpoint or subscription change), the system checks:
- The billing component has an
unlimited_valuethreshold configured (e.g.,99999999) - The company's new
initial_quotaORpostpaid_quotaequals or exceeds that threshold
If both conditions are met, is_unlimited=true is stored on the organization_package_components record and returned in all API responses going forward.
Practical implication: A company on an "unlimited" plan will have their feature's quota set to a very large number (≥ unlimited_value). The billing system detects this automatically and short-circuits all quota checks and deductions for that company's component.
Error Handling Best Practices
1. Check Quota First
Always call the Check Quota endpoint before attempting deduction. This helps you:
- Verify component availability
- Check if quota is sufficient
- Get accurate quota information
2. Handle Specific Error Codes
Implement specific handling for each error code:
// Example error handling (pseudo-code)
try {
const response = await checkQuota(params);
if (!response.extra_attrs.is_sufficient) {
// Handle insufficient quota
// This is NOT an error, it's expected behavior
showInsufficientQuotaMessage();
return;
}
// is_unlimited=true means quota is never depleted — deduction still proceeds
// but value_before == value_after in the deduction response
if (response.extra_attrs.is_unlimited) {
logInfo("Component is unlimited — deduction will be recorded without decrementing quota");
}
// Proceed with deduction
const deductionResponse = await deductQuota(params);
// Handle special credited_to values
switch (deductionResponse.credited_to) {
case "already-deducted":
// unique_code was already processed — idempotency, safe to ignore
logInfo("Deduction already processed for unique_code:", params.unique_code);
break;
case "retry-deduction":
// Optimistic lock conflict — system has auto-requeued the job
// No action needed; the deduction will be processed in the background
logInfo("Deduction requeued due to lock conflict, will be processed shortly");
break;
default:
// Normal: "initial", "additional", "postpaid", or "free"
logInfo("Deduction applied to:", deductionResponse.credited_to);
}
} catch (error) {
switch (error.resp_code) {
case "404":
// Component/package not found
logError("Setup issue - contact billing team");
break;
case "422":
if (error.resp_desc.en === "feature is not active") {
showMessage("Feature not activated for your account");
} else if (error.resp_desc.en === "billing log already exists") {
// Idempotent request - already processed
logInfo("Deduction already processed");
}
break;
case "500":
// Retry logic
retryWithBackoff();
break;
}
}
3. Use Unique Codes for Idempotency
When processing critical operations (e.g., broadcasts, transactions), always provide a unique_code:
{
"unique_code": "broadcast-{broadcast_id}-message-{message_id}"
}
This ensures that if your request is retried due to network issues, the quota won't be deducted multiple times.
4. Retry Strategy
For 500 errors, implement exponential backoff:
- First retry: Wait 1 second
- Second retry: Wait 2 seconds
- Third retry: Wait 4 seconds
- Max retries: 3 attempts
If all retries fail, log the error and alert your monitoring system.
Additional Notes
Quota Migration (Legacy Systems)
Existing systems (e.g., MUV, WA Balance, Chatbot AI) being migrated to this quota management platform must continue using the old flow in parallel (dual-write) during the transition period.
- Transition period: TBD (will be communicated by the Bifrost team)
Handling Insufficient Quota
When is_sufficient: false is returned from the Check Quota endpoint:
- This is not an error — it's expected behavior
- Any action required (e.g., showing upgrade prompt, disabling feature) must be handled by your service
- Do NOT attempt to call the Deduction endpoint when quota is insufficient
Testing
Before going to production:
- Test with your staging credentials
- Verify error handling for all error codes
- Test idempotency with duplicate
unique_code - Verify quota deduction accuracy
- Test with insufficient quota scenarios
Support
For questions or issues:
- Setup/Configuration: Contact Bifrost team via
#bifrost-support - Bug Reports: Create ticket in Bifrost JIRA
- Feature Requests: Discuss with product and billing teams
API Response Format
All responses follow the standard Mekari API format:
{
"resp_code": "200",
"resp_desc": {
"id": "Indonesian description",
"en": "English description"
},
"meta": {
"version": "1.0",
"api_env": "production"
},
"data": {
// Response data here
}
}
Error Response Format
{
"resp_code": "{HTTP_STATUS_CODE}",
"resp_desc": {
"id": "Deskripsi error dalam Bahasa Indonesia",
"en": "Error description in English"
},
"meta": {
"version": "",
"api_env": ""
}
}
Quick Reference: Error Codes Summary
| Error Code | Common Scenarios | First Action |
|---|---|---|
| 404 | Component/package not found | Verify setup with billing team |
| 422 | Feature not active, already processed, validation | Check component status or idempotency |
| 500 | System error | Retry with backoff, then escalate |