Trellis developer documentation / v1

Define the policy.
Bound the payment.

Know your agent, set spending rules, and grant bounded authority. Connect each signed payment request to its identity, delegation, and verifiable decision trail.

Start with your Trellis workspace. Register an identity, define its authority, and submit an exact signed request. Request early access →

The flow: define policy → bind a signing identity → delegate → authorize → approve when required → execute → reconcile.

API base URL
https://app.trellisfinance.io

Use app.trellisfinance.io for API requests. www.trellisfinance.io hosts these developer docs.

KYA: Know Your Agent

Know who’s acting, on whose behalf, and under what authority. Trellis connects a signing identity to an organization and its delegated permissions. KYA complements policy: a policy defines what is allowed; identity and delegation establish who may exercise it.

QuestionCurrent mechanismWhat it establishes
Which identity sent this?Registered Ed25519 key and signed intentPossession of that key and integrity of the exact request bytes.
On whose behalf?Organization-scoped agent record and owner-issued delegationThe organization record under which permission was granted; not independently verified legal identity.
With what authority?Policy, delegated budget, expiry, and any required human approvalThe boundaries checked before an action proceeds.
Why was it permitted?Stored intent and digest, decision snapshots, signed audit eventsAn attributable history of the evaluated request and later state changes.

A signing identity may represent a workflow, not every ephemeral agent process. Separate keys give distinct attribution and delegation boundaries. Sharing a key shares that identity; a signature cannot distinguish the processes holding it.

Scope matters. Key registration is not business verification, proof of agent provenance, or runtime attestation. A compromised signer can still submit requests within its granted authority.

Define the policy → · Register the signing identity → · Understand signed intent and evidence →

Integration quickstart

Connect a registered agent to bounded authority, submit its signed intent, and inspect the decision and evidence. The request below uses procurement as one example workflow.

1. Connect your workspace

Sign in to your Trellis workspace with your provisioned account. If you need access, contact us. No server installation is required.

API base URL
export TRELLIS_URL=https://app.trellisfinance.io

2. Grant a bounded authority

In Agents & authority, register an agent and retain its downloaded PRIVATE.json file. Create a USD policy allowing northstar, with a $1,000 per-purchase limit and a $500 approval threshold. Issue a delegation with a $2,000 budget and a future expiry. Record the delegation ID and expiry shown in the workspace.

In Developer API, create a key with authorize and execute scopes. The token is returned once in the secret download. Load that token into TRELLIS_API_KEY in your local shell; keep it out of source control and public browser code.

3. Create and sign the purchase

Save the sign.mjs helper below. In a private working directory, replace the three setup values and run:

Create an example intent in purchase.json and authorization-envelope.json
export KEY_FILE='/path/to/trellis-agt_PRIVATE.json'
export DELEGATION_ID='dlg_REPLACE_WITH_YOUR_ID'
# Set to the delegation's actual expires_at value:
export DELEGATION_EXPIRES_AT='REPLACE_WITH_UNIX_SECONDS'
umask 077
node --input-type=module <<'JS'
import { readFileSync, writeFileSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
const key = JSON.parse(readFileSync(process.env.KEY_FILE, 'utf8'));
const expiry = Math.min(Math.floor(Date.now()/1000)+600,
  Number(process.env.DELEGATION_EXPIRES_AT));
if (!Number.isFinite(expiry) || expiry <= Date.now()/1000) {
  throw new Error('Set a future delegation expiry');
}
const intent = {
  org_id: key.org_id, agent_id: key.agent_id,
  delegation_id: process.env.DELEGATION_ID,
  action: 'purchase.create', audience: 'trellis-procurement-v1',
  nonce: randomUUID(), expires_at: expiry,
  supplier_id: 'northstar', item_id: 'keyboard',
  quantity: 4, amount_minor: 26000, currency: 'USD'
};
writeFileSync('purchase.json', JSON.stringify(intent), { flag: 'wx', mode: 0o600 });
JS
node sign.mjs "$KEY_FILE" purchase.json authorization-envelope.json

4. Evaluate the purchase

curl
curl -sS -D authorization-headers.txt \
  "$TRELLIS_URL/v1/authorizations" \
  -H "Authorization: Bearer $TRELLIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @authorization-envelope.json \
  -o authorization.json
cat authorization.json

Expect HTTP 201 and status: authorized with the setup above. A policy denial also returns 201; always inspect status and reason before continuing. The response includes the exact intent, its digest, and the policy/delegation snapshots.

5. Execute and inspect

Create a separate execution envelope
node --input-type=module <<'JS'
import { readFileSync, writeFileSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
const a = JSON.parse(readFileSync('authorization.json', 'utf8'));
// This example executes an automatically authorized purchase.
if (a.status !== 'authorized') throw new Error('Inspect the decision before execution');
const intent = {
  ...a.intent, action: 'purchase.execute', nonce: randomUUID(),
  authorization_id: a.id, intent_digest: a.intent_digest,
  expires_at: Math.min(a.intent.expires_at, Math.floor(Date.now()/1000)+600)
};
writeFileSync('execution.json', JSON.stringify(intent), { flag: 'wx', mode: 0o600 });
JS
node sign.mjs "$KEY_FILE" execution.json execution-envelope.json
curl -sS -i "$TRELLIS_URL/v1/executions" \
  -H "Authorization: Bearer $TRELLIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @execution-envelope.json

Expect HTTP 202 with status: pending. Refresh Requests & approvals in the workspace to see the worker’s outcome. This UI uses a human session; developer keys cannot poll workspace state.

Keep the envelopes. If a response is lost, resend the same saved file. Do not rerun the intent-generation step to retry a purchase. The scripts refuse to overwrite an existing intent or envelope.

Try four monitors next ($720): the default $500 threshold requires human approval. Use the app to approve the exact digest before creating its execution request.

Two credentials. Two responsibilities.

A developer key identifies and meters an integration. An agent signature proves possession of the registered signing key. A delegation decides what that agent may do.

CallerCredentialAvailable actions
Machine integrationBearer developer key + signed envelopeAuthorize and/or execute, according to key scope.
Human ownerHttpOnly session cookieManage agents, policies, delegations, developer keys, and approvals.
Human approverHttpOnly session cookieApprove/reject purchases and perform authorized reconciliation operations.
Human viewerHttpOnly session cookieRead organization state and evidence.
Machine request headers
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Human POST requests must include the exact configured application Origin. For the hosted workspace, use Origin: https://app.trellisfinance.io. Login uses POST /auth/login with email/password JSON; logout uses POST /auth/logout. The browser app handles the session flow.

The browser can submit signed purchases using its same-organization session instead of a bearer key. A supplied invalid bearer key never falls back to a session. A developer key cannot approve an agent’s purchase or grant new authority.

Sign the bytes you send

Every machine purchase and execution request uses an Ed25519 envelope. Serialize the intent once, prepend the UTF-8 string trellis-intent-v1 and one newline byte, then sign those bytes. Encode the original payload and signature separately using unpadded base64url.

Envelope shape — placeholders, not a usable signature
{
  "payload": "BASE64URL_OF_EXACT_INTENT_BYTES",
  "signature": "BASE64URL_OF_ED25519_SIGNATURE"
}

This helper reads the PKCS#8 key downloaded from your workspace. It runs on your machine; the private key never goes to Trellis. The \n in the JavaScript string below evaluates to one newline byte.

sign.mjs
// sign.mjs — Node.js 20+, no dependencies
import { readFileSync, writeFileSync } from 'node:fs';
import { createPrivateKey, sign } from 'node:crypto';
const [keyPath, intentPath, outputPath] = process.argv.slice(2);
if (!keyPath || !intentPath || !outputPath) {
  throw new Error('Usage: node sign.mjs KEY.json INTENT.json ENVELOPE.json');
}
const keyFile = JSON.parse(readFileSync(keyPath, 'utf8'));
if (keyFile.format !== 'trellis-agent-key-v1') throw new Error('Wrong key format');
const payload = readFileSync(intentPath);
const intent = JSON.parse(payload.toString('utf8'));
if (intent.org_id !== keyFile.org_id || intent.agent_id !== keyFile.agent_id) {
  throw new Error('Intent does not match the key owner');
}
const key = createPrivateKey({
  key: Buffer.from(keyFile.private_key, 'base64url'),
  format: 'der', type: 'pkcs8'
});
if (key.asymmetricKeyType !== 'ed25519') throw new Error('Expected Ed25519');
const bytes = Buffer.concat([Buffer.from('trellis-intent-v1\n'), payload]);
const envelope = {
  payload: payload.toString('base64url'),
  signature: sign(null, bytes, key).toString('base64url')
};
writeFileSync(outputPath, JSON.stringify(envelope), { mode: 0o600, flag: 'wx' });
Intent fieldRule
org_id / agent_id / delegation_idUse the organization and registered agent bound to an active delegation.
actionpurchase.create for authorization; purchase.execute for execution.
audienceExactly trellis-procurement-v1.
nonce16–128 characters. Unique per organization + agent, across both actions. UUIDs work.
expires_atInteger Unix seconds, in the future, no more than 15 minutes ahead, and no later than delegation expiry.
amount_minor / currencyInteger USD cents. 26000 means $260.00.
supplier_id / item_id / quantityMust match the supported item definition and its price.
Do not decode and reserialize the payload after signing. Whitespace and key order affect the signed bytes. A signature proves key possession, not legal identity or runtime attestation.

Choose your language

Use JavaScript (Node.js), Go, or Python to sign the same request envelope. Each example loads the downloaded PKCS#8 key, checks the organization and signing identity, preserves the exact intent bytes, and refuses to overwrite a saved envelope. These are small, runnable examples rather than SDKs.

Complete the policy and delegation setup in the quickstart. Create its purchase.json, then choose one signer below. Use the same language to sign execution.json after authorization and any required human approval. All three use the same HTTP endpoints and curl steps.

JavaScript

node sign.mjs PRIVATE.json purchase.json authorization-envelope.json
node sign.mjs PRIVATE.json execution.json execution-envelope.json

Go

Save this complete helper as sign.go.

Show sign.go
// Go 1.22+, standard library only. Run outside the backend module or build this file.
package main

import (
	"crypto/ed25519"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
)

func run() error {
	if len(os.Args) != 4 {
		return fmt.Errorf("usage: go run sign.go KEY.json INTENT.json ENVELOPE.json")
	}
	keyBytes, err := os.ReadFile(os.Args[1])
	if err != nil {
		return err
	}
	var keyFile struct {
		Format     string `json:"format"`
		OrgID      string `json:"org_id"`
		AgentID    string `json:"agent_id"`
		PrivateKey string `json:"private_key"`
	}
	if err = json.Unmarshal(keyBytes, &keyFile); err != nil {
		return err
	}
	if keyFile.Format != "trellis-agent-key-v1" {
		return fmt.Errorf("wrong key format")
	}
	payload, err := os.ReadFile(os.Args[2])
	if err != nil {
		return err
	}
	var intent struct {
		OrgID   string `json:"org_id"`
		AgentID string `json:"agent_id"`
	}
	if err = json.Unmarshal(payload, &intent); err != nil {
		return err
	}
	if intent.OrgID != keyFile.OrgID || intent.AgentID != keyFile.AgentID {
		return fmt.Errorf("intent does not match the key owner")
	}
	der, err := base64.RawURLEncoding.DecodeString(keyFile.PrivateKey)
	if err != nil {
		return err
	}
	parsed, err := x509.ParsePKCS8PrivateKey(der)
	if err != nil {
		return err
	}
	key, ok := parsed.(ed25519.PrivateKey)
	if !ok {
		return fmt.Errorf("expected Ed25519")
	}
	message := append([]byte("trellis-intent-v1\n"), payload...)
	envelope := struct {
		Payload   string `json:"payload"`
		Signature string `json:"signature"`
	}{
		base64.RawURLEncoding.EncodeToString(payload),
		base64.RawURLEncoding.EncodeToString(ed25519.Sign(key, message)),
	}
	file, err := os.OpenFile(os.Args[3], os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
	if err != nil {
		return err
	}
	err = json.NewEncoder(file).Encode(envelope)
	closeErr := file.Close()
	if err != nil {
		return err
	}
	return closeErr
}

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
go run sign.go PRIVATE.json purchase.json authorization-envelope.json
go run sign.go PRIVATE.json execution.json execution-envelope.json

Python

Save this complete helper as sign.py.

Show sign.py
"""Python 3.10+; install cryptography in a virtual environment."""
import base64
import json
import os
import sys
from pathlib import Path
from cryptography.hazmat.primitives.serialization import load_der_private_key
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey


def b64(raw):
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii")


def main():
    if len(sys.argv) != 4:
        raise SystemExit("Usage: python sign.py KEY.json INTENT.json ENVELOPE.json")
    key_path, intent_path, output_path = sys.argv[1:]
    key_file = json.loads(Path(key_path).read_text())
    if key_file.get("format") != "trellis-agent-key-v1":
        raise ValueError("Wrong key format")
    payload = Path(intent_path).read_bytes()
    intent = json.loads(payload)
    if (intent.get("org_id"), intent.get("agent_id")) != (key_file["org_id"], key_file["agent_id"]):
        raise ValueError("Intent does not match the key owner")
    encoded = key_file["private_key"]
    der = base64.urlsafe_b64decode(encoded + "=" * (-len(encoded) % 4))
    key = load_der_private_key(der, password=None)
    if not isinstance(key, Ed25519PrivateKey):
        raise ValueError("Expected Ed25519")
    envelope = {"payload": b64(payload), "signature": b64(key.sign(b"trellis-intent-v1\n" + payload))}
    # Exclusive creation prevents overwriting an envelope needed for safe retries.
    fd = os.open(output_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    with os.fdopen(fd, "w") as output:
        json.dump(envelope, output)


if __name__ == "__main__":
    main()
python3 -m venv .venv
source .venv/bin/activate
python -m pip install cryptography
python sign.py PRIVATE.json purchase.json authorization-envelope.json
python sign.py PRIVATE.json execution.json execution-envelope.json

Run the execution command only after creating its intent in quickstart step 5. A human approval uses the app; a developer key cannot grant itself approval. Keep the saved envelopes for retries rather than creating a new nonce after an ambiguous network response.

Shared HTTP request

curl -sS -D authorization-headers.txt \
  "$TRELLIS_URL/v1/authorizations" \
  -H "Authorization: Bearer $TRELLIS_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @authorization-envelope.json \
  -o authorization.json

A 201 response can still contain denied or awaiting_approval. Inspect the decision before executing. See errors and retries for 429 responses and lost responses.

Register a signing identity

The API calls this an agent. It represents a public key authorized to act under a delegation, not an agent runtime hosted or managed by Trellis. A workflow can use one identity; use separate keys when you need independent attribution and permissions.

POST/v1/agents

Access: owner session. Register a 32-byte raw Ed25519 public key, encoded as unpadded base64url. Keep the corresponding private key in your own runtime.

JSON request — replace the public key
{
  "name": "Procurement assistant",
  "public_key": "YOUR_RAW_PUBLIC_KEY_BASE64URL"
}

201 response: an Agent object with id, name, public_key, owner_id, and status: active. Names are 1–120 characters. The browser app can generate and register the key for evaluation.

Policies & delegations

A policy defines the rules. A delegation binds those rules to one agent, a lifetime budget, and an expiry. Policy records are immutable through the API; create a new policy and delegation to change the rules.

POST/v1/policies

Access: owner session. Amounts below are cents, not dollars.

Policy request
{
  "name": "Office supplies",
  "currency": "USD",
  "suppliers": [
    "northstar"
  ],
  "per_action_minor": 100000,
  "approval_above_minor": 50000
}
FieldValidation
currencyUSD only.
suppliers1–20 entries; supported example counterparties are northstar and workroom.
per_action_minor1–100,000,000 cents.
approval_above_minor0 through the per-purchase limit. A purchase strictly above this value requires a human decision.

201 response: the policy, with a server-generated pol_… ID and version: 1.

POST/v1/delegations
Delegation request — replace IDs and expiry
{
  "agent_id": "agt_YOUR_AGENT",
  "policy_id": "pol_YOUR_POLICY",
  "budget_minor": 200000,
  "expires_at": 1799999999
}

Replace the illustrative expiry with a future Unix timestamp within 30 days. Budget must be 1–100,000,000 cents. Returns 201 with a dlg_… ID and status: active. Each delegation grants its own independent budget; reservations and completed purchases count toward its exposure.

POST/v1/delegations/{id}/revoke

Owner session; send {}. Returns the revoked delegation. Revocation cancels queued or unexecuted actions tied to it. It cannot reverse an already completed payment.

Evaluate a purchase

POST/v1/authorizations

Access: developer key with authorize scope plus signed envelope. The decoded payload uses action: purchase.create. Validation checks signature, active delegation, expiry, supplier, catalog price, per-action limit, and remaining budget.

Response excerpt — illustrative IDs; other fields omitted
{
  "id": "auth_EXAMPLE",
  "intent_digest": "SHA256_OF_EXACT_PAYLOAD",
  "status": "authorized",
  "reason": "within_delegated_authority"
}
HTTP 201 decisionMeaningNext step
authorizedThe purchase fits its delegated authority. Budget is reserved.Submit a separate signed execution.
awaiting_approvalThe purchase exceeds the human-approval threshold.An owner or approver must approve this exact digest.
deniedPolicy or budget evaluation failed.Inspect reason; do not execute.

Common denial reasons: supplier_not_allowed, quote_mismatch, per_action_limit, budget_exceeded, and currency_not_allowed. A completed evaluation is not proof that an order executed.

Approve the exact purchase

POST/v1/approvals/{id}/decision

Access: owner or approver session, with the configured Origin header. Use the authorization ID in the URL. The digest binds approval to the evaluated purchase.

JSON request
{
  "decision": "approve",
  "intent_digest": "DIGEST_FROM_AUTHORIZATION"
}

Use reject to reject the purchase and release its reservation. The response is the updated authorization. An eligible human records a decision bound to the exact intent digest.

Queue an execution

POST/v1/executions

Access: developer key with execute scope plus a new signed envelope. Use action: purchase.execute, a fresh nonce, the authorization ID, and the original intent digest. Keep organization, agent, and delegation bound to that authorization.

Execution uses the stored purchase. A client cannot change the recipient or amount by altering an execution request. The authorization must still be executable and unexpired.

Response excerpt — other fields omitted
{
  "id": "auth_EXAMPLE",
  "status": "pending"
}

202 means queued. The worker rechecks revocation and expiry, then records the outcome. Refresh the workspace or read GET /v1/state with a human session for current status. Use the returned provider_reference to correlate an execution with its outcome.

A distinct second execution attempt is rejected. An exact replay returns the original response, which may still say pending even after the current state has advanced.

Developer keys

POST/v1/developer-keys

Access: owner session. Choose the minimum scopes your integration needs.

JSON request
{
  "name": "Procurement integration",
  "scopes": [
    "authorize",
    "execute"
  ],
  "per_minute": 30,
  "per_day": 1000
}

201 response: {"key": {...metadata}, "token": "trl_dev_…"}. Save the token immediately. It is returned only once; the server stores its hash. Default expiry is 30 days; optional expires_at can extend this to at most 90 days.

Method / routePurpose
GET /v1/developer-keysLatest 100 metadata records; never returns secret values.
POST /v1/developer-keys/{id}/revokeSend {}. Idempotently block new admissions using that key.
GET /v1/developer-usageInspect current key, organization, and service usage buckets.

At most 10 active keys per organization. Per-key limits can be lower than the defaults or raised to the fixed maxima of 60/minute and 5,000/day. Shared limits still apply. Revoking a developer key does not cancel an already queued purchase; revoke its delegation when that is the intended action.

Usage limits & backpressure

Current limits are enforced before signature and domain work, in persistent PostgreSQL counters. They survive restarts and apply across replicas.

ScopePer UTC minutePer UTC day
New developer key301,000
Organization, across all keys and browser-signed actions605,000
Entire service60050,000

All admitted attempts count, including retries, subsequent signature failures, and policy denials. A rejected quota check does not increment any bucket. Windows are fixed UTC windows, not rolling averages.

Quota response
HTTP/1.1 429 Too Many Requests
Retry-After: 42

{"error":{"code":"usage_limit_exceeded","message":"usage_limit_exceeded"}}

Wait at least Retry-After seconds, add jitter, and resend the original envelope if still appropriate. The HTTP server also limits request bodies to 64 KiB, headers to 16 KiB, concurrent requests to 32, and request work to 15 seconds.

Errors, retries & uncertain outcomes

Log the X-Request-ID response header with the operation and authorization ID. Do not log private keys or bearer tokens.

Error envelope
{"error":{"code":"nonce_reused","message":"nonce_reused"}}
HTTPTypical causeWhat to do
400Malformed payload or invalid fieldsCorrect the request; inspect the error code.
401Missing/invalid key, session, or agent signatureCheck credentials and exact signing bytes.
403Wrong scope, Origin, action, audience, or delegationCorrect authority or configuration; do not retry blindly.
404Resource unavailable in this organizationVerify IDs and organization membership.
409Nonce reuse, digest mismatch, or non-executable stateInspect current state; do not create a replacement purchase.
415Wrong Content-TypeUse application/json.
429Usage allowance or login throttleHonor Retry-After.
500 / 503Service failure or paused APIInspect code; retry transient failures with backoff and the same envelope.

Idempotency lives in the signed nonce

The nonce namespace is shared across authorization and execution for each organization + agent. Repeating the same nonce with the same signed payload returns the original response. Different payload bytes with that nonce return 409. Do not invent an Idempotency-Key header; this API uses the signed envelope.

A timeout is not a failed payment

Keep the original envelope and retry it after transient transport failures. Check current workspace state as a human when the outcome is unclear. Do not submit a fresh purchase because you did not receive the first response.

Intent and decision evidence

The agent signs its exact intended action. Trellis records the evaluation against the agent’s delegation and policy. Keep these artifacts distinct: a signed request establishes what the signer requested; the event trail records what Trellis decided and what happened afterward.

Bind the organization, agent, delegation, action, audience, nonce, expiry and request terms. Execution references the authorization ID and original intent digest. An approval must refer to that same digest.

A signature is evidence of key possession and byte integrity. Use the decision status and current authority to determine the next step; an authorization alone is not proof of settlement.

Verify signed audit evidence →

Export and verify evidence

GET/v1/evidence

Access: authenticated organization member. Export the full event chain and a signed checkpoint from the workspace. Events bind organization, actor, sequence, predecessor digest, time, and decision data.

GET/v1/trust-root

Returns the authority’s public_key and key_id. Obtain and pin this public key through an independently trusted channel.

Event signatures use trellis-event-v1 plus a newline; checkpoint signatures use trellis-checkpoint-v1 plus a newline. Retain checkpoints outside the application to detect rollback to an older otherwise valid history.

Evidence shows what a key signed. It does not establish legal identity, runtime attestation, delivery, or settlement. A database administrator can bypass ordinary append-only protections.

Reconcile outcomes, preserve exceptions

The workbench compares execution records with imported provider observations. It is two-way transaction reconciliation, not bank-balance reconciliation or a financial ledger.

Method / routeAccess / purpose
POST /v1/reconciliation/importOwner session. Import 1–100 normalized events atomically.
POST /v1/reconciliation/runsOwner/approver session. Send {} to save an immutable snapshot.
GET /v1/reconciliationHuman session. Read recent runs and review notes.
POST /v1/reconciliation/reviewsOwner/approver session. Append an attributed investigation note.
GET /v1/reconciliation/example-feedHuman session. Download an example feed for the workflow.
Import request — replace reference with the execution’s provider_reference
{
  "events": [
    {
      "event_id": "demo-settled-1",
      "reference": "PROVIDER_REFERENCE_FROM_EXECUTION",
      "revision": 1,
      "status": "settled",
      "amount_minor": 26000,
      "fee_minor": 0,
      "currency": "USD",
      "supplier_id": "northstar",
      "occurred_at": 1790000000
    }
  ]
}

Set occurred_at to the event time. Revisions are contiguous per payment, starting at 1. An initial settled or failed record is allowed; a returned record needs prior settlement. Identical normalized duplicates are skipped. Conflicting event IDs are retained and surfaced as exceptions.

Matching checks reference, amount, currency, recipient, and history. Missing records remain unresolved. Fees are reported separately, not independently validated. Review notes acknowledge investigation; they do not overwrite a finding or move money. Example feeds derived from internal orders are not independent settlement evidence.

API contract

Download the OpenAPI document from your hosted workspace for exact routes, request fields, and response schemas. These integration examples use the deployed v1 HTTP contract.

curl -sS "$TRELLIS_URL/openapi.json" -o trellis-openapi.json

Keep your client aligned with that workspace’s contract. Developer credentials authenticate integration access; registered agent signatures and delegations authorize actions.

Discuss an integration ↗