# AGENTS.md - Potarix Enricher

Instructions for AI coding agents (Claude Code, Cursor, Windsurf, Codex, etc.) on how to use the Potarix Enricher API.

## What this is

Potarix Enricher is a small REST API for company-data enrichment. Six billable endpoints, one auth header, JSON in and JSON out. Use it to:

- Resolve a company name to its official website (`/find-website`)
- Find a person's verified work email (`/find-email/person`)
- Recover a decision-maker's email by role at a domain (`/find-email/decision-maker`)
- Turn a LinkedIn profile URL into a verified email (`/find-email/linkedin`)
- Scrape a company-wide email roster from a domain (`/find-email/company`)
- Run the full enricher in one call: website + decision-makers + company emails (`/find-all`)

Homepage: https://enricher.potarix.com
OpenAPI spec: https://enricher.potarix.com/openapi.yaml (also in this repo at `openapi.yaml`)
ChatGPT Actions import URL: https://enricher.potarix.com/openapi.yaml

## What an autonomous agent can do end to end

No human needed except a one-time card entry. The full loop:

1. **Discover** - read this guide, the OpenAPI spec (`/openapi.yaml`), the agent card (`/.well-known/agent-card.json`), the MCP tool surface (`/.well-known/mcp.json`), or auth metadata (`/.well-known/agent-auth`).
2. **Sign up** - `POST /auth/signup` with `{ email, password }`. Returns a `ptk_live_` key plus 25 free trial credits in one call.
3. **Authenticate** - send `Authorization: Bearer ptk_live_...` on every later call. (Existing account? `POST /auth/api-keys`.)
4. **Enrich** - call `/find-website`, `/find-email/person`, `/find-email/decision-maker`, `/find-email/linkedin`, `/find-email/company`, or `/find-all`. Check `/credits` first for a batch.
5. **Pay** - when trial credits run low, `POST /billing/checkout` once to capture a card (hand the Stripe URL to a human), then `POST /billing/topup` silently for every refill after that. A `402` is the signal to top up and retry.

Everything except step 5's first card capture is fully headless. Auth reference: `auth.md` (https://enricher.potarix.com/auth.md).

## Base URL

```
https://api.potarix.com/enricher
```

All endpoints are `POST` with a JSON body unless noted. `/me`, `/credits`, and `/billing/products` are `GET`.

## Auth

Every billable and account endpoint takes a bearer token: an API key that starts with `ptk_live_`.

```
Authorization: Bearer ptk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

You can mint a key headlessly (no browser):

- **New account:** `POST /auth/signup` with `{ "email", "password" }` returns the key once plus 25 free trial credits.
- **Existing account:** `POST /auth/api-keys` with `{ "email", "password" }` returns a fresh key once.

The key is shown ONCE. Store it immediately (env var or secret store). Never hardcode it in committed source.

## Codex and ChatGPT notes

- Codex reads this root `AGENTS.md` automatically when this repository is open. Keep it concise and keep the API examples current.
- ChatGPT does not execute API calls from `AGENTS.md` by itself. For a Custom GPT / GPT Action, import `https://enricher.potarix.com/openapi.yaml` and configure API key auth as a bearer token in the `Authorization` header.
- For web crawlers and browsing agents, `https://enricher.potarix.com/llms.txt` links to this guide and the OpenAPI spec.

## The full headless flow

```bash
# 1. Sign up and get an API key in one call (grants 25 free trial credits).
curl -s -X POST https://api.potarix.com/enricher/auth/signup \
  -H "Content-Type: application/json" \
  -d '{ "email": "you@company.com", "password": "a-strong-password", "label": "my-agent" }'
# -> { "api_key": "ptk_live_...", "key_prefix": "ptk_live_...", "credits_remaining": 25, ... }
# (If the account already exists you get 409 -> use POST /auth/api-keys instead.)

# Save the key for the rest of the session.
export PTK="ptk_live_..."

# 2. Check balance before spending (GET, lighter than /me).
curl -s https://api.potarix.com/enricher/credits -H "Authorization: Bearer $PTK"
# -> { "credits_remaining": 25, "total_purchased": 0, ... }

# 3. Run a lookup.
curl -s -X POST https://api.potarix.com/enricher/find-website \
  -H "Authorization: Bearer $PTK" -H "Content-Type: application/json" \
  -d '{ "company_name": "Stripe Inc." }'
# -> { "website_url": "https://stripe.com", "confidence": 0.99, "cached": false, "credits_remaining": 23 }
```

## Endpoints + credit costs

Credits are floored at worst-case provider cost so a hit never loses money. A whiffed lookup (no result) costs **zero**. A repeat lookup of the same input by the same account is served from cache for **free**.

| POST endpoint | Body (minimum) | Returns | Cost on hit |
|---|---|---|---|
| `/find-website` | `{ "company_name": "Acme Inc" }` (optional `context`) | `website_url`, `source`, `confidence` | 2 credits |
| `/find-email/person` | `{ "full_name": "Jane Doe", "domain": "acme.com" }` | `email`, `email_status`, `source` | 25 credits |
| `/find-email/decision-maker` | `{ "domain": "acme.com", "decision_maker_category": "ceo" }` | `name`, `email`, `linkedin_url`, `job_title` | 25 credits |
| `/find-email/linkedin` | `{ "linkedin_url": "https://www.linkedin.com/in/janedoe" }` | `email`, `name`, `job_title` | 10 credits |
| `/find-email/company` | `{ "domain": "acme.com" }` | `emails[]` (up to 500), `count` | 25 credits |
| `/find-all` | `{ "company_name": "Acme Inc" }` (optional `context`, `dm_categories`, `skip_company_emails`) | website + decision-makers + company emails | sum of the sub-calls that hit |

Account + billing (not billable):

| Endpoint | Method | Purpose |
|---|---|---|
| `/auth/signup` | POST | New account + first key (no auth). |
| `/auth/api-keys` | POST | Mint a key for an existing account (no auth, email+password). |
| `/me` | GET | Profile, balance, `has_saved_card`, key count. |
| `/credits` | GET | Balance only (lighter than `/me`). |
| `/billing/products` | GET | Credit pack tiers (no auth). |
| `/billing/checkout` | POST | Stripe Checkout URL to add a card (one browser trip). |
| `/billing/topup` | POST | Silent off-session top-up once a card is on file. |

### Request-body details

- `/find-email/person`: supply either `first_name` + `last_name` **or** `full_name`; and either `domain` (preferred) **or** `company_name`.
- `/find-email/decision-maker`: `decision_maker_category` is one of `ceo`, `sales`, `operations`, `buyer`, `finance`, `hr`, `marketing`.
- `/find-website` / `/find-all`: pass `context` (e.g. `"company that raised funding"`) to disambiguate namesakes. Providing `context` bypasses the shared cache for that lookup.
- `/find-all`: `dm_categories` defaults to `["ceo", "sales", "operations"]`, max 6. Set `skip_company_emails: true` to skip the roster scrape and save 25 credits.

## Example calls

```bash
# Decision-maker email by role (25 credits on hit).
curl -s -X POST https://api.potarix.com/enricher/find-email/decision-maker \
  -H "Authorization: Bearer $PTK" -H "Content-Type: application/json" \
  -d '{ "domain": "stripe.com", "decision_maker_category": "ceo" }'

# Person email from name + domain (25 credits on hit).
curl -s -X POST https://api.potarix.com/enricher/find-email/person \
  -H "Authorization: Bearer $PTK" -H "Content-Type: application/json" \
  -d '{ "full_name": "Patrick Collison", "domain": "stripe.com" }'

# LinkedIn URL to email (10 credits on hit).
curl -s -X POST https://api.potarix.com/enricher/find-email/linkedin \
  -H "Authorization: Bearer $PTK" -H "Content-Type: application/json" \
  -d '{ "linkedin_url": "https://www.linkedin.com/in/patrickcollison" }'

# Full pass: website + ceo/sales emails, skip the company roster.
curl -s -X POST https://api.potarix.com/enricher/find-all \
  -H "Authorization: Bearer $PTK" -H "Content-Type: application/json" \
  -d '{ "company_name": "Stripe Inc.", "dm_categories": ["ceo", "sales"], "skip_company_emails": true }'
```

Python:

```python
import os, requests

BASE = "https://api.potarix.com/enricher"
HEADERS = {"Authorization": f"Bearer {os.environ['PTK']}", "Content-Type": "application/json"}

r = requests.post(f"{BASE}/find-website", json={"company_name": "Stripe Inc."}, headers=HEADERS)
data = r.json()
print(data["website_url"], "credits left:", data["credits_remaining"])
```

## Billing note

- Pricing is pay-per-lookup: $0.01 per credit. Packs are `1k` ($10), `5k` ($50), `25k` ($250). Credits never expire.
- New accounts get **25 free trial credits** on signup. No subscription, no monthly minimum.
- A card cannot be entered headlessly. Call `POST /billing/checkout` with `{ "tier_key": "5k" }` once and hand the returned Stripe URL to a human to enter a card. After that, top-ups are silent: `POST /billing/topup` with a `tier_key` charges the saved card and adds credits immediately.
- If `/billing/topup` returns `402` with `error: "no_saved_card"`, do a `/billing/checkout` first. If it returns `status: "requires_action"` with a `next_action_url`, hand that URL to a human to confirm 3DS/SCA.

## Response + error conventions

- Every billable response includes `cached` (bool) and `credits_remaining` (int).
- `200` on a successful lookup. `/find-website` and `/find-all` return `200` even with no result (`website_url: null`); `/find-all` reports partial failures in a non-fatal `errors[]` array.
- `400` bad/missing params. `401` missing or invalid key. `402` insufficient credits (object has `credits_remaining` and the credits needed) - top up and retry. `404` no email found across all providers (not charged). `500` lookup error.

## Rules for agents

1. Read the key from an env var or secret store. Do not commit `ptk_live_` keys.
2. Call `/credits` (or `/me`) before a batch to confirm you can afford it; one website lookup is 2 credits, each email lookup is 10 to 25.
3. On `402`, call `/billing/topup` (if a card is saved) then retry; otherwise surface the `/billing/checkout` URL to the human.
4. Cache hits and whiffs are free, so safe-retry is cheap; do not pre-dedupe aggressively on the client.
5. Prefer `domain` over `company_name` for email lookups when you have it. Use `/find-all` to cut N calls down to one per company.

Full machine-readable contract: `openapi.yaml` in this repo. Short summary for crawlers: `public/llms.txt` (served at https://enricher.potarix.com/llms.txt).
