# Organization Payment Config

This document describes the MCP tools for managing organization payment configurations in Airbyte Cloud. These tools enable reading and updating billing state for organizations, including grace period management, permanent billing waivers, and Orb billing plan automation.

## Overview

The payment config tools provide two operations:

- **`get_organization_payment_config`** (read-only): Retrieve the current payment status, subscription status, grace period, usage category, and Orb billing plan for an organization. No PII or sensitive payment details are returned.
- **`update_organization_payment_config`** (destructive): Set or modify grace periods and/or usage category overrides for an organization. When setting a permanent waiver type, also changes the Orb billing plan and Stigg entitlement plan automatically. Requires [human-in-the-loop approval](#authorization).

## Payment Status State Machine

Organizations have a `payment_status` that follows this state machine:

| Status | Description | Set By |
|--------|-------------|--------|
| `uninitialized` | Default state; customer has not set up billing | Billing automation only |
| `okay` | Billing is healthy; payments are current | Billing automation only |
| `grace_period` | Payment failed; temporary reprieve before disabling | API (from `manual` only) |
| `disabled` | Grace period expired; connections are disabled | Billing automation only |
| `locked` | Manually locked; all connections disabled | API (from any status) |
| `manual` | Manually managed billing; Stripe webhooks are ignored | API (from any status) |

The API only allows setting status to `grace_period` (from `manual`), `manual` (from any), or `locked` (from any). The statuses `uninitialized`, `okay`, and `disabled` are controlled exclusively by billing automation (Stripe/Orb webhooks).

## MCP Tool: `get_organization_payment_config`

### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `organization_id` | string | Yes | -- | The organization UUID |
| `config_api_root` | string | No | `https://cloud.airbyte.com/api/v1` | Config API root URL override |

### Return Value

Returns an `OrganizationPaymentConfigInfo` object with:

- `organization_id`: The organization UUID
- `payment_status`: Current payment status enum value
- `subscription_status`: One of `pre_subscription`, `subscribed`, `unsubscribed`
- `payment_provider_id`: Stripe customer ID (opaque reference, not a payment method)
- `grace_period_end_at`: ISO 8601 datetime if a grace period is active
- `usage_category_overwrite`: `free` or `internal` if set
- `customer_tier`: Tier classification (`TIER_0`, `TIER_1`, or `TIER_2`)
- `tier_warning`: Warning message for sensitive customer tiers (TIER_0/TIER_1)
- `orb_subscription`: Current Orb billing subscription info (when `ORB_API_KEY` is configured), including plan name, external plan ID, status, and dates

## MCP Tool: `update_organization_payment_config`

### Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `organization_id` | string | Yes | -- | The organization UUID |
| `approval_comment_url` | string | Yes | -- | Slack approval record URL from `escalate_to_human` |
| `organization_name` | string | Effectively yes | `None` | Safety check: org name, email, or email domain. Optional in schema but the tool fails fast with valid identifiers if omitted or mismatched |
| `set_grace_period` | string | No | `None` | Grace period action (see [Grace Period Actions](#grace-period-actions)). Mutually exclusive with `set_permanent_waiver_type` |
| `set_grace_period_reason` | string | No | `None` | Reason for the grace period change. Required when setting/extending |
| `set_permanent_waiver_type` | string | No | `None` | Permanent billing waiver: `free` (partner/non-paying), `internal` (Airbyte-internal), or `none` (remove waiver). Mutually exclusive with `set_grace_period`. See [Permanent Waiver Type](#permanent-waiver-type) |
| `set_permanent_waiver_reason` | string | No | `None` | Reason for setting the permanent billing waiver. Required when `set_permanent_waiver_type` is provided |
| `config_api_root` | string | No | `https://cloud.airbyte.com/api/v1` | Config API root URL override |

### Grace Period Actions

The `set_grace_period` parameter accepts three forms:

| Input | Example | Behavior |
|-------|---------|----------|
| Date (`YYYY-MM-DD`) | `"2026-05-15"` | Sets grace period end to 11:59 PM Pacific on that date |
| Integer (days) | `"14"` | Sets grace period end to 11:59 PM Pacific, N days from today (Pacific Time) |
| `"cancel"` | `"cancel"` | Terminates the current grace period (sets status to `manual`) |

### Return Value

Returns an `OrganizationPaymentConfigUpdateResult` object with:

- `success`: Whether the update completed successfully
- `message`: Human-readable description of the outcome
- `organization_id`: The organization UUID
- `payment_status`: The resulting payment status after the update
- `grace_period_end_at`: The grace period end date if applicable
- `permanent_waiver_type`: The permanent billing waiver after the update (`free`, `internal`, or `None`)
- `orb_plan_change`: Result of the Orb plan change (e.g., `Changed from 'Cloud Self-Serve' to 'airbyte-partner'`), or `None` if not attempted
- `entitlement_plan_change`: Result of the Stigg entitlement plan change (e.g., `Changed to PARTNER`), or `None` if not attempted
- `customer_tier`: Tier classification
- `tier_warning`: Warning message for sensitive tiers

### Organization Name Validation

The `organization_name` parameter is a safety check to prevent accidental modifications to the wrong organization. The tool looks up the organization via the Config API and verifies the input against:

- The organization's literal name (e.g., `"Acme Corp"`)
- The organization's email address (e.g., `"billing@acme.com"`)
- The organization's email domain (e.g., `"acme.com"`)

If `organization_name` is omitted or does not match any of the above, the tool returns an error with all valid identifiers so the caller can verify and retry. The error message instructs the agent to confirm with its user if there is any doubt.

## Authorization

All calls to `update_organization_payment_config` require human-in-the-loop approval:

1. **Call `escalate_to_human`** with `approval_requested=True` and a summary of the planned action
2. **Wait for approval** — a human clicks "Approve" in the `#human-in-the-loop` Slack channel
3. **Pass the approval record URL** as `approval_comment_url` to the update tool

Additionally, the `AIRBYTE_INTERNAL_ADMIN_FLAG=airbyte.io` environment variable must be set.

## Grace Period Workflow

### Setting a Grace Period

To set a grace period for an organization (e.g., extending access for a customer with a billing issue):

```bash
# Step 1: Verify current status
poe mcp-tool-test get_organization_payment_config '{
  "organization_id": "<org-uuid>"
}'

# Step 2: Set the grace period (requires prior escalate_to_human approval)
poe mcp-tool-test update_organization_payment_config '{
  "organization_id": "<org-uuid>",
  "organization_name": "<org-name-or-email-or-domain>",
  "approval_comment_url": "<slack-approval-url>",
  "set_grace_period": "2026-05-15",
  "set_grace_period_reason": "Grace period extension per #ask-finance request"
}'
```

If the organization is not already in `manual` status, the tool automatically transitions to `manual` first before setting the grace period. See [Known Limitations](#known-limitations) for risks associated with this two-step process.

### Grace Period Lifecycle

Once a grace period is active:

- **Customer pays before the end date** → Stripe webhook transitions the org to `okay` and terminates the grace period
- **End date is reached without payment** → A Temporal workflow timer fires and transitions the org to `disabled`, disabling all connections

### Canceling a Grace Period

```bash
poe mcp-tool-test update_organization_payment_config '{
  "organization_id": "<org-uuid>",
  "organization_name": "<org-name>",
  "approval_comment_url": "<slack-approval-url>",
  "set_grace_period": "cancel",
  "set_grace_period_reason": "Grace period no longer needed"
}'
```

This sets the organization to `manual` status, terminating the grace period.

## Permanent Waiver Type

The `set_permanent_waiver_type` parameter sets a permanent billing waiver for an organization:

| Value | Description | Use Case |
|-------|-------------|----------|
| `free` | Non-paying account; usage is not billed | Partner accounts, sponsored accounts |
| `internal` | Airbyte-internal account | Internal testing, dogfooding |
| `none` | Remove an existing waiver | Reverting a partner/internal designation |

This is mutually exclusive with `set_grace_period` — only one may be provided per call.

When `ORB_API_KEY` is configured, setting `set_permanent_waiver_type` to `free` or `internal` also schedules an immediate Orb plan change:

| Waiver Type | Orb Plan |
|-------------|----------|
| `free` | Airbyte Partner (`airbyte-partner`) |
| `internal` | Airbyte Internal (`airbyte-internal`) |
| `none` | No Orb change (plan remains as-is) |

Additionally, the tool updates the Stigg entitlement plan to remove the trial paywall:

| Waiver Type | Stigg Entitlement Plan |
|-------------|------------------------|
| `free` | `PARTNER` |
| `internal` | `PARTNER` |
| `none` | No change |

The entitlement plan controls frontend feature gating and trial paywall visibility. Without this step, the "30 day trial expired" modal may still appear even if billing is correctly configured.

Both the Orb plan change and entitlement plan update are best-effort: if either fails, the payment config update still succeeds and the error is reported in the response.

```bash
# Mark an org as a partner (non-paying) account — also changes Orb plan
poe mcp-tool-test update_organization_payment_config '{
  "organization_id": "<org-uuid>",
  "organization_name": "<org-name-or-email-or-domain>",
  "approval_comment_url": "<slack-approval-url>",
  "set_permanent_waiver_type": "free",
  "set_permanent_waiver_reason": "Partner account per #ask-finance approval"
}'
```

## Known Limitations

### Two-Step Grace Period Transition ([#749](https://github.com/airbytehq/airbyte-ops-mcp/issues/749))

The Airbyte Cloud Config API only allows setting `grace_period` from `manual` status. For organizations in any other status (e.g., `disabled`), the tool must perform two sequential API calls:

1. Set status to `manual`
2. Set status to `grace_period` with the end date

If step 1 succeeds but step 2 fails (network error, validation rejection, etc.), the organization is left in `manual` status. In `manual` status, Stripe webhooks are ignored, meaning billing automation is bypassed until the status is corrected.

The tool reports this partial failure explicitly in its response, indicating the organization is now in `manual` status and prompting corrective action. However, the underlying risk should be addressed by the backend team by allowing direct `grace_period` transitions from any status.

## Orb Billing Integration

The tools integrate with the [Orb](https://www.withorb.com/) billing platform to surface subscription state and automate plan changes. Every Cloud customer syncing data must be enrolled in an Orb plan; without one, usage will not appear in the UI and syncs may be blocked.

### Configuration

Set the `ORB_API_KEY` environment variable to enable Orb integration. Without it:
- `get_organization_payment_config` returns `orb_subscription: null`
- `update_organization_payment_config` fails with an error when `set_permanent_waiver_type` is `free` or `internal` (the Orb plan change is required, not optional)

Optionally override the Orb plan IDs via environment variables:
- `ORB_PARTNER_PLAN_ID` (default: `VwkusX467BUccNtf` — Airbyte Partner)
- `ORB_INTERNAL_PLAN_ID` (default: `KVa2sbZUrjyJ9NZr` — Airbyte Internal)

### Orb API Key Permissions

When creating an Orb API key, the following minimum permissions are required:

| Resource | Permission | Why |
|----------|------------|-----|
| Subscriptions | Read + Write | List subscriptions by `external_customer_id`; schedule plan changes |
| Customers & Credits | Read | Resolve customer lookups and verify customer existence |
| Plans | Read | Validate target plan IDs |

All other resources (Invoices, Billable Metrics, Items, Coupons, Events, Credit Notes, Alerts, Prices, Licenses, Event Ingestion, Payment Methods) can be set to `None`.

### How it works

Orb subscriptions are looked up by `external_customer_id`, which is the Airbyte organization UUID. The tool finds the active subscription and either reports its current plan (read) or schedules an immediate plan change (update).

The plan change uses `POST /subscriptions/{id}/schedule_plan_change` with `change_option=immediate`, which takes effect right away. The Orb API returns HTTP 200 on success.

### Orb Plan Mapping

| Waiver Type | Orb Plan ID | Orb Plan Name |
|-------------|------------|---------------|
| `free` (partner) | `VwkusX467BUccNtf` | Airbyte Partner |
| `internal` | `KVa2sbZUrjyJ9NZr` | Airbyte Internal |
| `none` (remove waiver) | No Orb change | Plan remains as-is |

### Orb Plan Change Results

When `ORB_API_KEY` is configured and a waiver type change is requested, the `orb_plan_change` field in the response reports the outcome:

| Result | Meaning |
|--------|---------|
| `Changed from '<old>' to '<new>'` | Plan change succeeded |
| `Already on plan '<plan_id>'` | No change needed; already on the target plan |
| `Skipped: no active Orb subscription` | Customer has no active subscription in Orb |
| `Failed: <error>` | Orb API error; payment config was still updated |

If `ORB_API_KEY` is not configured and `set_permanent_waiver_type` is `free` or `internal`, the tool returns `success: false` before making any changes.

## Customer Billing Operations

This section describes end-to-end workflows for common billing operations.

### Onboarding a Partner Account

To designate an organization as a non-paying partner:

1. **Verify current state** — read the org's payment config and Orb plan:
   ```bash
   poe mcp-tool-test get_organization_payment_config '{"organization_id": "<org-uuid>"}'
   ```
   Confirm `payment_status`, `usage_category_overwrite`, and `orb_subscription.external_plan_id`.

2. **Get approval** — call `escalate_to_human` with a summary of the planned change.

3. **Set the permanent waiver** — this sets `usage_category_overwrite=free`, `payment_status=manual`, changes the Orb plan to `airbyte-partner`, and updates the Stigg entitlement plan to `PARTNER`:
   ```bash
   poe mcp-tool-test update_organization_payment_config '{
     "organization_id": "<org-uuid>",
     "organization_name": "<org-name-or-email-or-domain>",
     "approval_comment_url": "<slack-approval-url>",
     "set_permanent_waiver_type": "free",
     "set_permanent_waiver_reason": "Partner account per <ticket/request>"
   }'
   ```

4. **Verify** — re-read the payment config to confirm the waiver and Orb plan are set.

### Onboarding an Internal Account

Same as partner onboarding, but use `set_permanent_waiver_type: "internal"`. The Orb plan will be set to `airbyte-internal`.

### Reverting a Waiver

To remove a partner or internal designation:

```bash
poe mcp-tool-test update_organization_payment_config '{
  "organization_id": "<org-uuid>",
  "organization_name": "<org-name>",
  "approval_comment_url": "<slack-approval-url>",
  "set_permanent_waiver_type": "none",
  "set_permanent_waiver_reason": "Reverting partner status per <ticket>"
}'
```

This removes the `usage_category_overwrite` but does **not** change the Orb plan. The Orb plan must be manually changed back to a paying plan if needed.

### Prerequisites

The full partner/internal onboarding involves several billing systems:

| Step | System | Automated? |
|------|--------|------------|
| Set `payment_status` to `manual` | Airbyte Cloud Config API | Yes (via `update_organization_payment_config`) |
| Set `usage_category_overwrite` to `free`/`internal` | Airbyte Cloud Config API | Yes (via `update_organization_payment_config`) |
| Change Orb plan to partner/internal | Orb API | Yes (via `update_organization_payment_config`, requires `ORB_API_KEY`) |
| Change entitlement plan to `PARTNER` | Airbyte Cloud Config API (`/entitlements/update_plan`) | Yes (via `update_organization_payment_config`) |
| Ensure Stripe customer exists with billing address | Stripe | No (manual; invoices fail to sync without this) |

The Stripe customer setup is not automated by this tool. A Stripe customer ID with billing/shipping address must exist for invoices to sync correctly, even for non-paying accounts.

## Related Resources

- [Customer Tiers](./customer-tiers.md) — tier classification and tier-aware tool behavior
- [Issue #749](https://github.com/airbytehq/airbyte-ops-mcp/issues/749) — tracking the two-step transition risk
- [Orb API Reference](https://docs.withorb.com/api-reference) — Orb billing platform documentation
