# Agent API guide

One page, fetchable as plain markdown, covering everything an agent (or the
person wiring one) needs to integrate. Base URL: `https://api.counselorai.app`.
Machine-readable map: [/llms.txt](/llms.txt).

Two products share one account system, one key format, one credit balance:

- **counselorai** at `/v1`: financial-aid appeal letters, recommendation
  letters, award-letter analysis, FAFSA checklists, scholarship matching,
  translation. The same engine the CounselorAI app runs.
- **outputgate** at `/gate/v1`: a final check for any generated text before a
  human sees it (`check_output`), and a minimal rewrite that clears the
  findings (`fix_output`).

## 1. Sign up (no human required)

```
curl -X POST https://api.counselorai.app/v1/accounts \
  -H 'Content-Type: application/json' \
  -d '{"name": "My Agent", "email": "you@example.com"}'
```

Returns a **test key** (`bm_test_...`), shown once. Test keys run every tool
against realistic fixtures, hit every error branch, and never bill. Signup is
throttled per IP per day.

To go live: buy credits, then mint a live key.

```
curl -X POST https://api.counselorai.app/v1/credits/checkout \
  -H 'Authorization: Bearer bm_test_...' -H 'Content-Type: application/json' \
  -d '{"pack": "starter"}'          # returns a Stripe Checkout URL

curl -X POST https://api.counselorai.app/v1/keys \
  -H 'Authorization: Bearer bm_test_...'   # after the purchase settles; returns bm_live_..., shown once
```

`GET /v1/credits/checkout` lists the packs. 1 credit = $0.001. Balance any
time: `GET /v1/me`.

## 2. Discover tools

Live prices are in every manifest; a discounted account sees its own prices
over MCP.

| Surface | counselorai | outputgate |
|---|---|---|
| REST manifest | `GET /v1/tools` | `GET /gate/v1/tools` |
| OpenAPI 3.1 | `GET /v1/openapi.json` | `GET /gate/v1/openapi.json` |
| MCP (Streamable HTTP) | `POST /mcp` | `POST /gate/mcp` |
| A2A 1.0 agent card | `GET /.well-known/agent-card.json` | Not available |
| A2A 1.0 tasks | `POST /a2a/v1` | Not available |
| Legacy A2A 0.2 | `GET /.well-known/agent.json`, `POST /a2a` | `GET /gate/agent.json`, `POST /gate/a2a` |

## 3. Quote before you spend (free)

```
curl -X POST https://api.counselorai.app/v1/tools/appeal/quote \
  -H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
  -d '{"keyDetails": "household lost its primary income in March"}'
```

Returns the exact credit hold a real call would take at your price, whether
your balance covers it, and full schema validation of the input. An invalid
input is still a 200 quote with `valid:false` and the same `validation_errors`
a real call would return, so quote doubles as a free input linter. Over MCP,
the free `quote_call` tool does the same.

## 4. Call a tool

```
curl -X POST https://api.counselorai.app/v1/tools/appeal \
  -H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: my-run-42' \
  -d '{"studentName": "Sample Student", "keyDetails": "household lost its primary income in March"}'
```

Billing holds an estimate before the model runs and settles to actual token
usage after, so a runaway loop cannot spend money it does not have. The
`Idempotency-Key` header guarantees at-most-once billing on retries.

Every successful response is a structured envelope:

- `output.text`: the generated content.
- `output.verification`: whether a model verify pass checked the text, its
  PASS/FAIL, score, and whether a fix pass fired. Tools without a verify pass
  say `checked: false` rather than implying one.
- `output.grounding`: the fact sheet the verify pass checked figures and
  names against, plus a computed field-reflection report. Any input field the
  prompt did not actually use appears in `fields_not_reflected`, so silent
  degradation is visible to you on every response.
- `output.input_fidelity`: `"strict"`. This lane rejects over-limit input
  with a validation error instead of truncating.
- `billing`: credits charged, held, and remaining, and the USD equivalent.

## 5. Gate your own output

```
curl -X POST https://api.counselorai.app/gate/v1/tools/check_output \
  -H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
  -d '{"text": "<the generated text>", "source_facts": "award total: $4,500\nstudent: Sample Student"}'
```

Returns pass/fail plus findings: banned AI vocabulary with the exact terms,
em-dash and structural tells, PII-shaped content, and per-claim fact verdicts
(`verified` / `unverified` / `contradicted`) judged only against the
`source_facts` you supply. `deterministic_only: true` skips the model judge
for a cheaper regex-only pass. `fix_output` applies the fixes and re-scans
its own result to prove the rewrite cleared.

## 6. A2A

The primary lane uses native A2A 1.0 method names. `SendMessage` takes a data
part `{ "skill": "<tool name>", "input": { ... } }` and returns a terminal
task synchronously: completed with the envelope as a data artifact, or failed
with the error in the task status. Pass `metadata.idempotencyKey` and the task
ID derives from it, so a retried send returns the same task instead of running
twice.

`GetTask` replays one account-scoped task. For live keys, `ListTasks` returns
account-scoped A2A tasks newest first, with a page size from 1 to 100 and an opaque page
token. It supports status, status-timestamp, and artifact-inclusion filters.
Caller-supplied context identifiers are not retained, so context filtering is
rejected rather than answered incompletely. Response artifacts replay only
for accounts that opt into response retention. Default accounts still receive
status without a stored body. `CancelTask` returns not-cancelable because this
server completes calls synchronously. Streaming and push notifications remain
disabled.

The older `message/send`, `tasks/get`, and `tasks/cancel` methods remain at
`/a2a` for A2A 0.2 clients.

Test keys are the narrow exception to body-free default retention: only the
canned fixture output and terminal task status are retained for 24 hours so
integrators can test `GetTask`, legacy `tasks/get`, and idempotent retries. Test
fixtures are retrieved by their returned task ID and are not enumerable through
`ListTasks`. Caller input, billing
data, and live model output are never written to that test-task store.
The test store is eventually consistent, so an immediate `tasks/get` may need
a short retry before the fixture becomes visible.

## 7. Errors

Every non-2xx body is `{"error": {"code": "...", "message": "...", ...}}`
with a stable `code` and the next action as data: `insufficient_credits`
carries `top_up_url`, `invalid_input` carries `validation_errors` with field
paths, `rate_limited` carries `retry_after_seconds` (also in the
`Retry-After` header), `payment_required` carries `checkout_url`. Codes are
never repurposed. On MCP, tool-level failures come back as `isError` results
with the same data, so your model can react instead of treating the server
as broken.

## 8. Data handling

Request and response bodies are not persisted by default (`retention_mode`
`none`, the only setting appropriate for student data). Idempotent replays on
default accounts return a 409 pointing at the original call rather than a
stored body. Nothing from your inputs is used for anything except running the
call you paid for. The 24-hour test-fixture exception described above contains
only canned product fixtures, never caller input.

## Background tasks: gated preview

Durable A2A 1.0 tasks are disabled by default. Once enabled for an account with a written storage agreement, `SendMessage` accepts `configuration.returnImmediately=true` plus `metadata.storageConsent="a2a-storage-v1"`. This consent covers temporary input and output storage in Supabase, execution by Netlify, and model processing by Anthropic. Input is cleared when the job ends; output becomes unreadable 24 hours after submission and is cleared on the next successful cleanup run. Operational identifiers and billing records remain. Use opaque IDs with no student information. Poll `GetTask`, or cancel while queued with `CancelTask`. See the [implementation and activation guide](https://github.com/The-825/counselorai/blob/main/docs/A2A_JOBS.md) for the contract and deployment limits.

## Background tasks: gated preview

Durable A2A 1.0 tasks are disabled by default. Once enabled for an account with a written storage agreement, `SendMessage` accepts `configuration.returnImmediately=true` plus `metadata.storageConsent="a2a-storage-v1"`. This consent covers temporary input and output storage in Supabase, execution by Netlify, and model processing by Anthropic. Input is cleared when the job ends; output becomes unreadable 24 hours after submission and is cleared on the next successful cleanup run. Operational identifiers and billing records remain. Use opaque IDs with no student information. Poll `GetTask`, or cancel while queued with `CancelTask`. The implementation and activation guide is `docs/A2A_JOBS.md` in the source repository.
