A Connection keeps a Page current from an approved source. Refresh runs deterministic transforms and rendering; no model runs in that loop. Supported source names are google_sheets, csv, ga4, stripe, airtable, notion, and http_api. Availability depends on deployment and organization; a recognized type is not proof it is enabled.
Human handoff and safe discovery
Use list_connections before creating one. create_connection returns handoff_url and instructions for the human to authenticate, select the resource, or upload CSV. Never ask for credentials in chat. get_connection_schema returns the current safe configuration, resource_locator, typed columns, bounded samples and readiness. Reuse returned locators and real field names; do not guess IDs. A redacted locator or configuration needs human source selection, not an attempt to reconstruct secrets.
Save, wait, preview
- Configure the validated FetchSpec using resource, transforms, output.table and version. The examples below illustrate each source. Copy its real locator from get_connection_schema; adapt transforms only to observed columns.
- Keep revision_id from configuration_saved. Saving is not a completed refresh. Changed remote specs queue one refresh by default. Identical retries reuse the revision. trigger_sync:false defers remote refresh; trigger_sync:true requests a refresh subject to the existing debounce and plan limits.
- Call get_connection_schema with expected_revision_id. ready:true proves snapshot_revision_id matches that revision. connected:true alone only proves prior readable data. Respect retry_after_seconds; do not loop rapidly. If superseded, read the active configuration and reconcile. Unknown or failed state is not ready; follow the returned recovery.
- Call preview_connection_render with connection_id, expected_revision_id and the HTML with markers. Pending, unknown and superseded revisions cannot preview successfully. Fix any marker warnings.
- Read/export the target Page and publish the complete bundle with update_page, its matching base_version_id, and the same markers. Markers survive in the stored files; the server substitutes values on export/refresh.
CSV is upload-driven. A changed CSV spec waits for a fresh human upload at the returned handoff_url for that Connection. The fresh raw rows are transformed once under the active spec and stamped with its revision. There is no remote CSV credential worker and no retained raw dataset to repeatedly transform.
FetchSpec examples
The following are executable schema examples with fixture IDs. They do not authorize or fetch these resources. Replace connection_id and resource with values returned by discovery. HTTP URLs must be credential-free HTTPS destinations; human handoff stores authentication separately. Version 1 supports select, rename, filter, sort, limit, format and derive. Version 2 additionally supports filter_multi, group_aggregate and window. Transforms run in array order.
google-sheets
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"spreadsheet_fixture","range":"Sheet1!A1:C50"},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
csv
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"44444444-4444-4444-8444-444444444444"},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
ga4
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"123456789","query":{"metrics":"sessions","dimensions":"date","startDate":"28daysAgo","endDate":"today"}},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
stripe
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"charges","query":{"limit":"100"}},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
airtable
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"appFixture","path":"tblFixture"},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
notion
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"55555555-5555-4555-8555-555555555555"},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
http-api
{"tool":"configure_connection_data","args":{"connection_id":"44444444-4444-4444-8444-444444444444","spec":{"version":1,"resource":{"resourceId":"https://example.com/data.json","path":"items"},"transforms":[{"op":"limit","count":20}],"output":{"table":"summary"}}}}
{"tool":"get_connection_schema","args":{"connection_id":"44444444-4444-4444-8444-444444444444","expected_revision_id":"66666666-6666-4666-8666-666666666666"}}
{"tool":"preview_connection_render","args":{"connection_id":"44444444-4444-4444-8444-444444444444","expected_revision_id":"66666666-6666-4666-8666-666666666666","html":"<ul data-sp-repeat=\"summary\"><li><span data-sp-bind=\"name\"></span></li></ul>"}}
Marker vocabulary (frozen for v1)
The renderer is a dumb stencil. It substitutes values; it computes nothing. All logic, such as sort, filter, limit, format, and derive, lives in the fetch spec transforms, never in markers. Every bound value is HTML-escaped (spreadsheet cells are untrusted input).
| Marker | Meaning |
|---|---|
data-sp-bind="table.field" | Replace the element's text content with the field value (escaped). |
data-sp-attr="src:table.image_url,href:table.link" | Set attribute(s) from fields. Values escaped; href/src are validated http(s) only. Comma-separated attr:target pairs. |
data-sp-class="table.row_class" | Append spec-computed class token(s); validated [a-z0-9_-]+. |
data-sp-repeat="table" | The element is a row template. It is cloned once per row. Descendants bind row-relative: bare data-sp-bind="field" resolves against the repeat's table. |
data-sp-empty="table" | Shown only when the table has zero rows. Place it as a sibling of the repeat. |
data-sp-updated | Optional placeholder for the freshness badge. If absent, the renderer injects a default badge unless the page setting disables it. |
Reserved and inert in v1 (write-back fast-follow): data-sp-form,
data-sp-write. Do not use them yet.
Rules that keep the render predictable
- Inside a
data-sp-repeat, use bare field names (data-sp-bind="dish"), not qualified ones. Outside a repeat, always qualify (data-sp-bind="menu.title"). - Nested
data-sp-repeatis unsupported in v1; the inner one is left inert. - A marker that references a table or field the snapshot does not have is left
inert and surfaces a warning in
preview_connection_render, never an error. The render is total and never throws, so a malformed marker degrades quietly rather than breaking the page. - Only safe attributes are settable via
data-sp-attr;href/srcmust resolve to http(s). Unsafe attributes and URLs are dropped with a warning.
Example: a menu repeat
<ul data-sp-repeat="menu">
<li>
<span data-sp-bind="dish"></span>
<span data-sp-bind="price"></span>
<img data-sp-attr="src:photo_url" alt="" />
</li>
</ul>
<p data-sp-empty="menu">The menu is being updated.</p>
Pair this with a fetch spec whose output.table is menu and whose transforms
format price (for example, format to currency) and sort the rows. Run
preview_connection_render and confirm bound_tables includes menu and there
are no unknown_field warnings before you publish.