# [Sources](https://rebilder.com/docs/sources)

> The `GatewaySources` contract: how the gateway reads your source of truth, field by field, and the guarantees that govern what it renders.

- **Updated:** 2026-08-12
- **Publisher:** Rebilder

## The contract

A source is a lookup from a request `URL` to your source-of-truth data. Return the data when the URL is yours; return `null` (or `undefined`) when it isn’t. Sync or async both work, but resolvers run on the edge hot path (p95 < 50ms compute budget), so back them with your in-memory catalog, a KV cache, or a fast local store, not an origin round-trip.

GatewaySources

```
interface GatewaySources {            // all optional; missing source => pass through
  product?:  (url: URL) => ProductSource | null | Promise<ProductSource | null>
  policies?: (url: URL) => PolicySource[] | null | Promise<PolicySource[] | null>
  catalog?:  (url: URL) => CatalogItemSource[] | null | Promise<CatalogItemSource[] | null>
}
```

## Resolution order & error containment

- **The order is fixed: `product` → `policies` → `catalog`.** The first source returning data wins, so product wins when several would match a URL.
- **A source that throws is treated as "no match"** and resolution continues with the next source. It never breaks your site.
- **If the render itself fails, the request passes through to HTML**; the gateway never substitutes a lower-precedence document for the one that matched.
- **An empty `policies`/`catalog` array is no match.** So is a missing source: configure only the resolvers you have.

## ProductSource

One product detail page. Rendering order is fixed and front-loaded: title (linked to the canonical URL) → brand → price (compare-at struck through when present: `~~$120.00~~ $89.00`) → availability → shipping → returns → variants table → description → attributes → images as markdown links. Absent optionals render nothing.

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `url` | `string` | yes | Canonical product URL; the linked title points here. |
| `title` | `string` | yes | Product title. |
| `brand` | `string` | no | Rendered as a **Brand** fact line. |
| `description` | `string` | no | Rendered verbatim under a Description heading. |
| `price` | `Money` | yes | Current price. Minor units (see Money below). |
| `compareAtPrice` | `Money` | no | Struck through next to the price when present. |
| `availability` | `Availability` | yes | `'in_stock' | 'out_of_stock' | 'preorder' | 'backorder'`, rendered with deterministic labels (`In stock`, …). |
| `variants` | `ProductVariantSource[]` | no | Rendered as a table: id / title / options / price / availability. |
| `shipping` | `ShippingSource` | no | Front-loaded shipping facts. |
| `returns` | `ReturnsSource` | no | Front-loaded returns facts. |
| `images` | `{ url: string; alt?: string }[]` | no | Rendered as markdown links at the bottom. |
| `attributes` | `Record<string, string>` | no | Material, size-chart facts, etc., rendered verbatim under Details. |

## ProductVariantSource, ShippingSource, ReturnsSource

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `ProductVariantSource.id` | `string` | yes | Variant identifier (SKU-like id shown in the table). |
| `ProductVariantSource.title` | `string` | yes | Variant title. |
| `ProductVariantSource.price` | `Money` | yes | Per-variant price. |
| `ProductVariantSource.availability` | `Availability` | yes | Per-variant stock state. |
| `ProductVariantSource.sku` | `string` | no | Explicit SKU when distinct from `id`. |
| `ProductVariantSource.options` | `Record<string, string>` | no | e.g. `{ Color: "Juniper Green" }`. |
| `ShippingSource.summary` | `string` | yes | Merchant-authored shipping summary. Rendered verbatim. |
| `ShippingSource.freeThreshold` | `Money` | no | Free-shipping threshold. |
| `ShippingSource.regions` | `string[]` | no | Ship-to regions. |
| `ShippingSource.etaDays` | `[number, number]` | no | [min, max] delivery estimate in days, from the merchant. |
| `ReturnsSource.summary` | `string` | yes | Merchant-authored returns summary. Rendered verbatim. |
| `ReturnsSource.windowDays` | `number` | no | Return window in days. |
| `ReturnsSource.url` | `string` | no | Link to the full returns policy. |

## PolicySource

Policy documents (shipping, returns, warranty, …), rendered as linked headings + verbatim bodies.

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `title` | `string` | yes | Policy title, rendered as a linked heading. |
| `url` | `string` | yes | Canonical policy URL. |
| `body` | `string` | yes | Policy text. Rendered verbatim, never summarized or reworded. |

## CatalogItemSource

Collection/catalog listings, rendered as a markdown table (linked title, price, availability).

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `url` | `string` | yes | Item URL; the table’s linked title points here. |
| `title` | `string` | yes | Item title. |
| `price` | `Money` | yes | Item price. |
| `availability` | `Availability` | yes | Same enum as products. |

## Money

| Field | Type | Notes |
| --- | --- | --- |
| `amount` | `number` | **Integer minor units** (cents for USD): `8900` → `$89.00`. Zero-decimal currencies are handled (`¥4,900`). |
| `currency` | `string` | ISO currency code, e.g. `USD`. |

A non-integer `amount` **throws** rather than rounds, because silently altering a price is never acceptable.

## What rendering guarantees

> **Injection-only** Every substantive value in the output (prices, availability, discounts, shipping and returns text, policy bodies, attributes) is injected verbatim from the source object. The renderer never invents, estimates, or rewords a price, stock state, claim, or policy. The only text it adds is structural scaffolding: fixed labels like `**Price:**`, section headings like `Variants`, deterministic enum labels like `In stock`, and a fixed truncation note.

> **Same substance across formats** Markdown output is a format transformation of the same substance as the canonical HTML page. The renderer has no inputs (requester identity, headers, UA) that could even make different-substance output possible; it is pure, deterministic, and fully offline: no LLM, no network, no clock, no locale.

**Size budget:** output is capped at `maxBytes` (default 5120 bytes, UTF-8). Buying facts are front-loaded, and truncation only ever removes content from the bottom (description, attributes, images) on whole lines, appending a fixed truncation note; the front-loaded facts and the variants table are never removed. If the facts alone exceed the budget, they are emitted anyway: facts are never sacrificed to the byte budget.