Multi-Tenant Architecture
Overview
Section titled “Overview”A single Exchange Node serves many providers. This is the multi-tenant model — one Exchange operator runs an Exchange that serves 10, 100, or 1000+ providers. Each provider is represented as a “tenant” with isolated configuration, catalog, signing keys, and reporting obligations.
Per-Tenant Configuration
Section titled “Per-Tenant Configuration”// TenantConfig holds all provider-specific configuration.// A tenant represents a provider account (e.g., "Hearst Media") which may// operate multiple domains (cosmopolitan.com, esquire.com, elle.com, etc.).// URI resolution uses a domain→tenant lookup map, not offer_id prefix parsing.type TenantConfig struct { // Identity ID string // Unique tenant identifier (e.g., "hearst-media") Domains []string // All provider domains (e.g., ["cosmopolitan.com", "esquire.com", "elle.com"])
// Resource catalog (per-domain RSL discovery) RSLURLs map[string]string // domain -> RSL URL (e.g., "cosmopolitan.com" -> "https://cosmopolitan.com/rsl.txt") RSLPollInterval time.Duration // How often to re-fetch RSL (default: 5m)
// Pricing overrides (Exchange is the price authority — RSL pricing is a fallback hint) // Private pricing from Exchange config overrides RSL-derived pricing. PricingOverrides map[string]*rampv1.Pricing // path pattern -> pricing
// Content delivery ContentBaseURL string // CDN base URL (e.g., "https://cdn.techcrunch.com") CDNType CDNType // CloudFront, Akamai, Fastly, GenericHMAC SigningKeyRef string // Reference to key in secrets manager
// Signed URL configuration URLTTLSeconds int // Default: 300 (5 minutes) BindAgentID bool // Whether to include agent identity in signed URL
// Reporting policy ReportingPolicy *rampv1.ReportingObligation
// Application-level abuse thresholds (volumetric rate limiting is at infra layer) MinQueryToTxnRatio float64 // If query:txn ratio drops below this, return 429 (e.g., 0.01)
// Revenue share (not protocol-relevant, but needed for billing) RevenueSharePct float64 // Provider's share (e.g., 0.85 = 85%)
// v1.0: Attestation and dispute configuration CatalogContributors []CatalogContributor // Authorized third-party push sources VerifierTrustConfig *VerifierTrustConfig // Per-tenant verifier trust settings DisputeConfig *DisputeConfig // Per-tenant dispute thresholds}Tenant Isolation Model
Section titled “Tenant Isolation Model”┌──────────────────────────────────────────────────────┐│ Exchange Node ││ ││ Shared: ││ - HTTP server (Connect-Go) ││ - Transaction log writer (WAL) ││ - Billing adapter connection pool ││ - Metrics collector ││ - (Volumetric rate limiting at infra layer) ││ ││ Per-Tenant (isolated): ││ - Resource catalog (in CatalogSnapshot.Tenants) ││ - Signing keys (in SigningEngine) ││ - RSL ingestion goroutine ││ - Abuse detection counters (query:txn ratio) ││ - Reporting obligations ││ - Verifier trust configuration (v1.0) ││ - Dispute thresholds (v1.0) ││ - catalog_contributors authorization (v1.0) ││ ││ NOT shared across tenants: ││ - Signing key material ││ - Offer signatures (per-tenant Ed25519 key pair) ││ - Content paths ││ - Pricing configuration │└──────────────────────────────────────────────────────┘Isolation Guarantees
Section titled “Isolation Guarantees”- Data isolation: No tenant can access another tenant’s catalog, keys, or transaction records. Offer signatures are per-tenant (different Ed25519 key pairs). Transaction log records include tenant_id for filtering.
- Performance isolation: Per-tenant rate limiting prevents a noisy tenant from consuming all capacity. Optional: per-tenant goroutine pools for RSL ingestion.
- Failure isolation: An RSL ingestion failure for tenant A does not affect tenant B’s catalog. Each tenant’s catalog is independently rebuildable.
Domain-to-Tenant Mapping
Section titled “Domain-to-Tenant Mapping”Tenant resolution: The Exchange resolves tenant from the requested URI(s) in the ResourceQuery. The URI’s domain is matched against a domain-to-tenant lookup map. A tenant (e.g., “Hearst Media”) maps to many domains (cosmopolitan.com, esquire.com, etc.). For ExecuteTransaction, the tenant is resolved from the domain embedded in the signed offer token — no offer_id prefix parsing.
// Resolve tenant from the request's URIs (repeated string uris)tenantCfg, err := h.tenants.ResolveFromURIs(req.Msg.Uris)
// Resolve tenant from offer domain (ExecuteTransaction path)tenantCfg, err := h.tenants.ResolveFromDomain(offer.Domain)Provider Onboarding
Section titled “Provider Onboarding”Adding a new provider tenant requires:
- Adding a
TenantConfigentry (config file, API, or database) - Uploading the provider’s CDN signing key to the secrets manager
- The provider dropping
ramp.jsonpointing to this Exchange - The Exchange’s RSL ingestion goroutine automatically discovers and builds the catalog
No restart required if hot reload is enabled (see the Configuration page).
Provider Trust Verification
Section titled “Provider Trust Verification”The Exchange SHOULD periodically re-fetch /.well-known/ramp.json for each tenant’s domains to confirm the provider still authorizes this Exchange to sell their resources. If the Exchange is removed from a provider’s ramp.json:
- Revoke the tenant configuration
- Stop serving offers for that provider’s resources
- Log the revocation for audit
This is ongoing verification, not just onboarding. Providers can revoke authorization at any time by updating their ramp.json. The Exchange SHOULD check at least once per hour (configurable via tenant settings).
Key Rotation via Tenant Management API
Section titled “Key Rotation via Tenant Management API”Providers who don’t operate infrastructure manage signing keys via the Exchange tenant management API. The Exchange operator provides these endpoints as part of the tenant onboarding experience.
Key Storage
Section titled “Key Storage”- Keys stored encrypted at rest: AWS KMS envelope encryption or HashiCorp Vault transit engine
- Per-tenant key isolation — each tenant’s keys are under a separate KMS key or Vault path
- Access audit trail on all key operations
Rotation Protocol (API-Driven)
Section titled “Rotation Protocol (API-Driven)”- Provider uploads new key via
POST /admin/tenants/{tenant_id}/signing-keys - Exchange stores new key encrypted, marks as
pending_activation - Provider configures new key on their CDN (or Exchange manages CDN config for managed providers)
- Provider activates via
POST /admin/tenants/{tenant_id}/signing-keys/{key_id}/activate— new key becomesActiveKey, old key becomesPreviousKey - Dual-key grace period (configurable, default 1 hour): CDN and Exchange accept signatures from both keys
- After grace period:
PreviousKeyis archived (retained for audit, not used for signing)
Automatic Rotation
Section titled “Automatic Rotation”For managed providers, the Exchange supports automatic rotation on a configurable schedule (default: 90 days per NFR). The Exchange generates a new key pair, provisions it on the CDN, and rotates with a dual-key grace period — no provider action required.
Provider Manifest and RSL Endpoints
Section titled “Provider Manifest and RSL Endpoints”The edge function expects to fetch ramp.json (WellKnownManifest, role=ROLE_PUBLISHER) and rsl.txt from the Exchange on behalf of provider domains. The Exchange generates these from its tenant catalog and serves them for edge caching.
Endpoints
Section titled “Endpoints”GET /provider/{domain}/ramp.jsonReturns a generated WellKnownManifest (role=ROLE_PUBLISHER) for the given domain. The manifest is built from the tenant’s catalog configuration (pricing, access restrictions, exchange endpoint). Cached by the edge function with a TTL matching the tenant’s rsl_poll_interval.
GET /provider/{domain}/rsl.txtReturns a generated RSL document for the given domain. The RSL is synthesized from the tenant’s catalog entries (URI patterns, pricing, access policies). Cached by the edge function. Regenerated when the catalog is updated.
Both endpoints return 404 Not Found if the domain is not associated with any tenant.
Public Key Exchange
Section titled “Public Key Exchange”Brokers and agents fetch the Exchange’s /.well-known/ramp.json and read public_keys to verify exchange_signature on Offers without manual key exchange. The Exchange’s offer-signing Ed25519 public keys are inline in that manifest as RFC 7517 JWKs, selected by kid.
Note: The Exchange’s ramp.json public_keys serve the Exchange’s offer-signing keys. Agent keys are separate — they are fetched from the agent’s /.well-known/ramp.json manifest (self-signup) or stored from enterprise registration (out-of-band key exchange).
Since the default offer signature algorithm is Ed25519, these are public keys — ramp.json does NOT require authentication. Public keys are safe to expose; they can only verify signatures, not create them.
public_keys entries (in the Exchange’s ramp.json):
{ "public_keys": [ { "kid": "mk-2026-03", "kty": "OKP", "crv": "Ed25519", "use": "sig", "alg": "EdDSA", "x": "<base64url>", "not_before": "2026-03-01T00:00:00Z", "not_after": "2026-06-01T00:00:00Z" }, { "kid": "mk-2026-01", "kty": "OKP", "crv": "Ed25519", "use": "sig", "alg": "EdDSA", "x": "<base64url>", "not_before": "2026-01-01T00:00:00Z", "not_after": "2026-03-01T00:00:00Z" } ]}- Returns current and previous keys (for rotation grace period); each key’s validity window is half-open
[not_before, not_after) - No authentication required — Ed25519 public keys are safe to expose (they can only verify, not sign)
- Brokers SHOULD cache with 24-hour TTL
- On signature verification failure, refresh key cache before retrying
- The
xfield contains the base64url-encoded Ed25519 public key (32 bytes)
CatalogService Authentication
Section titled “CatalogService Authentication”The Exchange exposes CatalogService (PushResources, RemoveResources, RefreshCatalog) for external parties to push resource metadata. Every CatalogService request is authenticated:
-
Verify the RFC 9421 HTTP Message Signature: The
caller_idfield identifies the pusher, and the request is signed with an RFC 9421 HTTP Message Signature carried in the HTTP headers (Signature/Signature-Input, with aContent-Digestover the body) — there is no in-message caller signature field. The Exchange verifies it against the caller’s published Ed25519 key at{caller domain}/.well-known/ramp.json(selected bykid). Invalid signatures are rejected withCodeUnauthenticated. -
Check
caller_idauthorization: The Exchange verifies thatcaller_idis registered and authorized to push resources for the specifiedtenant_id. Unauthorized tenant access is rejected withCodePermissionDenied. -
Caller registration: CatalogService callers are registered out-of-band (admin API or configuration). Registration includes:
caller_id— unique identifier for the caller- Caller domain — where the Ed25519 signing key is published (
{domain}/.well-known/ramp.json) - Authorized
tenant_idset — which providers this caller can push for - Caller type — “cms_plugin”, “intelligence_provider”, “syndicator”
This prevents unauthorized parties from injecting or modifying catalog entries. The same authentication mechanism applies to RemoveResources and RefreshCatalog calls.