Docs Β· Protocol spec

Timbro Protocol Spec β€” v0.1

1. Primitives

1.1 Identity

1.2 Contract

The escrow object. Fields: buyer_did, seller_did, amount (decimal string), currency (USD | USDC), rail (stripe | usdc), deliverable_hash (sha256 of the agreed deliverable spec), acceptance policy, expires_at.

Acceptance policy:

{ "type": "buyer_approval | mutual | oracle",
  "timeout_hours": 72,
  "fallback": "release | refund | split",
  "oracle_url": "https://… (only for type=oracle)" }

1.2a Delivery proof

A delivery has two parts: artifact_hash (integrity β€” *this exact blob*) and artifact_uri (retrievability β€” *go look at it yourself*). Both are persisted on the contract and exposed to the buyer and to arbiters via GET /v1/contracts/{id}, the contract.delivered webhook, and the arbiter evidence package. A buyer should never approve blind: fetch the artifact URI and check it before approving.

URL deliverables: set deliverable_type: "url" and deliverable_target (the URL the artifact must be *about*, e.g. the site that was scanned) at contract creation. For these contracts artifact_uri is required on delivery and must be a well-formed http(s) URL. services/deliverables.py provides the verifier: verify_contract_delivery(contract, resolve_target) compares the committed deliverable_target with the target the delivered artifact is actually about. Timbro never fetches artifacts itself (that would be an SSRF hole) β€” the buyer or arbiter supplies resolve_target, a function that retrieves the artifact and extracts its target. Without a resolver the verifier reports the artifact as retrievable-but-uncompared rather than guessing.

1.3 Reputation

Append-only ledger_events table; the score is a view, recomputed on read. v0.1 formula (published β€” transparency is the product):

completion_rate = clean_settlements / max(1, closed_contracts)
dispute_rate    = disputes / max(1, closed_contracts + disputes)
volume_factor   = min(1, log10(1 + settled_volume) / 4)

score = clamp(0, 1000,
        1000 * (0.65 * completion_rate + 0.35 * volume_factor)
        βˆ’ 300 * dispute_rate)

DIDs with no history return score: null, status: "unproven" β€” distinct from 0.

1.4 Reputation v2 (adversarial-hardened, ?model=v2, default)

See docs/THREAT_MODEL.md for the full analysis. Changes vs v1:

2. Lifecycle

awaiting_funding ──fund──► funded ──deliver──► delivered ──approve──► settled
       β”‚                     β”‚                      β”‚  β–²                 β–²
       β”‚                     β”‚ dispute              β”‚  β”‚ dispute         β”‚ resolve
       β–Ό                     β–Ό                      β–Ό  β”‚                 β”‚
    expired               disputed ──resolve──► settled (release|refund|split)
       β”‚                     β”‚
       └── timeouts β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3. API surface (v0.1)

Method & pathActorEffect
GET /v1/identities/{did}anyoneresolve DID document
POST /v1/contractsbuyercreate contract (awaiting_funding)
GET /v1/contracts/{id}anyonestatus (applies timeouts)
POST /v1/contracts/{id}/fundbuyerconfirm rail funding β†’ funded
POST /v1/contracts/{id}/deliversellersubmit artifact_hash β†’ delivered
POST /v1/contracts/{id}/approvebuyerrelease funds β†’ settled
POST /v1/contracts/{id}/disputebuyer/seller→ disputed
POST /v1/contracts/{id}/resolveoperatorrelease/refund/split β†’ settled
POST /v1/contracts/{id}/arbitratebuyer/sellerassign arbiter
POST /v1/contracts/{id}/verdictassigned arbiterverdict β†’ settled
POST /v1/contracts/{id}/appealbuyer/sellerreopen β†’ disputed (once, 48h)
POST /v1/arbitersanyone (API key)register arbiter
GET /v1/arbiters/{did}/recordanyoneverdict track record
GET /v1/reputation/{did}anyonescore + components
POST /v1/webhooks/endpointsanyone (API key)subscribe to events

Auth: X-API-Key header on all calls; mutating contract calls additionally require X-Actor-DID matching the buyer/seller as per actor rules.

4. Arbitration (oracle-arbiter plugin)

Disputes are resolved by third-party arbiters, not the operator. Full design: docs/ARBITRATION.md.

5. Webhooks

Events: contract.funded, contract.delivered, contract.settled, contract.disputed, contract.expired. Deliveries are HMAC-SHA256 signed (X-Timbro-Signature) with the endpoint secret.

5. Rails

Rails are plugins implementing `funding_instructions / confirm_funding / release / refund. v0.1 ships stripe and usdc` as simulated implementations behind TIMBRO_RAIL_MODE. Live mode requires keys and is explicitly unwired β€” the interfaces are stable so wiring is mechanical.

Core invariant: Timbro never custodies funds. Fiat rests with Stripe Connect; USDC rests in a programmatic escrow address. We orchestrate.