# Prospect API

Bearer credentials with explicit scopes, mandatory idempotency keys on every mutation, and a request id on every response.

Bearer credentials, created on the API credentials page in the app. The secret is shown once.

```bash
curl -s -H 'Authorization: Bearer pxk_xxxxxxxx_…' \
  'https://prospex.ch/api/v1/prospect/projects'
```

Every response carries `request_id` in the body and in `X-Request-Id`, plus `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Every call is recorded in your project's audit history with the credential name and that request id.

## Scopes

| Scope | Permits |
| --- | --- |
| prospect:read | Read projects and prospects |
| prospect:write | Add companies and record decisions |
| prospect:run | Start cycles and request research |
| prospect:stages | Read, claim and complete external stage jobs |
| prospect:approve | Approve a named outreach revision |
| prospect:outcomes | Record downstream outcomes (published, but no route accepts it yet) |
| webhooks:manage | Manage webhook endpoints (published, but no route accepts it yet) |
| companies:read | Search the Swiss company directory |
| markets:read | Read your markets and their companies |
| watches:read | Read your watches and their signals |

Approving is a scope of its own because it releases customer-facing copy into downstream work: a read or run credential cannot approve a bundle. `prospect:stages` permits reading, claiming and completing configured external stage jobs.

## Routes

| Name | Path |
| --- | --- |
| cycle_create | /api/v1/prospect/projects/\<str:project_id>/cycles |
| cycle_detail | /api/v1/prospect/projects/\<str:project_id>/cycles/\<str:cycle_id> |
| project_detail | /api/v1/prospect/projects/\<str:project_id> |
| project_list | /api/v1/prospect/projects |
| prospect_approvals | /api/v1/prospect/projects/\<str:project_id>/prospects/\<str:prospect_id>/approvals |
| prospect_decisions | /api/v1/prospect/projects/\<str:project_id>/prospects/\<str:prospect_id>/decisions |
| prospect_detail | /api/v1/prospect/projects/\<str:project_id>/prospects/\<str:prospect_id> |
| prospect_research | /api/v1/prospect/projects/\<str:project_id>/prospects/\<str:prospect_id>/research |
| prospects | /api/v1/prospect/projects/\<str:project_id>/prospects |
| stage_job_cancellations | /api/v1/prospect/projects/\<str:project_id>/stage-jobs/\<str:job_id>/cancellations |
| stage_job_claims | /api/v1/prospect/projects/\<str:project_id>/stage-jobs/\<str:job_id>/claims |
| stage_job_detail | /api/v1/prospect/projects/\<str:project_id>/stage-jobs/\<str:job_id> |
| stage_job_failures | /api/v1/prospect/projects/\<str:project_id>/stage-jobs/\<str:job_id>/failures |
| stage_job_list | /api/v1/prospect/projects/\<str:project_id>/stage-jobs |
| stage_job_results | /api/v1/prospect/projects/\<str:project_id>/stage-jobs/\<str:job_id>/results |
| stage_job_retries | /api/v1/prospect/projects/\<str:project_id>/stage-jobs/\<str:job_id>/retries |

Project creation and offering editing stay in the browser.

`.../research` *requests* research using the project's configured executor. External output enters only through a named stage job's `/results`, which binds it to the right input snapshot and upstream revisions.

## Mutations

- Every mutation needs an `Idempotency-Key` header.
- The same key with the same request returns the original response byte for byte, with `Idempotency-Replayed: true`, and spends no second research unit.
- The same key with a *different* request answers **422 `idempotency_conflict`**.
- Work that runs in the background answers `202` with the cycle.
- Starting a cycle when one is already queued or running answers `200` with that cycle when your request names no new prospects. Naming new prospects answers `409` with the blocking cycle attached.
- Adding companies resolves identity and consumes no research unit. At most 100 per request.
- An ambiguous identity comes back as a structured resolution with the candidates.
- Approving names both the outreach revision and the content fingerprint you reviewed. A fingerprint that belongs to a different revision is refused.

10,000 requests per account per day.

## Errors

```json
{
  "error": {
    "code": "validation_failed",
    "message": "…",
    "errors": [{"code": "evidence_reference_missing", "field": "claims[1].evidence_ids", "detail": "…"}]
  },
  "request_id": "req_…"
}
```

| Status | code | When |
| --- | --- | --- |
| 400 | invalid_parameter | A parameter was understood and its value refused. |
| 400 | invalid_request | The body or a parameter could not be read. |
| 400 | unknown_parameter | A parameter this route does not accept. Refused because a silently dropped filter returns more rows than you asked for. |
| 400 | window_exceeded | A company search asked for results past the search index's ceiling. Narrow the query, or read a market's companies at `/api/v1/markets/<id>/companies`, which has no ceiling. |
| 401 | unauthenticated | Missing, malformed, unknown or revoked credential. |
| 402 | subscription_required | The credential is valid but the account's subscription does not cover this scope's product. Resolve it from the billing page. |
| 403 | forbidden | The credential does not hold the scope this route needs. |
| 404 | not_found | No such object for this credential. Markets and watches use 404 for another account's object too, so a response never confirms that an id exists. |
| 405 | method_not_allowed | This route does not accept that method. |
| 409 | cycle_active | A queued or running cycle blocks this work. The body carries that cycle. |
| 409 | external_worker_failed | The worker reported a failure for this job. |
| 409 | job_allowance_full | This project already holds its limit of open stage jobs. |
| 409 | job_cancelled | This job was cancelled. |
| 409 | job_expired | This job passed its deadline. Retry it to get a fresh one. |
| 409 | job_not_claimed | This job is unclaimed, or claimed by another credential. |
| 409 | job_not_open | This stage job has already been completed, cancelled or expired. |
| 409 | lease_expired | The claim lease ran out. Claim the job again. |
| 422 | idempotency_conflict | This idempotency key was already used for a different request. Generate a new key and retry. |
| 422 | validation_failed | The request was understood and refused. `errors` names the fields and stable codes. |
| 429 | burst_limited | Too many requests in the last minute. `retry_after` is seconds. Slow down and continue. |
| 429 | daily_row_limit | The account's shared daily row budget is spent. Every surface that returns rows (feeds, exports, API) shares this budget. |
| 429 | rate_limited | The account's daily request allowance is spent. |
| 500 | internal_error | Ours. The `request_id` is what to quote. |
| 503 | api_disabled | This part of the API is switched off globally. |
| 503 | search_unavailable | The search index is unreachable. Retry shortly. |
