Contract first

The checked-in OpenAPI document currently describes 143 paths and 190 operations. The Rust API generates it, and the Svelte client generates TypeScript types from the same file.

Tests fail when either checked-in layer drifts. That makes the contract executable: a new handler is not complete until clients can discover and type it.

Regenerate the contract and client types shell
mise run openapi
cd web
pnpm run gen:api

Authentication and scope

Authenticated API calls send a bearer key. A key resolves an identity, tenant and access predicate; handlers do not accept a tenant selector from request data.

Keys are shown once, stored hashed and represented in the interface only by an audit-safe prefix.

List visible assets shell
curl 'http://127.0.0.1:8080/assets?limit=20' \
  -H 'Authorization: Bearer $DAMRS_API_KEY'

Five MCP tools

MCP is another way into the same library, not a parallel authorization system.

ToolPurpose
search_assetsRun the same scoped query language as the interface
get_assetRead metadata and evidence for one visible asset
get_brand_guidelinesRetrieve the tenant guidance needed for use
check_rightsEvaluate a proposed channel and territory
get_download_urlRequest a purpose-bound delivery URL
  • Tool discovery reveals shapes, not hidden assets.
  • Every result is limited by the caller’s compiled access predicate.
  • get_download_url still enters the ordinary delivery chokepoint.
  • Cross-tenant asset identifiers return no useful oracle.

Errors should teach the contract

Query parsing refuses an unknown field and reports its character offset instead of silently treating a typo as free text. Readiness names the dependency that failed. Configuration rejects unknown keys at startup.

That directness is part of the API design: a client should be able to correct a request without reading Rust source or inferring state from an empty body.