# Error Handling

Source: https://docs.pitchpointsolutions.com/developer_guides/sessions_and_workflows/error_handling/

**Beta**

How the Session and Workflow APIs report errors, how to branch on them, and the limits worth knowing before you go live.

## How errors arrive

An error arrives in one of two JSON shapes, and which one you get follows which layer answered, not the status code.

| `Content-Type` | Body | Read |
|---|---|---|
| `application/json` | The `messages` envelope. | `messages[].code`, such as `VALIDATION_ERROR` or `SESSION_EMPTY`. |
| `application/problem+json` | A problem document: `type`, `title`, `status`, `detail` and `instance`, with no `messages` key. | `type`, compared as the whole URI. |

A 400 and a 409 can arrive in either shape. A 401, 403, 404 or 500 from the service is always a problem document. These are the problem types the Session and Workflow APIs return:

| `type` | Status | Meaning |
|---|---|---|
| `about:blank` | 400, 401, 403, 404, 500 | The status alone describes the problem: a body that could not be read as the request, no credential at all, a denial before the operation ran, a path this API doesn't serve, or an unhandled failure. |
| `https://pointservices.com/problems/invalid-token` | 401 | A token was sent and refused. Get a fresh one. |
| `https://pointservices.com/problems/not-authorized` | 403 | You are signed in, and you may not act on this resource or product. |
| `https://pointservices.com/problems/not-found` | 404 | The identifier you sent did not resolve. |
| `https://pointservices.com/problems/invalid-request` | 400 | The `Content-Type` header names a character set the service can't use. |
| `https://pointservices.com/problems/concurrent-modification` | 409 | Another request changed the same resource first, and nothing from yours was applied. Read it again before you resubmit. |

The rules that follow from this:

1. **Branch on the `Content-Type` first, then on `messages[].code` or `type`.** Compare the media type only, ignoring parameters such as `charset`. Check that `messages` exists before you read `messages[0].code`: on several endpoints the problem document is the common case, not the exception.
2. **Some rejections carry no JSON body at all.** The edge in front of the service refuses a POST without `Content-Type`, or a value that looks like SQL or markup, with a 403 and an HTML body, and a rate-limited request with a bare 429. Write your client to tolerate an error it can't parse.
3. **Treat an unrecognized message code as non-retryable.** The vocabulary is closed but additive; meeting a new code means stop, not retry.
4. **What a 500 means depends on the call.** On the reads it means nothing happened, so read again. On Start a Session and Stage Data the 500 is not stored, so send the same request again with the same `Idempotency-Key`: if the failed attempt's work was kept you get its original response, and if not it runs again, so you end up with one session or one snapshot either way. On Start a Workflow, Start a Workflow in a Session and Reconfirm a Workflow, the outcome is unknown, not that nothing happened. With an `Idempotency-Key`, a 500 on those three is stored and every retry under that key returns it, so re-read, or search by `correlationId`, then send the call under a new key only if it didn't happen. Without a key, retrying blind is how one workflow becomes two. On Edit a Workflow, send the same request again: a repeated outcome report is recorded again and changes no answer.
5. **Send an `Idempotency-Key` on every POST you might retry**, except Edit a Workflow, which takes none. A retry under the same key returns the first response instead of doing the work again. See [Retrying Safely](https://docs.pitchpointsolutions.com/developer_guides/sessions_and_workflows/retrying_safely/).
6. **Quote `instance` when you report an error.** It is written `urn:pps:request:<id>`, and the id is the same one the `x-pps-request-id` response header carries. It is what lets PPS find that one request.

## Things to know

- **A session never expires**, and it is not a login session. Holding a `sessionId` is not permission to use it: every call is authorized on its own.
- **`dataStaged` never returns to `false`** once a stage has succeeded. Re-staging replaces the snapshot; nothing empties a session.
- **A 409 `SESSION_EMPTY` means stage first.** It is the one 409 you can fix on your own: stage data, then start the workflow again.
- **A rejected stage changes nothing.** The previous snapshot stays current, and no partial one is recorded.
- **`status` is one of four lowercase values**: `processing`, `complete`, `failed` and `cancelled`. Branch on it, not on message codes.
- **A request's `correlationId` is limited to 255 characters**, after trimming. On a session start, a workflow start (direct or in a session) or a reconfirm, a longer value is a 400 and nothing is stored. A term's own `correlationId` has the same limit, but a longer one is ignored rather than rejected; see [Correlation ids on terms](https://docs.pitchpointsolutions.com/api/sami_terms/conventions/#correlation-ids-on-terms).
- **Find Workflows returns at most 100 workflows, newest first, with no paging**, and it reads at most the newest 1,000 matching runs. Make your correlation ids distinctive.
- **A reconfirm body decides what runs.** No body, or `{}`, re-runs against the session's current data. A body with `terms` records a new snapshot of the session, stated in full, and re-runs against it.
- **Every POST needs `Content-Type: application/json`**, including a session start or a reconfirm with no body.
