Skip to main content
When you add or remove features on a plan, existing active subscriptions do not automatically pick up the change — their entitlements stay as they were at activation. Reconciliation converges an active subscription’s feature entitlements to its plan’s current shape in a single call: it provisions missing features, removes features no longer on the plan, and synchronizes the corresponding billing.
Reconciliation applies to Standard Billing (billingVersion: 1) subscriptions billed on their plan’s line-item schedule. Legacy Billing subscriptions are rejected. See Billing Versions.

When to use it

Reach for reconciliation whenever a plan’s feature list drifts from what an existing active subscription actually has:
  • You added a feature to a plan and want existing subscribers to get it without recreating the subscription.
  • You removed a feature from a plan and want to stop billing existing subscribers for it and revoke their entitlement.
  • A prior subscription activation partially failed and left the subscription without one of the plan’s features.
  • You want to preview either of the above without touching billing — use dryRun: true.
You do not need reconciliation when the plan and subscription are already aligned. New subscriptions provision entitlements automatically at activation; see Automatic entitlement provisioning.

What reconciliation does

A reconciliation is a single, idempotent operation that:
  1. Adds an entitlement (and, for metered features, its initial grant) for every plan feature the subscription is missing.
  2. Removes the entitlement and voids the grants of any feature the subscription has but the plan no longer includes.
  3. Skips features that are already present — they are left unchanged.
  4. Synchronizes billing by creating future line items for added prices and removing future, uninvoiced line items for removed prices.
Reconciliation mints metered feature grants as full-period grants and does not prorate for the current period. Reconciliation never modifies already-invoiced line items — it only touches future, uninvoiced periods.

Making a reconciliation request

API Reference: See the Reconcile Features API endpoint for the complete request and response schema.
Send an empty body (or { "dryRun": false }) to a subscription’s reconciliation endpoint. The subscription must be active.
async function reconcileSubscription(subscriptionId, { dryRun = false } = {}) {
  const response = await fetch(
    `https://api.paygentic.io/v0/subscriptions/${subscriptionId}/reconciliations`,
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer sk_live_YOUR_API_KEY',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ dryRun }),
    }
  );

  if (!response.ok) {
    throw new Error(`Reconciliation failed: ${response.status}`);
  }

  return response.json();
}
import requests

def reconcile_subscription(subscription_id, dry_run=False):
    url = f"https://api.paygentic.io/v0/subscriptions/{subscription_id}/reconciliations"
    headers = {
        "Authorization": "Bearer sk_live_YOUR_API_KEY",
        "Content-Type": "application/json",
    }
    response = requests.post(url, json={"dryRun": dry_run}, headers=headers)
    response.raise_for_status()
    return response.json()
curl -X POST https://api.paygentic.io/v0/subscriptions/{subscriptionId}/reconciliations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"dryRun": false}'

Previewing with dry run

Pass dryRun: true to compute the same diff without creating any entitlement, grant, or line item. The response is shaped identically to a real run and reports exactly what a subsequent real call would do. Use it to confirm the scope of a change before applying it.
{
  "dryRun": true
}

Response

The response returns a subscriptionReconciliation object describing the outcome:
{
  "object": "subscriptionReconciliation",
  "dryRun": false,
  "features": {
    "added": [
      {
        "featureId": "feat_a1b2c3d4e5f6g7h8",
        "featureKey": "advanced_reporting",
        "billed": true
      }
    ],
    "skipped": [
      {
        "featureId": "feat_i9j0k1l2m3n4o5p6",
        "featureKey": "basic_dashboard"
      }
    ],
    "removed": [
      {
        "featureId": "feat_q7r8s9t0u1v2w3x4",
        "featureKey": "legacy_export"
      }
    ],
    "failed": []
  },
  "lineItems": {
    "created": 1,
    "removed": 1
  }
}

Feature groups

  • added — Features the subscription was missing that were provisioned. A billed: false entry means the feature’s price does not produce a recurring line item, so the feature is entitled but not billed by this reconciliation. Examples include an instant price, a credit-denominated price, or an already-elapsed one-time charge.
  • skipped — Features the subscription already had an active entitlement for. Left unchanged.
  • removed — Features no longer on the plan. Their entitlement was canceled and grants voided so the subscription matches the plan’s current features.
  • failed — Features that could not be fully provisioned. Never billed by this call. Each entry has a coded reason:
    • entitlement_failed — the entitlement itself could not be created.
    • grant_mint_failed — the entitlement was created but its initial metered grant could not be minted. Re-running reconciliation retries the mint safely.

Line items

  • created — Number of features newly billed on the subscription by this reconciliation. A dry-run preview returns the same count as the applied run.
  • removed — Number of features whose billing was removed from the subscription’s upcoming (not-yet-invoiced) invoice because their price is no longer on the plan.
  • syncFailed — Present and true when entitlements were provisioned successfully but the line-item synchronization step failed. Safe to retry.

Status codes

StatusMeaning
201 CreatedReconciliation applied. Feature entitlements match the plan and billing was fully synchronized.
200 OKThe reconciliation did not create a full set of resources. This covers a dryRun preview, a run where one or more features could not be provisioned (billing is withheld until they succeed), or a run whose entitlements provisioned but whose line-item synchronization failed. Safe to retry — the API does not duplicate already-provisioned entitlements or grants.
400 Bad RequestRequest is malformed, or the subscription is not eligible. See error codes below.
404 Not FoundSubscription does not exist or is not visible to your merchant.

Error codes

  • SUBSCRIPTION_NOT_ACTIVE — The subscription is not in the active state. Reconciliation is only available for active subscriptions.
  • SUBSCRIPTION_NOT_V1_BILLING — The subscription is on Legacy Billing (billingVersion: 0). Reconciliation only applies to Standard Billing subscriptions.

Idempotency and retries

Reconciliation is safe to retry:
  • The API detects already-provisioned entitlements and grants and does not duplicate them.
  • If a previous run created an entitlement but failed to mint its initial metered grant, re-running reconciliation retries the mint on the same idempotency key. The mint is a no-op when the grant is already present.
  • If a run returns 200 with lineItems.syncFailed: true, retry to complete billing synchronization.

Rate limits

Reconciliations are limited to 2 requests per second per subscription. Automated retries should include backoff; a 429 Too Many Requests response returns the standard rate-limit error message.