# AI API Spend Kill-Switch – OpenAI & Anthropic Budget Guard (`jet_mythology/ai-api-spend-kill-switch`) Actor

Hard spending limit for OpenAI and Anthropic (Claude) APIs. Checks spend via the official Admin APIs on a schedule, alerts Slack/Discord/webhooks, and can automatically disable leaked or runaway API keys.

- **URL**: https://apify.com/jet_mythology/ai-api-spend-kill-switch.md
- **Developed by:** [jet_mythology](https://apify.com/jet_mythology) (community)
- **Categories:** Developer tools, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## AI API Spend Kill-Switch – OpenAI & Anthropic (Claude) budget guard

**A hard spending limit and automatic kill switch for your OpenAI and Anthropic API keys.** Run it on an Apify schedule (for example every 15 minutes): it reads your real spend from the official **Admin APIs**, alerts you on Slack, Discord or any webhook when a limit is crossed, and — if you allow it — **disables the leaked or runaway API keys** before the bill gets worse.

Built after the October 2026 wave of **API key leaks** (keys exposed through hosting environment variables were drained within hours) and after users reported being billed **$515 against a $1 usage limit**. Provider-side limits are soft or delayed; this Actor adds an independent second line of defense.

### What it does

- ✅ **OpenAI spending limit**: today's and month-to-date cost from `GET /v1/organization/costs`, grouped **per project and per API key** — so it can point to the exact key that is burning money.
- ✅ **Anthropic API budget**: today's and month-to-date cost from the Claude Admin API cost report (`GET /v1/organizations/cost_report`), grouped **per workspace**.
- ✅ **Three kinds of limits**: daily org limit, monthly org limit, and an optional **per-key (OpenAI) / per-workspace (Anthropic) daily limit** that catches a single leaked key even while your total looks normal.
- ✅ **Alerts** to Slack incoming webhooks, Discord webhooks, or any HTTPS endpoint (JSON). One alert per breach per day (no spam every 15 minutes), plus an alert each time a key is disabled.
- ✅ **Kill switch** (opt-in):
  - **Anthropic**: offending keys are set to `status: "inactive"` — fully reversible in the Claude Console.
  - **OpenAI**: OpenAI has no documented "pause key" endpoint for project keys, so the offending project keys are **deleted** (`DELETE /v1/organization/projects/{project_id}/api_keys/{key_id}`), or — your choice — the whole project is **archived** (`POST /v1/organization/projects/{project_id}/archive`). **Both are irreversible**: you will create new keys afterwards.
- ✅ **Allowlist / blocklist**: protect your production keys or projects, restrict actions to some projects/workspaces, or name keys to kill first (e.g. keys you know were exposed on a compromised host).
- ✅ **Dry run** to see exactly which keys would be disabled, without touching anything.
- ✅ **Demo mode** with simulated data — run it right now without any key.

### Quick start

1. Create an **Admin key**:
   - OpenAI: *platform.openai.com → Settings → Organization → Admin keys* (Organization Owner only). Normal `sk-proj-...` keys cannot read costs.
   - Anthropic: *Claude Console → Settings → Admin keys* (organization admin). Normal `sk-ant-api...` keys cannot read the cost report.
2. Paste the key(s) into the Actor input (fields are **secret**: encrypted by Apify and never written to the log or dataset).
3. Set a **daily** and/or **monthly** limit in USD. Start in **Alert only** mode.
4. Add your Slack/Discord **webhook URL**.
5. Run once and check the dataset. When you trust the numbers, switch to **Disable keys** with **Dry run** on, then turn dry run off.

#### Schedule it (every 15 minutes)

1. In Apify Console open **Schedules → Create new**.
2. Cron expression: `*/15 * * * *` (every 15 min) — or `0 * * * *` for hourly.
3. **Add Actor** → pick this Actor and the saved input (or create a **Task** with your input first and schedule the task).
4. Optional: in the Actor's **Settings → Notifications**, enable "run failed" emails. With *Mark the run as failed if a provider check fails* on (default), a wrong key or a provider outage will email you.

Provider cost data only updates every few minutes to hours, so running more often than every 10–15 minutes does not add protection.

### Input

| Field | Meaning |
|---|---|
| `openaiAdminKey` / `anthropicAdminKey` | Admin keys (secret). Leave both empty for a demo run. |
| `dailyLimitUsd` | Max spend today (UTC), whole organization, per provider. |
| `monthlyLimitUsd` | Max month-to-date spend (UTC), per provider. |
| `perKeyOrWorkspaceDailyLimitUsd` | Max spend today for a single OpenAI key / Anthropic workspace. When set, only units over this limit are treated as offending ("precise mode"). |
| `mode` | `alert` (default, never touches keys) or `disable` (kill switch). |
| `disableScope` | `offending` = keys (OpenAI) / workspaces (Anthropic) that spent in the breached period; `all` = every active key in scope. |
| `openaiDisableAction` | `delete_keys` (default) or `archive_projects`. |
| `dryRun` | Show what would be disabled, change nothing. |
| `protectedKeyIds`, `protectedProjectOrWorkspaceIds` | Never touched. Use `default` for Anthropic's default workspace. |
| `onlyProjectOrWorkspaceIds` | Only act inside these. |
| `blocklistKeyIds` | Always disabled when any limit is hit. |
| `webhookUrl` | Slack / Discord / generic webhook (secret field). |
| `alertEveryRun` | Re-alert on every run while over the limit (default: once per day per breach). |

### Output

One dataset row per provider per run, plus an `OUTPUT` record in the key-value store with the run summary:

```json
{
  "provider": "openai",
  "checkedAt": "2026-10-22T11:00:00+00:00",
  "todayUsd": 42.8,
  "monthUsd": 46.0,
  "dailyLimitUsd": 20,
  "breaches": [
    {"type": "org_daily", "spendUsd": 42.8, "limitUsd": 20},
    {"type": "unit_daily", "unit": "key_leaked", "spendUsd": 41.7, "limitUsd": 15}
  ],
  "topSpendersToday": [{"unit": "key_leaked", "usd": 41.7}, {"unit": "key_prod", "usd": 1.1}],
  "actions": [{"keyId": "key_leaked", "keyName": "railway-env", "scopeId": "proj_railway", "keyHint": "sk-...lk3d", "result": "deleted"}],
  "alertSent": "HTTP 200",
  "error": null
}
```

Only key IDs, names and the provider's own redacted hints are stored — never key values.

### Pricing

Pay per event:

- **Spend check** — $0.002 per provider checked (one OpenAI + one Anthropic check = $0.004). Every 15 minutes ≈ $0.19/day ≈ $5.80/month for both providers; hourly ≈ $1.50/month.
- **Kill-switch action** — $0.05 per key actually disabled/deleted or project archived. Dry runs and alerts are free.
- Demo runs and failed checks are not charged per event.

That is usually less than a few minutes of a leaked key's spend.

### Important limitations (please read)

- **Best effort, not a guarantee.** Provider billing data **lags** (typically minutes, sometimes hours) and is finalized later. A leaked key can spend money between two checks and before the cost shows up. Use this together with provider-side limits, one key per project, and key rotation — not instead of them.
- Costs are computed in **UTC days/months**, as reported by the providers.
- **OpenAI** deletion and project archiving are **irreversible**. Keep `dryRun` on until you are sure, and allowlist your production keys.
- OpenAI costs that are not tied to an API key (e.g. some org-level line items) count toward the org totals but cannot be attributed to a key (`unattributedTodayUsd`).
- **Anthropic** reports cost per **workspace**, not per key. In `offending` mode every active key of an offending workspace is deactivated. Put each app in its own workspace for precise kills. Org-scoped (principal-bound) keys appear under `default`.
- OpenAI **organization/user keys** and **admin keys** are not touched; only project API keys are.
- Only official, documented endpoints are used: OpenAI `GET /v1/organization/costs`, `GET /v1/organization/projects`, `GET /v1/organization/projects/{id}/api_keys`, `DELETE /v1/organization/projects/{id}/api_keys/{key_id}`, `POST /v1/organization/projects/{id}/archive`; Anthropic `GET /v1/organizations/cost_report`, `GET /v1/organizations/api_keys`, `POST /v1/organizations/api_keys/{id}`. OpenRouter, Google Gemini and others are not supported yet.
- This Actor is not affiliated with OpenAI or Anthropic.

### FAQ

**Why not just use the provider's usage limit?** Provider limits are often soft, delayed, or apply only to some products — users have reported spending far above their configured limit. An independent check that can actually revoke keys closes that gap.

**My key leaked right now — what should I do?** Rotate it immediately in the provider console. Then run this Actor in `disable` mode with the leaked key IDs in `blocklistKeyIds` and schedule it so the next leak is caught automatically.

**Is my admin key safe?** Admin keys are powerful. The input fields are marked secret (encrypted at rest by Apify, hidden in the UI and logs). The Actor only sends them to `api.openai.com` / `api.anthropic.com`. Create a dedicated admin key for this Actor and delete it whenever you stop using it.

# Actor input Schema

## `openaiAdminKey` (type: `string`):

An OpenAI **Admin** key (sk-admin-...), created by an Organization Owner at platform.openai.com > Settings > Organization > Admin keys. A normal project key will not work. Stored encrypted by Apify, never logged. Leave both keys empty to run a demo with simulated data.

## `anthropicAdminKey` (type: `string`):

An Anthropic **Admin** key (sk-ant-admin...), created by an organization admin in the Claude Console > Settings > Admin keys. Stored encrypted by Apify, never logged.

## `dailyLimitUsd` (type: `integer`):

Trigger when today's (UTC) spend for the whole organization exceeds this amount. Applied per provider.

## `monthlyLimitUsd` (type: `integer`):

Trigger when month-to-date (UTC) spend exceeds this amount. Applied per provider.

## `perKeyOrWorkspaceDailyLimitUsd` (type: `integer`):

Optional. Catches one leaked key burning money even when the org total is still below the limit. OpenAI reports cost per API key; Anthropic's cost report is per workspace, so on Anthropic this applies per workspace.

## `mode` (type: `string`):

Start with Alert only. In Disable mode, Anthropic keys are set to status "inactive" (reversible in the Console). OpenAI has no documented way to pause a project key, so OpenAI keys are DELETED (irreversible: you will need to create new keys) or, if you choose it below, their project is archived.

## `disableScope` (type: `string`):

Offending: OpenAI keys / Anthropic workspaces that spent money in the breached period (or, with a per-key/workspace limit set, only those over that limit). All: every active key in scope.

## `openaiDisableAction` (type: `string`):

OpenAI cannot pause a project key through the API. Deleting keys is irreversible; archiving a project is irreversible and blocks all its keys.

## `dryRun` (type: `boolean`):

Recommended for your first runs in Disable mode.

## `protectedKeyIds` (type: `array`):

OpenAI key IDs (key\_...) or Anthropic key IDs (apikey\_...) that must never be disabled.

## `protectedProjectOrWorkspaceIds` (type: `array`):

OpenAI project IDs (proj\_...) or Anthropic workspace IDs (wrkspc\_...; use "default" for the default workspace).

## `onlyProjectOrWorkspaceIds` (type: `array`):

Optional. If set, keys outside these projects/workspaces are never disabled.

## `blocklistKeyIds` (type: `array`):

Key IDs to kill first whenever any limit is exceeded (e.g. keys you know were exposed on a host).

## `webhookUrl` (type: `string`):

Slack incoming webhook and Discord webhook URLs get a readable message; any other URL receives a JSON POST with the full result.

## `alertEveryRun` (type: `boolean`):

Off: one alert per breach per UTC day (plus every time keys are disabled).

## `failOnError` (type: `boolean`):

Lets Apify's own run-failure notifications warn you when a key is wrong or a provider API is down.

## `demoMode` (type: `boolean`):

Runs against simulated Admin API responses so you can see the output and test your webhook. Automatically on when no key is given. Demo runs are not charged per check.

## Actor input object example

```json
{
  "dailyLimitUsd": 10,
  "monthlyLimitUsd": 100,
  "mode": "alert",
  "disableScope": "offending",
  "openaiDisableAction": "delete_keys",
  "dryRun": false,
  "alertEveryRun": false,
  "failOnError": true,
  "demoMode": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `checks` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "dailyLimitUsd": 10,
    "monthlyLimitUsd": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("jet_mythology/ai-api-spend-kill-switch").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "dailyLimitUsd": 10,
    "monthlyLimitUsd": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("jet_mythology/ai-api-spend-kill-switch").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "dailyLimitUsd": 10,
  "monthlyLimitUsd": 100
}' |
apify call jet_mythology/ai-api-spend-kill-switch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jet_mythology/ai-api-spend-kill-switch"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zy9I9HssaJiSAjexM/builds/105SSJHiqzeT08suH/openapi.json
