# Troubleshooting API errors

> The errors you meet while building, with the exact message and a fix for each: 401 missing or invalid key, 403 scope or ownership, 404, 422 references and idempotency, 429 rate limits.

Source: https://developers.anpheros.com/guides/troubleshooting

The errors you are most likely to meet while building on Anpheros, with the exact message the API
returns and a request that works. The full list of status codes is in the [error catalogue](https://developers.anpheros.com/guides/errors).

**How to read an error:**

| Where | Error body |
|---|---|
| `/v1/…` and `/admin/…` | `{"error": {"type": "login", "message": "Invalid API key."}}` |
| `/fhir/R4/…` | `{"resourceType": "OperationOutcome", "issue": [{"severity": "error", "code": "login", "diagnostics": "…"}]}` |

Every response, error or not, carries an `Anpheros-Request-Id` header. Quote it when you write to
platform@anpheros.com and we can find your request.

```bash
curl -i https://platform.anpheros.com/v1/patients -H "Authorization: Bearer $ANPHEROS_KEY"
# -i shows the status line and the headers, including Anpheros-Request-Id
```

## 401 Unauthorized: the API does not know who you are

### Missing credentials

```json
{"error":{"type":"login","message":"Missing credentials: send an API key or an OAuth access token as 'Authorization: Bearer <token>'."}}
```

The `Authorization` header is missing, or it has the key without the word `Bearer`, or your
variable is empty.

This is the error people meet most often, usually in the [API reference](https://developers.anpheros.com/docs)
before the key is pasted: press **Authorize** at the top of the page, paste your `sk_test_…` key and
press **Authorize** again, then **Try it out**. In your own code:

```bash
# wrong: no "Bearer"
curl https://platform.anpheros.com/v1/patients -H "Authorization: $ANPHEROS_KEY"
# right
curl https://platform.anpheros.com/v1/patients -H "Authorization: Bearer $ANPHEROS_KEY"
# check the variable is set in this terminal
echo "$ANPHEROS_KEY"
```

### Invalid API key

```json
{"error":{"type":"login","message":"Invalid API key."}}
```

The key is incomplete (cut off when copied), has a space, a line break or quotes in it, or was
deleted. Copy it again from where you saved it. Keys are shown only once: if you no longer have it,
create a new key in the [dashboard](https://platform.anpheros.com/dashboard/?src=troubleshooting&utm_source=developers.anpheros.com&utm_medium=guide&utm_campaign=troubleshooting).
The response also carries `WWW-Authenticate: Bearer realm="anpheros", error="invalid_token"`.

### This API key has been revoked

```json
{"error":{"type":"login","message":"This API key has been revoked."}}
```

Someone on your team revoked the key in the dashboard. Create a new one and replace it wherever the
old one was used.

### This key does not match its project's environment

```json
{"error":{"type":"login","message":"This key does not match its project's environment."}}
```

A test key (`sk_test_…`) only works on a sandbox project, and a live key (`sk_live_…`) only on a
production project. Use the key of the project you are calling.

### Access token expired or revoked (OAuth)

```json
{"error":{"type":"login","message":"Access token expired or revoked."}}
```

Access tokens are short-lived. Use the refresh token at `POST /oauth/token` with
`grant_type=refresh_token`; if the person revoked access, send them through the consent screen again.
Both SDKs refresh once on a `401` for you. Other OAuth messages: `The consent behind this token is no
longer active.` (the person withdrew consent) and `The application behind this token is not active.`
(the app was disabled). Details: [Authentication and OAuth](https://developers.anpheros.com/guides/authentication).

## 403 Forbidden: the API knows you, but this is not allowed

### Missing scope

```json
{"error":{"type":"forbidden","message":"This credential does not have the 'write' scope."}}
```

The key was created with fewer scopes than the call needs: for example a read-only key used for a
`POST`. Create a key with `read` and `write` in the dashboard, or through the Admin API:

```bash
curl -X POST https://platform.anpheros.com/admin/projects/proj_…/keys \
     -H "Authorization: Bearer $FIREBASE_ID_TOKEN" -H 'content-type: application/json' \
     -d '{"name":"local dev","scopes":["read","write"]}'
```

### Written by another organization

```json
{"error":{"type":"forbidden","message":"This Observation was written by another organization and cannot be changed or deleted by yours. Write your own resource instead."}}
```

You can read what other organizations wrote to a record you have access to, but only the author can
change or delete it. Create your own resource, and use `derived_from` or a note to point to the
original if needed. The same rule inside your organization: `Only the project that created this
Observation can change it.`

### The consent does not allow it (OAuth)

```json
{"error":{"type":"forbidden","message":"The consent behind this token does not allow 'write' on Observation."}}
```

The person approved fewer SMART scopes than the call needs, for example `patient/Observation.rs`
(read and search) and not `.c` (create). Ask for the scope you need in the authorization request;
the person approves it on the consent screen.

## 404 Not Found

### The resource does not exist for you

```json
{"error":{"type":"not-found","message":"…"}}
```

Projects are isolated: a patient created by another project does not exist for you, so you get `404`,
never `403`. Ids are also pairwise: the same person has a different id in every project. Use the ids
your own project received, for example from `GET /v1/patients`.

### No such endpoint

```json
{"error":{"type":"not-found","message":"No such endpoint: GET /v1/patient. Reference: https://developers.anpheros.com/docs"}}
```

A typo in the path, or the wrong method. Collections are plural (`/v1/patients`,
`/v1/patients/{id}/observations`). Paths start directly with `/v1`, `/fhir/R4` or `/oauth`: a path
like `/api/v1/patients` returns `{"detail":"Not Found"}`. Every path is in the
[API reference](https://developers.anpheros.com/docs).

## 422 Unprocessable: the request is well formed, but it cannot be applied

### The referenced patient does not exist

```json
{"resourceType":"OperationOutcome","issue":[{"severity":"error","code":"not-found","diagnostics":"The referenced Patient does not exist."}]}
```

The `subject` of the resource points to a patient that does not exist for your project, often an id
from another project or from before a sandbox reset (reset gives the synthetic patients new ids). Read
the id again:

```bash
curl "https://platform.anpheros.com/fhir/R4/Patient?identifier=https://anpheros.com/fhir/sid/sandbox-persona|p02" \
     -H "Authorization: Bearer $ANPHEROS_KEY"
```

Each synthetic patient keeps the same `sandbox-persona` identifier across resets (`p02` is always
Elena Ionescu).

### Idempotency-Key used with a different request

```json
{"error":{"type":"conflict","message":"Idempotency-Key was already used with a different request."}}
```

An `Idempotency-Key` stands for one request. Repeat the same request with the same key and you get the
same answer without a duplicate; send a different body and you get this error. Use a new key for
each new write:

```bash
-H "Idempotency-Key: $(uuidgen)"
```

### A resource cannot be moved to another patient

```json
{"resourceType":"OperationOutcome","issue":[{"severity":"error","code":"invalid","diagnostics":"A resource cannot be moved to another patient."}]}
```

An update changed the `subject` of an existing resource. Delete it and create a new one for the
right patient instead.

## 429 Too Many Requests

```json
{"error":{"type":"throttled","message":"Rate limit of 600 requests/minute exceeded for this credential."}}
```

You sent more requests in one minute than the limit for your key (600 a minute by default). The
response has a `Retry-After` header with the number of seconds to wait. Wait that long, then retry;
both SDKs do it for you with backoff. To need fewer calls: read pages of up to 200 items, use
`_summary=count` when you only need a number, and use [webhooks](https://developers.anpheros.com/guides/webhooks) instead of polling.
All limits: [Limits and rate limits](https://developers.anpheros.com/guides/limits).

```python
import time, requests

def get(url, key):
    while True:
        r = requests.get(url, headers={"Authorization": f"Bearer {key}"})
        if r.status_code != 429:
            return r
        time.sleep(int(r.headers.get("Retry-After", "5")))
```

## Still stuck?

Write to platform@anpheros.com with the `Anpheros-Request-Id`, the method and path, and the status you
got. Never send your key.

## Frequently asked questions

### Why do I get 404 and not 403 for another project's patient?
Projects are isolated, so another project's patients do not exist for you. A 403 would confirm that the patient exists, which the platform never does.

### Does a sandbox key work in production?
No. Sandbox keys (`sk_test_…`) see only sandbox data, and production keys (`sk_live_…`) only production data. The base URL is the same.

### How long is my access token valid?
Access tokens are short-lived and refresh tokens last longer; the exact lifetimes are in [Limits and rate limits](https://developers.anpheros.com/guides/limits). The SDKs refresh tokens automatically.
