API Docs/Agentic Prompt

Agentic Prompt

If you're building with an AI coding agent (Claude Code, Cursor, Windsurf, a custom agent, etc.) and want it to integrate the Keeper API, paste the block below into your agent's system prompt or initial instructions. It is self-contained — it covers the full async workflow, authentication, idempotency, polling, error handling, and the shape of the CSV output so the agent can write correct code on the first try without re-reading the docs.

Keep your API key away from the agent

Treat your Keeper API key like a production database password. The goal is that the model never sees the key's value, in any message, tool call, or file it reads.

  • Do not paste the key into the chat. Not in the system prompt, not in a user message, not in a .env file you show the model.
  • Set the key as an environment variable in the shell / container / CI runner where the agent's code-execution tool runs — e.g. export KEEPER_API_KEY=kh_live_... in a terminal the model does not have stdout access to, or as a secret in your CI/hosting provider.
  • The agent references the key by name, never by value. Generated code should read process.env.KEEPER_API_KEY / os.environ["KEEPER_API_KEY"] / $KEEPER_API_KEY — the literal string kh_live_... must never appear in code, logs, prompts, or commit history.
  • Do not echo, print, or log the key in any command the agent runs. If you need to check the key is set, check its length (echo ${#KEEPER_API_KEY}), not its value.
  • Do not commit .env files. Add .env to .gitignore before the agent writes any code.
  • Rotate immediately if a key ever appears in a chat transcript, a log, or a commit — assume it is compromised.

The prompt below is written so a well-behaved agent will naturally follow these rules. Keep it that way: don't edit the prompt to include the key literal "just this once."

The prompt

markdown
You are integrating with the Keeper Health API (https://api.keeperhealth.com). Keeper returns negotiated healthcare rates pulled from payer machine-readable files. The API is asynchronous: you submit a search, poll for completion, then download a gzipped CSV from a pre-signed URL. Follow these rules exactly.

## Authentication and API key handling (READ FIRST)

The API key is a secret. You must never see or handle its value.

- The key lives in the environment variable `KEEPER_API_KEY`, set outside this conversation.
- In generated code, reference it by name only: `process.env.KEEPER_API_KEY`, `os.environ["KEEPER_API_KEY"]`, `$KEEPER_API_KEY` in shell, etc. The literal value (starts with `kh_live_` or `kh_test_`) must NEVER appear in code, comments, logs, commit messages, file contents, or any message you output.
- Do not run commands that print the key: no `echo $KEEPER_API_KEY`, no `printenv KEEPER_API_KEY`, no `curl -v` that dumps headers to stdout, no `console.log(headers)`, no writing the key to a file.
- Do not ask the user to paste the key into this chat. If the env var is missing, tell the user to set it in their shell/CI secrets and stop — do not work around it.
- Do not commit `.env` files. Ensure `.env` is in `.gitignore` before writing code that loads from it.
- The pre-signed `download_url` returned when a job completes is the only URL that does NOT take the Authorization header — it is already signed. Do not attach the bearer token to it.

Every request to `/v1/*` must include the header `Authorization: Bearer <KEEPER_API_KEY>`, where the value comes from the environment at runtime.

## Workflow — three steps, in order

### Step 1: POST /v1/searches — submit the job

Request shape:

- Method: `POST`
- URL: `https://api.keeperhealth.com/v1/searches`
- Headers:
  - `Authorization: Bearer $KEEPER_API_KEY` (value sourced from env at runtime)
  - `Content-Type: application/json`
  - `Idempotency-Key: <stable-string>`  ← REQUIRED in production. Searches are billable and long-running; a retry without this key creates a duplicate job and duplicate cost. Use a deterministic key tied to the logical request (e.g. `"nightly-rates-2026-04-14"`), not a random UUID per retry. Same key within 24h returns the original `job_id`.
- Body fields:
  - `npis`: `integer[]`, required, 1–1000 ten-digit NPIs.
  - `billing_codes`: `string[]`, required, 1–100 CPT/HCPCS codes.
  - `fee_schedules`: `object[]`, required, 1–20 items of `{ "payer": string, "plan": string }`.
  - `state`: optional two-letter US state code (e.g. `"CA"`) or `"ALL"`. Default `"ALL"`.
  - `testing`: optional boolean. Leave `false` in production.

Example request (note the env var — never substitute the literal key):

```bash
curl -X POST https://api.keeperhealth.com/v1/searches \
  -H "Authorization: Bearer $KEEPER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-weekly-rates-2026-04-14" \
  -d '{
    "npis": [1144218512, 1234567890],
    "billing_codes": ["99213", "99214"],
    "fee_schedules": [
      {"payer": "Cigna", "plan": "PPO"},
      {"payer": "UnitedHealthcare", "plan": "Choice Plus"}
    ],
    "state": "CA",
    "testing": false
  }'
```

Expected response: `202 Accepted`

```json
{
  "job_id": "6f4245de-4b44-4bb6-aaae-f11c0d4f45c0",
  "status": "queued",
  "created_at": "2026-04-11T09:39:35Z",
  "status_url": "https://api.keeperhealth.com/v1/searches/6f4245de-4b44-4bb6-aaae-f11c0d4f45c0"
}
```

Store `job_id`. Do NOT re-POST on network failures — poll instead, or retry the POST with the SAME `Idempotency-Key`.

### Step 2: GET /v1/searches/{job_id} — poll for completion

Request shape:

- Method: `GET`
- URL: `https://api.keeperhealth.com/v1/searches/{job_id}`
- Headers:
  - `Authorization: Bearer $KEEPER_API_KEY`

Example request:

```bash
curl https://api.keeperhealth.com/v1/searches/6f4245de-4b44-4bb6-aaae-f11c0d4f45c0 \
  -H "Authorization: Bearer $KEEPER_API_KEY"
```

Example response while still running:

```json
{
  "job_id": "6f4245de-4b44-4bb6-aaae-f11c0d4f45c0",
  "status": "processing",
  "created_at": "2026-04-11T09:39:35Z"
}
```

Example response when finished:

```json
{
  "job_id": "6f4245de-4b44-4bb6-aaae-f11c0d4f45c0",
  "status": "completed",
  "created_at": "2026-04-11T09:39:35Z",
  "completed_at": "2026-04-11T09:41:02Z",
  "row_count": 48213,
  "download_url": "https://storage.keeperhealth.com/results/6f4245de-....csv.gz?X-Amz-Signature=..."
}
```

`status` is one of: `queued`, `processing`, `completed`, `failed`.

Polling rules (follow these, do not invent your own):

- Poll every 5 seconds for the first minute, then every 15 seconds after that.
- Cap total wait at ~15 minutes. Most jobs finish in under 2 minutes; large ones can take longer.
- If `status` is `queued` or `processing`: wait the interval and poll again.
- If `status` is `completed`: use `download_url` (pre-signed, expires ~1 hour). Proceed to step 3.
- If `status` is `failed`: the response contains an `error` object with `type` and `message`. Surface the message; do not retry automatically.
- If the GET itself returns `429`: back off per the `Retry-After` header.

### Step 3: Download the CSV

`GET` the `download_url` with NO Authorization header — it is a pre-signed URL.

```bash
curl -o results.csv.gz "<download_url from step 2>"
gunzip results.csv.gz
```

The body is a gzipped CSV (`Content-Encoding: gzip`, filename ends in `.csv.gz`). Decompress before parsing.

## CSV output schema

The decompressed CSV has a header row. Key columns:

- `npi` — provider NPI (10-digit)
- `provider_name`
- `billing_code` — CPT/HCPCS
- `billing_code_description`
- `payer` — e.g. `Cigna`
- `plan` — e.g. `PPO`
- `negotiated_rate` — dollar amount as a decimal string
- `negotiated_type` — e.g. `negotiated`, `fee schedule`, `percentage`
- `billing_class``professional` or `institutional`
- `service_code` — place-of-service code(s)
- `expiration_date` — ISO date
- `last_updated_on` — ISO date the rate was last seen in the source MRF

Parse by header name, not column index.

## Error envelope

All errors on `/v1/*` return JSON of the form:

```json
{ "error": { "type": "validation_error", "message": "...", "details": [...] } }
```

Handle these `error.type` values:

- `validation_error` (400) — a field failed validation. Fix the request body; do not retry unchanged.
- `invalid_request` (400) — malformed JSON or malformed `Idempotency-Key`. Fix and resend.
- `unauthorized` (401) — missing/bad API key. Stop and surface to the user. Do NOT print or ask for the key.
- `forbidden` (403) — key revoked or org inactive. Stop and surface to the user.
- `not_found` (404) — `job_id` does not exist or belongs to another org.
- `rate_limited` (429) — back off per `Retry-After`, then retry.
- `enqueue_failed` (503) — transient; retry with the SAME `Idempotency-Key` after `Retry-After`.
- `internal_error` (500) — transient; retry once, then surface to the user.

## Hard limits per request

- `npis`: ≤ 1000
- `billing_codes`: ≤ 100
- `fee_schedules`: ≤ 20

If the user asks for more, split into multiple searches (each with its own stable `Idempotency-Key`) and concatenate results after download.

## What "done" looks like

A correct implementation:

1. Reads `KEEPER_API_KEY` from the environment — never as a literal in code, prompts, or logs.
2. POSTs with `Idempotency-Key` set to a deterministic string for the logical request.
3. Polls `GET /v1/searches/{job_id}` on a 5s-then-15s cadence until `status` is `completed` or `failed`, capped at ~15 minutes.
4. Downloads `download_url` without the auth header, decompresses the gzip, parses the CSV by header name.
5. Handles every error `type` above — never silently swallows a failure, never retries a `validation_error`, never retries a POST without the same idempotency key.
6. Never prints, logs, commits, or otherwise surfaces the API key's value.

Write the integration in the language the user is working in. Prefer the standard HTTP client for that language. Do not invent a Keeper SDK — there isn't one; call the REST API directly.

How to use it

  1. Set KEEPER_API_KEY in the environment where your agent's code-execution tool runs (shell export, Docker env, CI secret, hosting provider secret manager). Do not paste the key into the chat.
  2. Add .env to .gitignore if your agent will create one.
  3. Copy everything inside the fenced block above (from You are integrating... through ...call the REST API directly.).
  4. Paste it into your agent's system prompt, a CLAUDE.md / .cursorrules / equivalent rules file, or the initial message you send the agent.
  5. Ask the agent to build the integration in whatever language and framework your project uses — it now has everything it needs, without ever seeing the key's value.

If you want a minimal reference implementation to compare the agent's output against, see the Quickstart and Code Examples pages.

Keeper Health API v1 · Questions? company@keeperhealth.com