---
title: REST and n8n
description: Call Felspar from code or an automation tool with three HTTP requests - brief, decision, verify - plus job lists and output checks.
sidebar:
  order: 7
---

Any code or automation that can send HTTP requests can use Felspar. Three calls cover the whole loop.

| Step | Request |
| --- | --- |
| 1. What does the job need? | `GET https://api.felspar.ai/v0/jobs/{job}/brief` |
| 2. Decide | `POST https://api.felspar.ai/v0/jobs/{job}/decisions` |
| 3. Verify before acting | `POST https://api.felspar.ai/v0/executions/{executionObjectId}/verify` |

Two more routes are there when you need them: `GET /v0/jobs` lists the jobs the key may run, and `POST /v0/jobs/{job}/output-checks` checks text written for a decision. The examples below use a fictional marketing team's `review-claim` job, whose subject is a campaign asset identified by its asset ID.

Every request carries the agent's key: `Authorization: Bearer <agent key>`. Get one from [Register an agent](/guides/register-an-agent).

## 1. Get the brief

```sh
curl -s https://api.felspar.ai/v0/jobs/review-claim/brief \
  -H "Authorization: Bearer $FELSPAR_AGENT_KEY"
```

The brief is the same for every caller until a new release, so cache it per job. It starts with the job's purpose and subject (`subject.send` says which ID to send), then lists each question's `key`, what to send (`answer.send`), how it's read (`answer.interpretation`), whether it applies always or only conditionally (`answer.applies`, `appliesWhen`), the answers with their meanings, where to look, the outcomes with next steps, the escalations, the attempt limit, the request limits, and `input.jsonSchema`, the exact request format.

## 2. Ask for a decision

```sh
curl -s https://api.felspar.ai/v0/jobs/review-claim/decisions \
  -H "Authorization: Bearer $FELSPAR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "facts",
    "executionId": "3f2a6c1e-8b7d-4e0a-9c55-1d2e3f4a5b6c",
    "jobId": "review-claim",
    "entityId": "AST-5520",
    "facts": {
      "claim_kind": { "text": "Cuts onboarding time in half for every customer.",
        "source": { "class": "campaign-asset", "id": "AST-5520", "locator": "AST-5520#headline", "observedAt": "2026-10-21T09:00:00Z" } }
    }
  }'
```

| Field | Meaning |
| --- | --- |
| `mode` | `"facts"`: you send what sources say; Felspar decides the values |
| `executionId` | A new unique ID (a UUID) for each decision. Resend the same ID with the same body to retry safely; you get the original result. |
| `jobId` | The job, as in the path |
| `entityId` | The case's ID, as the brief's `subject.send` says: an asset ID, a ticket number, a contract ID, a domain |
| `related` | Only for questions `about` a related record: that record's ID under its relationship name |
| `facts` | One entry per question key: an excerpt, a list of up to three, or for number and date questions a typed `{ value, source }` |
| `expectedReleaseDigest` | Optional. Hold a specific release; refused with `release_changed` if it isn't the one Felspar would use |

Send every question you have evidence for in one request, including conditional ones. A conditional question that doesn't apply is never classified and doesn't change the outcome. If one passage answers several questions, send the same text under each; those questions are classified together, faster.

See [Decision result](/reference/rest-api#decision-result) for the response.

## 3. Verify before acting

```sh
curl -s https://api.felspar.ai/v0/executions/$EXECUTION_OBJECT_ID/verify \
  -H "Authorization: Bearer $FELSPAR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "receipt": <result.receipt>, "expectedReceiptDigest": "<result.receipt.receiptDigest>" }'
```

Act only when `status` is `reproduced` and `decided` names the job, entity and an outcome that allows the action. When `decided.escalation` is present, route the case by its `id` instead of acting. See [Verify receipts](/guides/verify-receipts).

## n8n

In n8n, use **HTTP Request** nodes:

1. **Credentials:** create a Header Auth credential named `Felspar` with header `Authorization` and value `Bearer <agent key>`.
2. **Brief:** an HTTP Request node, `GET`, URL `https://api.felspar.ai/v0/jobs/review-claim/brief`, authentication `Felspar`. Run it once and keep the question keys.
3. **Gather evidence:** your own nodes: fetch a page, read a CRM record. Map the exact text into the facts shape.
4. **Decide:** an HTTP Request node, `POST`, URL `https://api.felspar.ai/v0/jobs/review-claim/decisions`, JSON body as above, with `executionId` set to a fresh UUID expression such as `{{ $uuid() }}` or your record's own unique ID.
5. **Branch:** a Switch node on `result.escalation.id` first (each escalation your workflow handles: a review queue, a message to the owner), then on `result.outcome`. Treat a `null` outcome as "not decided": follow `result.attempts.next`.
6. **Verify:** before any node that acts, an HTTP Request node to the verify URL with the receipt; continue only on `reproduced`.

n8n's MCP Client node can also connect to `https://mcp.felspar.ai/mcp` with the same header, for n8n AI agents.

## Handling errors

- **400** with `error`, `path`, `issues[]`: the request was malformed. Each issue has a `path` and a `fix`. Fix them all and resend; nothing was decided.
- **403 `forbidden`:** this key can't run this job, or the release isn't available to it.
- **409 `execution_id_reused`:** this execution ID was used for a different request. Use a new ID, or resend the original request unchanged.
- **409 `release_changed`:** read the brief again and resend.
- **429 `rate_limited`:** wait `retryAfterSeconds` (60) and resend the same request with the same execution ID.

All codes: [Refusal codes](/reference/refusal-codes).

## Batch runs

Decide each entity in its own request, with all its questions together. Spread large runs over time to stay within [rate limits](/reference/limits), and hold one release for the whole run with `expectedReleaseDigest`, so a release mid-run doesn't mix results. For runs of thousands of entities, talk to the Felspar team.
