API DOCUMENTATION

QUICKSTART.
AUTH. FULL
REFERENCE.

Everything you need to wire your AI agent into Truss: get a key in under a minute, authenticate with a single header, and verify your first invoice call against the complete POST /api/agents field reference below. For copy-paste Python and Node.js examples, see the SDK quickstart. For exact failure bodies and corrective actions, see the API error reference.

02

AUTHENTICATION

SIGN EVERY REQUEST.

Every request to /api/agents requires a Truss API key. Pass it in either of the two accepted header forms — both are equivalent:

Authorization: Bearer truss_<your-48-hex-key>
x-api-key: truss_<your-48-hex-key>

Auth failure responses:

Status Body Cause
401 { "error": "API key required" } No key supplied in either header
401 { "error": "Invalid or revoked API key" } Supplied key is malformed, truncated, otherwise unrecognized, or revoked. See the revoked-key guidance.
429 {"error":"rate_limited",...} Plan quota exceeded for your key
03

POST /api/agents
REFERENCE

POST https://api.truss.polsia.app/api/agents

Auth: Authorization: Bearer <key> or x-api-key: <key> (required)
Content-Type: application/json

Request Body Fields

Field Type Required Description
invoice_id string required ID of the invoice to verify. Omitting or passing an empty value triggers anomaly invoice_id is required.
expected_amount number optional Cross-checks against invoice.amount. Tolerance ±0.01. Mismatch triggers an error-severity anomaly.
expected_currency string optional Cross-checks against payment.currency (case-insensitive, e.g. "USD").
expected_vendor_name string optional Cross-checks against invoice.vendor_name (case-insensitive).
expected_routing_number string optional Cross-checks against payment.routing_number (exact string match).
expected_account_last4 string optional Cross-checks against payment.account_last4 (exact string match).
webhook_url string|null optional Sets a credential-free http:// or https:// callback URL (maximum 2048 characters) for this API key. The URL is persisted and reused when this field is omitted; pass null to clear it. The verdict is still returned inline in the same response.
any additional fields any optional Persisted verbatim to api_invocations.request_payload and ignored by the verifier. Use for agent metadata (agent_id, intent, etc.).

Three-Way Match Mode verdict

Add "mode": "three_way_match" to the request body to cross-check a purchase order, invoice, and receipt in a single synchronous call. Truss compares each document’s amount, vendor_name, and date (case/space-insensitive) and returns one of four verdicts:

Verdict When
verifiedAll three cross-checks pass (amount / vendor / date line up across PO and invoice and receipt).
partialOne or two cross-checks pass; the rest fail.
mismatchAll three cross-checks fail — documents are present but don’t agree.
missingOne or more documents are absent, or any required field on a document is empty.

Three-Way Match Body

Field Type Required Description
modestring"three_way_match" — required to dispatch the three-way flow. All other request-body fields above are ignored when this is set.
poobjectPO document — must include amount, vendor_name, date, po_number.
invoiceobjectInvoice document — must include amount, vendor_name, date.
receiptobjectReceipt document — must include amount, vendor_name, date.
curl -X POST https://api.truss.polsia.app/api/agents \
  -H "Authorization: Bearer truss_<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "three_way_match",
    "po":      { "po_number": "PO-001", "amount": 100.00, "vendor_name": "Acme", "date": "2026-07-01" },
    "invoice": {                      "amount": 100.00, "vendor_name": "Acme", "date": "2026-07-01" },
    "receipt": {                      "amount": 100.00, "vendor_name": "Acme", "date": "2026-07-01" }
  }'
// 200 OK — all three line up
{
  "authenticated": true,
  "owner_label": "your-key-label",
  "invocation_id": 123,
  "verification": {
    "mode": "three_way_match",
    "verdict": "verified",
    "verified": true,
    "missing_documents": [],
    "comparisons": [
      { "field": "amount",      "matched": true,  "values": { "po": 100,    "invoice": 100,    "receipt": 100 } },
      { "field": "vendor_name", "matched": true,  "values": { "po": "Acme",  "invoice": "Acme",  "receipt": "Acme" } },
      { "field": "date",        "matched": true,  "values": { "po": "2026-07-01", "invoice": "2026-07-01", "receipt": "2026-07-01" } }
    ],
    "matched_fields": [...],
    "anomalies": [],
    "checked_at": "2026-08-22T12:00:00.000Z"
  }
}

Every successful response includes an invocation_id. Keep this ID with the result if a callback is configured; it is the correlation ID for checking delivery status.

When an amount mismatch is detected, the verdict flips to "mismatch" (or "partial" if other cross-checks still line up) and verification.anomalies surfaces the offending field with the per-document values, e.g.:

{
  "verification": {
    "mode": "three_way_match",
    "verdict": "mismatch",
    "verified": false,
    "missing_documents": [],
    "comparisons": [
      { "field": "amount", "matched": false, "values": { "po": 100, "invoice": 110, "receipt": 130 } },
      ...
    ],
    "anomalies": [{ "field": "amount", "severity": "error", "values": {...} }],
    ...
  }
}

Webhook Callbacks

When a valid webhook_url is active, Truss returns the verdict synchronously and asynchronously POSTs the same response envelope to that URL. The callback adds the top-level API-key tier; its nested verification object is unchanged, including all three-way match fields.

// POST to your webhook_url
{
  "authenticated": true,
  "owner_label": "your-key-label",
  "invocation_id": 123,
  "verification": {
    "mode": "three_way_match",
    "verdict": "verified",
    "verified": true,
    "missing_documents": [],
    "comparisons": [
      { "field": "amount", "matched": true, "values": { "po": 100, "invoice": 100, "receipt": 100 } },
      { "field": "vendor_name", "matched": true, "values": { "po": "Acme", "invoice": "Acme", "receipt": "Acme" } },
      { "field": "date", "matched": true, "values": { "po": "2026-07-01", "invoice": "2026-07-01", "receipt": "2026-07-01" } }
    ],
    "matched_fields": [ ... ],
    "anomalies": [],
    "checked_at": "2026-08-22T12:00:00.000Z"
  },
  "tier": "paid"
}

Truss sends the case-insensitive X-Truss-Signature header in the format sha256=<lowercase-hex-digest>. The digest is an HMAC-SHA256 made with the API key’s plaintext value as the secret and the exact UTF-8 JSON request body as the message. Verify the signature against the raw body before parsing JSON. For Python and Node.js request examples, see the SDK examples. The callback includes the same top-level invocation_id as the inline response. Any 2xx response counts as delivered; non-2xx responses, timeouts, and network errors are retried up to three total attempts with short backoff (100ms, then 200ms). A final failure is recorded, but never delays or changes the inline verdict response.

Webhook Delivery Status

Poll delivery state with the invocation_id returned by POST /api/agents. This endpoint uses the same API key, so an invocation belonging to another key is indistinguishable from an unknown ID. The public delivery_state enum is not_configured, retrying, delivered, or permanently_failed. The persisted internal pending state is returned as retrying until delivery settles.

curl https://api.truss.polsia.app/api/agents/123/webhook-status \
  -H "Authorization: Bearer truss_<your-key>"
// 200 OK
{
  "invocation_id": 123,
  "delivery_state": "retrying",
  "attempt_count": 2,
  "created_at": "2026-08-22T12:00:00.000Z",
  "delivered_at": null,
  "latest_failure_reason": "HTTP 502"
}
Lifecycle example Representative status fields Meaning
Pending (internal)delivery_state: "retrying"
attempt_count: 0
delivered_at: null
latest_failure_reason: null
Callback is configured but delivery attempts have not settled. Callers see this persisted pending state as the transient retrying state.
Successful deliverydelivery_state: "delivered"
attempt_count: 1
delivered_at: "2026-08-22T12:00:01.000Z"
latest_failure_reason: null
A callback received a 2xx response. Delivery is complete, whether it succeeded on the first attempt or after retries.
In-progress retrydelivery_state: "retrying"
attempt_count: 2
delivered_at: null
latest_failure_reason: "HTTP 502"
An attempt failed and another attempt may still run. The reason is the latest failure so far.
Terminal failuredelivery_state: "permanently_failed"
attempt_count: 3
delivered_at: null
latest_failure_reason: "HTTP 500"
All three attempts failed. The reason is now the terminal error and no further delivery attempt will run.

Delivery makes one immediate attempt, then retries after 100ms and 200ms, for three total attempts. A retry count is therefore max(attempt_count - 1, 0).

Field Meaning
invocation_idThe invocation ID returned by the verification request and included in the callback envelope.
delivery_statePublicly, not_configured means no callback was active, retrying means delivery is still transient (including the internal pending state), delivered means a 2xx response was received, and permanently_failed means all three attempts failed.
attempt_countTotal callback attempts made so far, including the first send, from 0 through 3. The retry count is max(attempt_count - 1, 0).
created_atUTC timestamp when the invocation was recorded; it is not a timestamp for each individual delivery attempt.
delivered_atUTC timestamp when delivery succeeded, or null while delivery is pending/retrying or has permanently failed.
latest_failure_reasonnull before any failure and after successful delivery; while retrying, it is the most recent error, and after all three attempts fail, it is the terminal error.

When a callback is configured, retrying is the transient state to poll, including the internal pending period and the backoff between attempts. Stop when it settles at the terminal delivered or permanently_failed state; for permanent failure, use latest_failure_reason as the terminal error. not_configured is also terminal. A malformed, nonexistent, or other-account invocation returns 404 with {"error":"Invocation not found"}. Missing or invalid credentials retain the standard 401 responses.

SDK-Style Receiver

Register this route before a global express.json() parser so the raw request bytes remain available:

const crypto = require('node:crypto');
const express = require('express');
const app = express();
const TRUSS_API_KEY = process.env.TRUSS_API_KEY;

app.post('/truss/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const received = req.get('X-Truss-Signature') || '';
    const expected = 'sha256=' + crypto
      .createHmac('sha256', TRUSS_API_KEY)
      .update(req.body)
      .digest('hex');
    const valid = received.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
    if (!valid) return res.sendStatus(401);

    const callback = JSON.parse(req.body.toString('utf8'));
    // callback.verification.verdict is ready for your async workflow.
    console.log(callback.tier, callback.verification);
    return res.sendStatus(204);
  }
);

Response Shape

{
  "authenticated": true,
  "owner_label": "your-key-label",
  "verification": {
    "verified": false,
    "invoice_id": null,
    "payment_id": null,
    "matched_fields": [],
    "anomalies": [
      {
        "field": "invoice_id",
        "severity": "error",
        "message": "invoice_id is required"
      }
    ],
    "checked_at": "2026-08-21T12:00:00.000Z"
  }
}

Each entry in anomalies carries field, severity ("error" or "warning"), and optionally expected, actual, and message. verified is true only when the invoice and payment both resolve and no error-severity anomalies fire.

Status Codes

Code When
200 Always on a valid key — check verification.verified for data correctness
401 Missing header: { "error": "API key required" }; supplied malformed, truncated, unrecognized, or revoked key: { "error": "Invalid or revoked API key" }
400 Malformed, credential-bearing, or non-HTTP(S) webhook_url
429 Rate limit exceeded for your plan
500 Internal server error

For the exact failure bodies and the corrective action for each status, see the API error reference.

How Results Map to /api_invocations

Every authenticated call — including verified: false outcomes — is logged to api_invocations and visible in the /dashboard/api-usage view. The verification object is stored in response_payload; the verified flag and anomaly count are surfaced as columns in that dashboard so you can audit all agent traffic and spot mismatches before they reach payment.