Skip to content

Datasources API

External data injected into Story content at render time. The editor drops a $ds marker into a field; the CDN resolves the marker to live data from an HTTP source (or pushed via webhook) before serving the Story.

Two driver types

DriverDirectionUse case
http_jsonPeacock pulls, on cron schedulePublic APIs, news feeds, Shopify product lists
webhook_pushSource pushes, signed with HMACInventory systems, ERPs that emit events on change

CRUD

http
GET    /v1/spaces/{slug}/datasources
POST   /v1/spaces/{slug}/datasources
PATCH  /v1/spaces/{slug}/datasources/{key}
DELETE /v1/spaces/{slug}/datasources/{key}

POST body for http_json:

json
{
  "key": "current-weather",
  "label": "Vienna weather",
  "type": "http_json",
  "config": {
    "url": "https://wttr.in/Vienna?format=j1",
    "json_path": "$.current_condition[0]",
    "cron": "*/15 * * * *",
    "headers": { "User-Agent": "PeacockCMS/0.1" }
  }
}

POST body for webhook_push:

json
{
  "key": "shopify-products",
  "label": "Shopify product catalog",
  "type": "webhook_push",
  "config": {
    "secret_hash": "auto",
    "allow_unsigned": false,
    "schema": {
      "id": "string",
      "title": "string",
      "price_cents": "integer"
    }
  }
}

The response includes the webhook_secret (only shown once) for configuring HMAC signing on the source side.

Webhook ingest endpoint

http
POST /v1/spaces/{slug}/datasources/{key}/webhook
Content-Type: application/json
X-Peacock-Signature: sha256=<hex hmac>
X-Peacock-Timestamp: <unix-seconds>

Body: a single entry or array of entries:

json
[
  { "id": "sku-1", "title": "T-Shirt", "price_cents": 1990 },
  { "id": "sku-2", "title": "Hoodie",  "price_cents": 5490 }
]

Signature: hex(hmac_sha256(secret, "${ts}.${raw_body}")). Timestamps older than 5 minutes are rejected to prevent replay.

allow_unsigned: true is the dev-only escape hatch — disabled by default; if enabled, signature verification is skipped (use only for local testing).

Manual sync

http
POST /v1/spaces/{slug}/datasources/{key}/sync

Triggers an immediate pull for http_json driver (ignored for webhook_push). Returns the resulting entry count + first-row sample.

Entries

http
GET /v1/spaces/{slug}/datasources/{key}/entries?page=1&per_page=50

The data the CDN sees when resolving $ds. Searchable, paginated, useful for the admin's "what does my data actually look like" panel.

$ds markers in Story content

In any text field, write $ds:current-weather.temp_C (or any JSONPath-like accessor). On CDN read:

  1. The controller collects all markers in the published content.
  2. For each unique key, loads the latest entry / entries.
  3. Substitutes the resolved values inline.
  4. Sets meta.datasource_refs = { "current-weather": 1 } so the frontend can know what was rendered.

If a datasource is offline or returns no data, the marker is replaced with an empty string — the Story doesn't break.

Scheduling (cron)

http_json datasources fire on the cron schedule in their config.cron. A scheduler tick (peacock:sync-due-datasources, registered to run every minute) walks the table, dispatches SyncDatasourceJob for any due rows.

The host-level cron entry is required:

cron
* * * * * docker exec peacock-api php artisan schedule:run >> /var/log/peacock-schedule.log 2>&1

See also