Per-customer attributes

How embed JWT attributes flow into your dataset SQL.

Every embed token carries an attributes object — free-form JSON your backend sets at mint time. Values referenced from your dataset SQL as {{trend.attributes.<key>}} are resolved from that object at query time and bound as parameters.

Defining attributes

Attributes are a flat JSON object your backend sends when calling POST /api/embed/token:

{
  "externalId": "acme-corp",
  "attributes": {
    "company_id": "acme-corp",
    "region": "us-east",
    "plan": "enterprise"
  }
}

Keys must look like SQL identifiers (letters, digits, underscores; no leading digit). Values can be strings, numbers, booleans, arrays, or nested objects.

Referencing them in dataset SQL

Use the trend.attributes. prefix. trend parameter-binds these — they never get concatenated into your query text:

SELECT date_trunc('day', created_at) AS day,
       sum(amount) AS revenue
FROM orders
WHERE company_id = {{trend.attributes.company_id}}
  AND created_at >= now() - interval '30 days'
GROUP BY 1
ORDER BY 1

If a customer’s token supplies company_id: "acme-corp", the query runs as WHERE company_id = $1 with acme-corp as the bound parameter.

Optional clauses

Wrap conditional filters in [[...]] — the block gets dropped when its variables are missing. Useful for chart-level filters that come from dashboard controls:

SELECT * FROM events
WHERE company_id = {{trend.attributes.company_id}}
  [[ AND event_type = {{event_type}} ]]

Warning

Don’t wrap {{trend.attributes.*}} references in [[...]]. If the token doesn’t supply the attribute, trend hard-rejects the query with a 400 — but if you wrap it, the block silently drops and the WHERE disappears, returning every customer’s rows. The extractor catches wrapped references at the API boundary and still rejects, but writing the SQL correctly (attribute filters outside [[...]], control filters inside) makes the intent obvious.

The safety guarantee

Every embed chart query goes through this check at the API boundary:

  1. Extract every {{trend.attributes.<key>}} reference from the dataset SQL (including references inside [[...]] blocks).
  2. Diff against the keys the token supplied.
  3. If any reference is missing an attribute in the token, return 400 Missing required attributes: <list>.

Then the same three-layer SQL safety as any internal chart query applies: SELECT-only validator, session-level default_transaction_read_only, and the DB-level read-only role you set up for the connection.

Observed customers

Every mint auto-populates a row in Embed → Customers with the external ID and a "last seen" timestamp. No manual registration required — the page is an audit view showing which external IDs you’ve issued tokens for.