> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keystoneos.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# BitGo Repo Demo

> Create and read one linked Canton/Sepolia repo as an immutable record, in the view your actor is granted, and know exactly what the record does not claim yet.

The BitGo repo demo exposes one repurchase agreement between two custodian clients: Canton test collateral (`tcanton`) against an ERC-20 cash leg on Sepolia. The API holds the agreed terms and the identities of the four movements as a durable record. It does not execute, sign, hold, settle or confirm anything: provider execution is a separate, later integration, and until it is qualified every record stays in its initial state.

<Warning>
  The demo exists only in non-production deployments that carry a trusted configuration. In production the routes are not mounted, and a deployment refuses to start with the configuration set. Without a configuration the routes are not mounted either: the paths answer the API's ordinary `404 HTTP_ERROR` `Not Found` before any credential is read, and they are absent from that deployment's `/openapi.json`. This reference documents them from a configured application.
</Warning>

## What the record is, and is not

| It is | It is not |
| - | - |
| An immutable record of one repo's economics: collateral amount, cash principal, fixed repayment, maturity | A settlement, an escrow trade or a custody bundle; it runs no state machine |
| Four fixed movements with stable ids: open collateral A to B, open cash B to A, repay A to B, return collateral B to A | A transfer, a hold or a signed instruction; no provider is called |
| A log of what happened to the record, starting with `repo_created` | Evidence of any ledger, acceptance or book effect |

The repayment is whatever amount the operator approved in the template. It may be below the principal; the API derives no rate from the pair.

## Who may call

Every route needs a platform credential with `settlements:read` (reads) or `settlements:write` (create, commands), under an active environment. A scope admits you to the route; it never makes you an actor. The operator's configuration binds each authenticated identity (a user's subject or an environment's M2M client) to exactly one actor:

| Actor | May | Views |
| - | - | - |
| `source` | Create repos, read | `source`, plus any view the operator grants |
| `client_a` (borrower) | Read | the views the operator grants, usually `custodian_a` |
| `client_b` (lender) | Read | the views the operator grants, usually `custodian_b` |

A session token is never an actor. An identity the configuration does not bind, a credential from another environment, or a deactivated platform or environment is refused with `403`. A repo your actor holds no role on is `404 Repo not found`, exactly as an unknown id.

The `view` query parameter narrows: omitted, the sole granted view is answered; with several granted, one must be named; naming a view you are not granted is `403`. Every view carries the same economics and the same shared progress. No view carries a wallet, a party, a participant address, a participant's platform or environment, or a provider record.

## Endpoints

| Route | Scope | Answers |
| - | - | - |
| `POST /v1/demo/repos` | `settlements:write` | `201` with the snapshot; `200` with `Idempotency-Replayed: true` for the same key and body |
| `GET /v1/demo/repos/{repo_id}` | `settlements:read` | The snapshot in one view |
| `GET /v1/demo/repos/{repo_id}/events` | `settlements:read` | A page of the log after `cursor` (default `0`), at most `limit` (default 50, max 200) |
| `POST /v1/demo/repos/{repo_id}/commands` | `settlements:write` | `503 HTTP_ERROR` `Repo execution is not qualified`, nothing written |

All bodies and responses use the `bitgo-repo-demo.v1` wire: camelCase keys only, every quantity, revision and cursor a canonical decimal string, every instant UTC. A snake\_case key, an unknown key, a number where a string is expected, or any field that would name a wallet, a state or an amount is refused `422` with nothing written.

### Create

```json theme={null}
POST /v1/demo/repos
{
  "schemaVersion": "bitgo-repo-demo.v1",
  "templateId": "bitgo-repo-demo",
  "idempotencyKey": "repo-2026-10-07-001"
}
```

`templateId` names a template the operator configured; a name that is not configured is a field-level `422`. The same `idempotencyKey` with the same body answers `200` with the record as it was created, under the terms of that day, whatever the template says now. The same key with a different body is `409 IDEMPOTENCY_CONFLICT`.

### The snapshot

```json theme={null}
{
  "schemaVersion": "bitgo-repo-demo.v1",
  "repoId": "…", "runId": "…", "revision": "0",
  "templateId": "bitgo-repo-demo",
  "templateDigest": "…64 hex…", "termsDigest": "…64 hex…",
  "view": "source",
  "createdAt": "2026-10-07T12:30:00Z",
  "terms": {
    "borrower": "client_a", "lender": "client_b",
    "collateral": {"network": "canton-testnet", "assetId": "tcanton", "symbol": "testCC", "decimals": 10, "amountBaseUnits": "25000000000"},
    "cash": {"network": "sepolia", "chainId": "11155111", "tokenAddress": "0x…", "symbol": "USDC", "decimals": 6, "principalBaseUnits": "100000000000000000000000000", "repaymentBaseUnits": "100500000000000000000000000"},
    "maturityAt": "2026-12-31T00:00:00Z"
  },
  "opening": {"phaseId": "…", "kind": "opening", "complete": false, "movementIds": ["…", "…"]},
  "closing": {"phaseId": "…", "kind": "closing", "openingPhaseId": "…", "complete": false, "movementIds": ["…", "…"]},
  "movements": [
    {"movementId": "…", "phaseId": "…", "kind": "opening_collateral", "fromActor": "client_a", "toActor": "client_b", "assetId": "tcanton", "amountBaseUnits": "25000000000",
     "executionStatus": "not_started", "signingStatus": "not_started", "acceptanceStatus": "not_observed", "ledgerStatus": "not_observed", "bookStatus": "not_observed", "evidence": [], "observedAt": null}
  ],
  "nextActions": [],
  "blockers": [{"code": "provider_execution_unqualified", "message": "Repo execution is not qualified"}],
  "trust": {"provider": "bitgo_test", "custodyModel": "self_custody_mpc", "reservation": "simulated", "crossNetworkAtomicity": false},
  "authority": {"state": "not_observed", "observedAt": null},
  "decision": {"state": "not_started", "observedAt": null},
  "reservation": {"mode": "simulated", "state": "not_observed", "observedAt": null},
  "asOf": {"sequence": "0", "observedAt": null}
}
```

The movements are always the four above in that order, and the cash `assetId` is `eip155:11155111/erc20:` followed by the lowercase token address. In this release every status is its initial literal, `nextActions` is empty, `evidence` is empty and both phases are incomplete: the schema admits nothing else, so no response can claim a movement started, a signature, an acceptance, a ledger effect, a book posting, a live reservation or a Registry decision. Those vocabularies are added only together with the provider evidence that proves them.

### Events

```json theme={null}
GET /v1/demo/repos/{repo_id}/events?cursor=0&limit=50
{
  "schemaVersion": "bitgo-repo-demo.v1",
  "repoId": "…",
  "view": "custodian_a",
  "events": [{"eventId": "…", "sequence": "1", "recordedAt": "2026-10-07T12:30:00Z", "kind": "repo_created"}],
  "nextCursor": null,
  "hasMore": false
}
```

An event names what happened and when. It carries no payload. `cursor` is the last sequence you hold, a canonical decimal string of at most `9223372036854775807`, the last sequence the log can hold; a cursor past that is refused `422` naming `query.cursor`. When `hasMore` is true, `nextCursor` is the sequence to continue after.

### Commands

`POST /v1/demo/repos/{repo_id}/commands` takes `{"schemaVersion", "actionId", "expectedRevision", "idempotencyKey"}`. In this release no action is offered, so after your credential, your actor and the repo are verified, every command answers `503 HTTP_ERROR` with `Repo execution is not qualified`, and no command, event or revision is written. A `503` is not an accepted command, and nothing retries it for you.

## Errors

| Status | Code | When |
| - | - | - |
| `401` | existing auth codes | No or invalid credential |
| `403` | `INSUFFICIENT_SCOPES` | Missing scope; identity not bound to an actor; wrong or inactive environment; view not granted; creating as a participant |
| `403` | `SESSION_WRITE_DENIED` | A session token on create or commands |
| `403` | `NO_ENVIRONMENT_CONTEXT` | A user token without `X-Keystone-Environment` |
| `404` | `HTTP_ERROR` | `Repo not found` |
| `409` | `IDEMPOTENCY_CONFLICT` | Same key, different body |
| `409` | `HTTP_ERROR` | A participant environment is not registered or not active |
| `422` | `VALIDATION_ERROR` | Not the closed camelCase shape, an unknown template, a malformed id or cursor |
| `503` | `HTTP_ERROR` | Any command: `Repo execution is not qualified` |

No error carries a configured value: not a wallet, a party, an address, an identity, a template's terms or the manifest.

## Digests

`termsDigest` and `templateDigest` are SHA-256 over the UTF-8 bytes of the RFC 8785 canonical JSON of a closed document (strings, booleans, null, arrays and objects only; every integer a decimal string; instants spelled `YYYY-MM-DDTHH:MM:SS.ffffffZ`; UUIDs lowercase; EVM addresses lowercase), rendered as 64 lowercase hex characters.

* `templateDigest` binds `{"schemaVersion": "bitgo-repo-demo.v1", "kind": "repo-template", "template": {templateId, collateral, cash, maturityAt}}`.
* `termsDigest` binds `{"schemaVersion": "bitgo-repo-demo.v1", "kind": "repo-terms", "template", "roles", "source", "participants"}`: the same template economics, the fixed roles, the source's platform and environment, and both participants in order with their platform, environment and pinned wallet routing on each network. Grants and anything mutable are outside it, so the digest never moves with a permission.

The participants' wallet routing is inside the digest but never inside a response. The accepted-shape golden vector at `tests/fixtures/demo_repo/golden_vectors.json` holds synthetic documents whose every identifier is in the shape the runtime admits (32-hex wallet ids, a printable hint and a 68-hex fingerprint per Canton party, lowercase non-zero EVM addresses), their exact canonical text and their digests, for independent implementations to reproduce. It is synthetic throughout and claims no provider support for a coin and no control of a wallet.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.