# Report a leaked API key

> Anyone who finds a coop_live_ key can ask Cooper to revoke it. Operated by Avatar 8 LLC (ops@avatar33.com).

HTML version: https://cooperemail.com/docs/leaked-keys · API docs: https://cooperemail.com/docs · Security: https://cooperemail.com/security

## Anyone who finds a key can report it

POST /api/v1/keys/report-leak does not need an API key, an account, or a signed-in owner. A person, a secret scanner, or the developer on another machine can send the key. Cooper does not have a partnership with a scanning vendor or with GitHub secret scanning.

The operator is Avatar 8 LLC (ops@avatar33.com). A vulnerability that is not a leaked key still goes to that address. The policy page is https://cooperemail.com/security.

## What a Cooper key looks like

A live key is the prefix coop_live_ plus 32 base64url characters. Secret scanners can match coop_live_[A-Za-z0-9_-]{32}. Keys are stored as a SHA-256 hash. The report route compares that hash and does not write the raw key.

A string that does not match is accepted and ignored. A scanner can send a batch that mixes Cooper keys with other tokens.

## The request

The body is one object or an array of at most 20 objects. key is required, trimmed, and at most 200 characters. token is an alias of key. source_url is optional, must be http or https, and is at most 2048 characters. url is an alias of source_url. reporter is optional plain text of at most 200 characters and is not stored. Unknown fields, including type and source, are ignored.

Invalid JSON, a missing key, a bad source_url, a reporter that is not plain text, or more than 20 items is HTTP 400. The error object has type, code, message, and param. More than 20 items uses param body.

The route allows 30 requests per hour for each client IP. The next request is HTTP 429 rate_limited with Retry-After, retry_after_seconds, and resets_at.

```
curl -sS -X POST https://cooperemail.com/api/v1/keys/report-leak \
  -H "content-type: application/json" \
  -d '{"key":"coop_live_…","source_url":"https://example.com/paste"}'
```

## The response is the same either way

HTTP 202 and {received:N}. received is how many items were in the body. It is not how many keys were revoked. An unknown key, a key that is already revoked, and a string that is not a Cooper key all return that same 202. The body does not include the key, an account id, or a statement that a key was valid.

## What Cooper does with a match

A key that matches and is not already revoked stops working on the next REST or MCP call. The response is HTTP 401 api_key_revoked. Other keys on the account keep working. revoked_at and leak_reported_at are the time of the report. revoked_reason is leak_reported. leak_source_url is the reported URL, or null. DELETE /api/v1/keys/{id} sets revoked_reason to manual. A row revoked before this field existed stays null.

GET /api/v1/keys and GET /api/v1/keys/{id} include revoked_reason and leak_reported_at. The secret is still omitted. There is no MCP tool for this report, so the key is not written into a chat transcript. The tool catalog is unchanged.

## Owners are told once

When a report revokes a key, each verified owner address on the account gets one email. The same address on more than one inbox is emailed once. The subject is [Security] Cooper API key revoked: and the key name. The body has the name, the prefix, the key id, the time, and the reported URL as plain text. It says to mint a replacement with POST /api/v1/keys using a key that still works, or POST /api/v1/recover with the inbox address when this was the only key. An account with no verified owner gets no email. The same per-owner send limits as other owner mail apply. A second report of a key that is already revoked does not send another email.

key.leak_reported is written to the event log. The payload is key_id, name, key_prefix, inbox_id, revoked_reason, revoked_at, leak_reported_at, and leak_source_url. It does not contain the key. A webhook receives it only when its events list includes key.leak_reported. A webhook created without an events list still receives only message.received.
