Skip to content

API endpoints

Sill exposes its public surface from a single edge origin, https://edge.sill.so. All routes are unversioned at the host level; per-route versioning is in the path (/v1/… for the discovery and transactional routes, /.well-known/jwks.json for the verifier-facing key set). Every signed response is independently verifiable against the public JWKS using off-the-shelf ed25519 tooling — no Sill SDK is required, and there is no Sill SDK to vendor.

The transactional mandate endpoint is in Phase 2 and operates inside the limited-production bounds documented there; the rest of the surface is live for every verified site.

MethodPathPurposeAuthDoc
POST/v1/discovery/ingestDiscovery beacon from the embed script.None (public, site-key scoped).Embed script
GET/v1/agent-card/{site_key}.jsonPer-site signed A2A-compatible agent card.None.Agent card
GET/v1/catalog/{site_key}.jsonPer-site signed ARD ai-catalog.json.None.ARD catalog
POST/v1/mcp/{site_key}Per-site Streamable-HTTP MCP server.None at the transport; tool invocation is policy-gated.MCP server
POST/v1/m/{site_key}/mandateTransactional mandate submission.ed25519-signed mandate in the request body.Signed mandates
GET/.well-known/jwks.jsonPublic JWKS for verifying every Sill-issued signature.None.Public JWKS

{site_key} is the public per-site credential issued in the dashboard. It is a public identifier, not a secret — the same posture as a Stripe publishable key. The site-key shape is validated at the edge against ^(sk_)?[A-Za-z0-9_-]{43}$ before any KV read.

  • Transport: HTTPS only. The edge does not serve plaintext.
  • CORS: every route handles its own OPTIONS preflight at the edge. Public GET surfaces (agent-card, catalog, jwks) reply with Access-Control-Allow-Origin: *. The mandate endpoint echoes the request Origin and never sets Access-Control-Allow-Credentials — authentication is the signed mandate body, not a cookie.
  • Caching: signed GET surfaces carry Cache-Control: public, max-age=60 (cards and catalogs) or public, max-age=300 (JWKS). Error responses are no-store to prevent intermediate caches from pinning a 404 for a key that may become valid.
  • Method-not-allowed: any method outside the documented set returns 405 with an Allow: header naming the supported methods.
  • Anti-fingerprinting: 404 responses for unknown or malformed site keys return a constant body ({"error":"not_found"}) so probing cannot distinguish “never existed” from “exists but you can’t see it”.
  • Timestamps: every timestamp on the wire is ISO-8601 UTC.
  • Encoding: signatures and public keys use unpadded base64url per RFC 4648 §5.
  • Canonicalization: every signed payload is canonicalized with RFC 8785 JCS before signing.
flowchart LR
    Browser[Merchant page<br/>+ embed.js] -->|POST /v1/discovery/ingest| Edge
    Agent[AI agent] -->|GET, POST /v1/...| Edge
    Verifier[Third-party verifier] -->|GET /.well-known/jwks.json| Edge
    Edge[Edge · edge.sill.so] -->|signed records| Origin[Sill origin]

The edge owns the latency-sensitive surface — manifest serving, MCP transport, mandate verification, signing-key publication. The origin owns audit storage, the identity registry, and connectors. Every signed surface uses the same ed25519 key (kid: foyer/edge/card-signing-v1), so a single JWKS resolves both an agent card and an ARD trust manifest.

POST https://edge.sill.so/v1/discovery/ingest

The fire-and-forget beacon the embed script emits once per page load. The body is a small JSON envelope (schema_version: "1") carrying the site key, an observed_at timestamp, and nested identification and page_context objects; success is 204 No Content with no body.

Terminal window
curl -i -X POST https://edge.sill.so/v1/discovery/ingest \
-H 'Content-Type: application/json' \
--data '{
"schema_version": "1",
"site_key": "sk_EXAMPLE_REPLACE_WITH_YOURS_AAAAAAAAAAAAAAAAA",
"observed_at": "2026-06-22T14:03:11.000Z",
"identification": {},
"page_context": {}
}'

The merchant’s bundle does not have to call this directly — the embed script handles it via navigator.sendBeacon. The endpoint is documented here for completeness and for diagnostic curl. See Identifying agents for how Sill classifies the caller.

GET https://edge.sill.so/v1/agent-card/{site_key}.json

Per-site, signed, A2A-compatible agent card. protocol_version: "a2a/1.2". The card’s skills[] is backing-filtered — only skills the site actually exposes through a wired backing appear.

Terminal window
curl -i "https://edge.sill.so/v1/agent-card/SITE_KEY.json"

Response headers (200):

HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: public, max-age=60
access-control-allow-origin: *

The card body includes a detached ed25519 JWS (alg: EdDSA, crv: Ed25519) over the canonical card payload. To verify, follow the Verify a signature recipe; the JWKS is at /.well-known/jwks.json.

404 is returned uniformly for unknown, malformed, or non-existent site keys; 503 is returned when an upstream dependency is unreachable on a cold path.

GET https://edge.sill.so/v1/catalog/{site_key}.json

Per-site signed Agentic Resource Discovery ai-catalog.json. specVersion: "1.0". host.identity: did:web:{domain} per the did:web method. Each trustManifest carries a compact, detached ed25519 JWS over the JCS-canonical manifest body.

Terminal window
curl -i "https://edge.sill.so/v1/catalog/SITE_KEY.json"

Response headers (200) match the agent card’s: Cache-Control: public, max-age=60, CORS *. The signature attests the host identity + provenance; the per-entry url / capabilities / description fields are not inside the signed payload — their integrity rests on did:web + TLS per the ARD spec.

POST https://edge.sill.so/v1/mcp/{site_key}

Per-site Streamable-HTTP Model Context Protocol server. Implements the standard MCP handshake (initialize), tool discovery (tools/list, backing-filtered to match the agent card), and tool invocation (tools/call, recorded in the site’s signed audit log).

Terminal window
curl -i -X POST "https://edge.sill.so/v1/mcp/SITE_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "example-client", "version": "0.0.0" }
}
}'

MCP responses themselves are not signed — only the agent card’s pointer to the MCP endpoint is signed. Money-movement skills (e.g. place_order) require a signed mandate and route through the same transactional pipeline as /v1/m/{site_key}/mandate. See MCP server for the scope of claim.

POST https://edge.sill.so/v1/m/{site_key}/mandate

Submits a signed mandate for verification, policy evaluation, and (on approval) processor authorization. Authentication is the ed25519 signature carried inside the request body — there is no Authorization header, no cookie, and no API key. The body is capped at 8 KB; oversize requests reject before parsing.

Terminal window
curl -i -X POST "https://edge.sill.so/v1/m/SITE_KEY/mandate" \
-H 'Content-Type: application/json' \
-H 'X-Sill-Request-Id: req_example_correlate_me' \
--data @mandate.json

mandate.json is the two-part { signed, envelope } shape described in Signed mandates.

The response body is a small JSON envelope discriminated by an outcome field. The HTTP status is the secondary signal — the outcome is load-bearing.

OutcomeHTTPWhen
approved200Policy passed; the processor was asked to authorize.
escalated202Policy escalated to human-in-the-loop review.
rejected400 / 401 / 403 / 404 / 413 / 415Mapped to the rejection class — see Signed mandates → rejection taxonomy.
rate_limited429Per-account or per-site limiter tripped; includes retry_after_seconds.
unavailable503Verifier dependencies (nonce store, registry) could not answer. Fail-closed.

Example approved response:

HTTP/2 200
content-type: application/json
cache-control: no-store
{
"outcome": "approved",
"mandate_id": "mnd_01J9XYZABCDEFGHJKMNPQRSTVW",
"record_id": "rec_01J9XYZAAAAAAAAAAAAAAAAAAA",
"evaluated_at": "2026-06-22T14:03:11.000Z",
"rules_evaluated": 28
}

Example rejected response (signature failed verification):

HTTP/2 401
content-type: application/json
cache-control: no-store
{
"outcome": "rejected",
"reason": "signature_invalid"
}

Clients may send an X-Sill-Request-Id header for correlation; if present, it is echoed back on the response.

GET https://edge.sill.so/.well-known/jwks.json

The public ed25519 signing key in RFC 7517 JWKS form. The same key signs every Sill-published agent card and ARD trust manifest, so a verifier resolves both surfaces with a single fetch.

Terminal window
curl -i "https://edge.sill.so/.well-known/jwks.json"

Response (truncated for the example x value):

HTTP/2 200
content-type: application/jwk-set+json; charset=utf-8
cache-control: public, max-age=300, s-maxage=300
access-control-allow-origin: *
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"alg": "EdDSA",
"use": "sig",
"kid": "foyer/edge/card-signing-v1",
"x": "<base64url-32-byte-public-key>"
}
]
}

Algorithm + key type follow RFC 8037 (EdDSA in JOSE) and JWS. See Verify a signature for the end-to-end recipe.

The signed-surface endpoints are designed so that a third party can prove provenance without trusting Sill’s word for it.

sequenceDiagram
    autonumber
    participant Verifier as Third-party verifier
    participant Edge as edge.sill.so
    Verifier->>Edge: GET /v1/agent-card/{site_key}.json
    Edge-->>Verifier: 200 + signed card (detached JWS)
    Verifier->>Edge: GET /.well-known/jwks.json
    Edge-->>Verifier: 200 + JWKS (ed25519 public key)
    Verifier->>Verifier: JCS-canonicalize card payload
    Verifier->>Verifier: Reconstruct JWS signing input
    Verifier->>Verifier: ed25519.verify(sig, input, jwk.x)

The same flow applies to the ARD catalog’s trust manifest. The transactional mandate uses an analogous recipe but with a different domain-separation tag (sill-mandate-v1) and the agent’s registered key, not Sill’s; see Signed mandates → verifying a mandate signature.

Is there a Sill SDK? No. The verifier path uses only standard tools: any RFC 8785 (JCS) canonicalizer and any reputable ed25519 library (@noble/ed25519, pynacl, tweetnacl, OpenSSL). Sill publishes no client SDK for the verifier surface, and there is no Sill code in the verifier path.

Do I authenticate to call these endpoints? The GET surfaces (agent-card, catalog, jwks) and the discovery ingest are unauthenticated. The MCP transport is unauthenticated; tool invocation is policy-gated, and money-movement tools require a signed mandate. The transactional mandate endpoint authenticates via the ed25519 signature inside the request body — there is no API key or bearer token.

Why is the site key in client-side HTML? The site key is a public identifier, not a secret. It is analogous to a Stripe publishable key. It identifies which Sill site an interaction belongs to; it does not authorize any privileged action on its own.

What happens if Sill’s verifier can’t answer? The mandate endpoint returns 503 unavailable. Sill is fail-closed by design — an unreachable registry or nonce store never produces an approval. See Signed mandates → replay defense.

Which protocols does the mandate endpoint accept? Currently a2a, ap2, and mcp on the edge allowlist. acp, ucp, and x402 are on the roadmap; see Protocols reference.

Is there a status page? The origin liveness check is at https://api.sill.so/healthz. Sill does not currently publish an uptime SLA for the edge endpoints; see Security overview.