Skip to content
Felspar
Esc
↑↓navigate↵open⌘Jpreview
On this page

REST API

Felspar's REST routes - authentication, the agent routes, authoring and review routes, the decision result, and request formats.

Base URL: https://api.felspar.ai. All routes are versioned under /v0.

This page is written by hand from the service’s routes and contracts. A generated reference from a reviewed OpenAPI contract is coming and will replace it.

Authentication

Caller Credential
Registered agent Authorization: Bearer <agent key>
Person, in the web app The web app’s session cookie. Writes from a browser must come from the app’s own origin.

Every request checks the credential, the caller’s current membership and grants. There is no cross-origin browser access. Responses are never cached.

Request bodies

  • JSON bodies up to 2 MiB.
  • application/x.felspar-content-parts bodies up to 4 MiB. This compact format, used by the SDK and the web app, sends repeated text once; Felspar verifies every part against its fingerprint before use.

Agent routes

These are the routes a registered agent uses. An agent can call them only for jobs it is registered for.

Method and path Does
GET /v0/jobs The jobs this agent key may run, as { "jobs": [{ "id" }] }: those it is registered for, followed or pinned, with a live release. Agent keys only; a revoked key gets 403.
GET /v0/jobs/{job}/brief Everything the job needs: purpose, use-when, not-for, owner, subject, identity, questions, quoted meanings, answers, where to look, outcomes, next steps, escalations, attempt limit, request format
POST /v0/jobs/{job}/decisions Decide one case from facts. Felspar picks the release (Latest or the agent’s pin).
POST /v0/jobs/{job}/output-checks Check text written for one of the caller’s own decisions: { executionId, text, output? }. Returns pass, revise, review_required or unavailable.
GET /v0/jobs/{job}/output-checks/{checkId} Read one of the caller’s own output checks back, for an action gate
POST /v0/executions/{executionObjectId}/verify Replay a decision and compare its receipt. Agents can verify only their own decisions.

A person calling the job routes gets the job’s newest release.

Release-specific routes

To work against one exact release:

Method and path Does
GET /v0/releases/{releaseId}/describe?job={job} The brief for that release
POST /v0/releases/{releaseId}/jobs Decide one case on that release

Decision request

A fictional support desk’s triage-ticket job, whose subject is a ticket with one related customer:

{
  "mode": "facts",
  "executionId": "a-new-uuid",
  "jobId": "triage-ticket",
  "entityId": "48213",
  "related": { "customer": "cus-9f2" },
  "expectedReleaseDigest": "sha256:... (optional on job routes)",
  "facts": {
    "topic": {
      "text": "I was charged twice for the October invoice.",
      "source": { "class": "ticket", "id": "48213", "locator": "48213#message-1", "observedAt": "2026-10-21T09:00:00Z" }
    },
    "premium": {
      "text": "Plan: Premium (yearly), account manager: assigned",
      "source": { "class": "account", "id": "cus-9f2", "observedAt": "2026-10-20T00:00:00Z" }
    }
  }
}
  • entityId: the case’s ID, as the brief’s identity.send and subject.send say. Felspar normalizes it by the subject’s match rule (trimmed; lowercased too for case-insensitive) before it counts attempts, keeps history or writes a receipt. Required when the job uses the record’s own ID (provided, and every job released before identity modes). Leave it out for a fields or content job, and on the first decision of an issued job; afterwards send the case.id Felspar returned. See identity modes.

  • identity: only for a fields job, each identity field’s value by name, such as { "email": "sam@example.com", "requisition": "REQ-114" }. Felspar derives the case ID from them.

  • related: only for questions about a related record. Each relationship’s record ID, or a list of IDs for a many relationship; then each excerpt for that question names its record in about. At most 16 related IDs.

  • executionId: unique per decision. Resending the same ID with the same body returns the original result and is never decided twice. A different body with a used ID is refused (execution_id_reused). A request whose earlier attempt is still running or unknown is never re-run automatically.

  • facts: per question, one excerpt, a list of up to 3, or (number and date questions only) a typed { value, source }. A typed value from a CRM looks like { "value": 1200, "source": { "class": "crm-record", "id": "acct-881", "observedAt": "..." } }. source may also carry excerpt, the exact words that state the value.

  • expectedReleaseDigest: optional on /v0/jobs/{job}/decisions; if left out, the decision is bound to the release Felspar chose, which the result records. Required on /v0/releases/{id}/jobs.

Decision result

{
  "status": "ready",
  "reason": null,
  "result": {
    "executionId": "a-new-uuid",
    "executionObjectId": "execution-...",
    "resultId": "result-...",
    "releaseId": "release-...",
    "releaseDigest": "sha256:...",
    "evaluationTime": "2026-10-21T09:00:03.120Z",
    "evidenceMode": "felspar-interpreted",
    "outcome": "billing",
    "scoring": null,
    "context": null,
    "subject": { "type": "ticket", "id": "48213", "related": [{ "relationship": "customer", "subject": "customer", "id": "cus-9f2", "questions": ["premium"] }] },
    "receipt": { "receiptDigest": "sha256:...", "...": "..." },
    "explanation": { "...": "see below" },
    "diagnostics": [],
    "interpretation": { "...": "how each excerpt was read", "judgmentsDigest": "sha256:...", "modelCalls": 3 }
  }
}
Field Meaning
status, reason ready when a rule decided. Otherwise the most specific cause, such as gap, stale, conflict or a context reason.
result.outcome The outcome, or null
result.explanation.decision The decision’s own status and reasons
result.explanation.why The deciding rules, quoted from the policy, with any overrides
result.explanation.needs[] Each open question: accepted source classes, maximum age, how each excerpt was read
result.explanation.reviewNeeds Review the pack requires
result.explanation.evidenceFreshUntil When the supporting evidence stops being fresh
result.retry[] When undecided: only the open questions, each with fix, notUsed, the answers Felspar can choose and where to look
result.attempts When undecided: used, remaining, next (retry or escalate)
result.nextStep When undecided: the pack’s “when unresolved” instruction, quoted
result.interpretation Per excerpt: method, value, leading answer, probability, threshold, close alternatives, whether cached; plus judgmentsDigest and modelCalls
result.outputs When decided: the outputs this outcome asks the agent to write, each with its approved context and checks. Missing or empty means write nothing.
result.case { id, mode } for a job released with identity modes: the case ID Felspar used (the record’s own ID normalized, derived from fields or content, or issued) and the mode: provided, fields, content or issued. Send case.id as entityId for every later decision on the case.
result.subject { type, id, related[] }: the normalized case ID and, for each related record, the questions it supplied evidence for
result.escalation { id, trigger, why } when the release names a hand-off for this result: trigger is outcome, unresolved or attempt_limit, and why quotes the policy. Felspar names it and stops; your code acts on it.
result.missing With status gap: required related records the request lacked, such as ["missing: counterparty"]. Nothing was decided and there is no receipt.
result.receipt The receipt. Verify it before acting.
result.context Context the release allows for this outcome, if any

The next step for a decided outcome is in the brief’s outcomes[].nextStep.

A wrong or missing case ID is refused with one of the identity refusals. A caller may open at most 200 new issued cases per job in any 24 hours (issued_case_limit).

Verify request and result

{ "receipt": { "...": "result.receipt" }, "expectedReceiptDigest": "sha256:...", "deliveredCapsule": null }

Result: { "authority": "unapproved", "status": "reproduced" | "mismatch", "mismatches": [], "decided": { "jobId", "entityId", "releaseId", "outcome", "case", "subject", "escalation" } }. case ({ id, mode }) is recomputed from the retained request by the release’s identity mode; subject ({ type, related }) and escalation ({ id, trigger }) are re-derived from the release and the replayed outcome. Each appears when the release declares it. See Verify receipts.

Authoring and review routes

For signed-in people. The authoring MCP tools call these same routes.

Method and path Permission Does
GET /v0/sources Inspect Connected documents, with your access to each and watch state
POST /v0/sources Add sources Admit a source from supplied text (not available to an MCP sign-in)
GET /v0/sources/{id} Inspect, plus your own document access The exact stored source
POST /v0/sources/{id}/current Add sources Read the document’s current version with your own connection and admit it as a new source
POST /v0/drafts/check Propose Check a felspar.pack-draft (v2, v1 or v0) against a source; saves nothing. Lists breaking changes against the current release.
POST /v0/drafts/propose Propose Check and submit as a proposal; returns the review link and any breaking changes
POST /v0/proposals Propose Submit a full proposal (advanced)
GET /v0/proposals Inspect The 25 newest proposals with their approvals and releases, and the 25 newest releases
GET /v0/proposals/{id} Inspect, plus document access One proposal
POST /v0/proposals/{id}/backtest Propose and inspect, plus document access; never an agent Backtest the proposal: { reinterpret?, executionIds?, limit? }. Reports each side’s escalation per case, escalationChanged and breaking.
POST /v0/proposals/{id}/approve Approve (web app) Record approval of that exact proposal
GET /v0/approvals/{id} Inspect, plus document access One approval
POST /v0/approvals/{id}/release Release (web app) Release that exact approval
GET /v0/releases/{id} Inspect, plus document access The verified release bundle
GET /v0/runs/{id} Inspect A compact decision result
GET /v0/executions/{id} Inspect The retained material needed to replay a decision

Approval and release are used by the web app’s review pages. An MCP sign-in and agent keys can never call them.

Errors

Every deliberate refusal is JSON:

{
  "error": "too_many_excerpts",
  "path": ["facts", "topic"],
  "issues": [
    { "code": "too_many_excerpts", "path": ["facts", "topic"], "fix": "Send at most 3 excerpts per question." },
    { "code": "unknown_question", "path": ["facts", "severity"], "fix": "This question is not in the released job. Use the question keys from describe." }
  ]
}

Every mistake in a request is reported together, so you can fix them all in one pass. Refusals name codes and paths, never your submitted values or source text. Anything not deliberately described is 403 operation_unavailable. See Refusal codes.

Health

GET /health answers when the service is running. It says nothing about your workspace.

Was this page helpful?