An ingredient is a manifest declaring one operation. The manifest is what the validator checks at install or submission time and what the enforcement boundary reasons about — nothing routes on runtime guesswork.
#Anatomy
{
"slug": "deal-reader-hubspot",
"author": "recued-core",
"kind": "http",
"category": "data",
"risk_tier": "read",
"input": {
"method": "GET",
"url": "{{vault.hubspot.base_url}}/crm/v3/objects/deals/{{deal_id}}",
"header.authorization": "Bearer {{vault.hubspot.token}}",
"deal_id": null,
"query.properties": "dealname,amount,dealstage,closedate"
},
"output": {
"deal_id": "response.id",
"deal_name": "response.properties.dealname",
"amount": "response.properties.amount"
}
}Input keys map targets to values: a literal, a {{ref}}, or null — null
means the caller must supply it, and an unfilled null is an install-time
error, not a runtime surprise. Keys without a recognized prefix (like
deal_id) are the ingredient's own parameters, referenced from other values
as {{deal_id}}.
#Enabled kinds
kind is required and names the adapter that executes the ingredient. The
enabled authoring surface exposes these adapters, each validated at install:
kind |
Executes | Where it runs |
|---|---|---|
http |
Outbound HTTP / REST call | Server |
dom |
Page trigger, read, or write on a live site | Browser Bridge, server-directed |
ai |
Programmatic generative-model or embeddings call | Server |
mcp |
MCP tool call | Server |
service |
Enrolled long-running service | Server |
storage |
Warehouse, shared records, enrichments, files | Server |
connection |
Call through a named connection record | Server |
Each kind carries required and forbidden input-key rules — an http manifest
must declare url; an ai manifest must carry at least one llm.* key and
may not carry url; and so on. Misclassified manifests fail at submission,
never at runtime. The url string doubles as the attestation surface: the
validator extracts its pattern at publish time and the resolved runtime URL
must match it.
#Categories and risk tiers
category says what the operation is: data (reads), ai (inference),
action (modifies external state). risk_tier says how it is governed:
| Tier | Governance |
|---|---|
read |
Executes silently when granted |
write |
Approval required |
admin |
Approval plus scope |
destructive |
Approval plus an explicit confirmation |
To make a consequential-but-reversible op (spends money, an expensive render)
ask every time, set a catalog operation's approval to always on a
write/admin tier — the owner can still grant it for a session. Kinds admit
only sensible tiers — an ai manifest can only be read (inference cannot
itself be destructive; the destructive tool call belongs to mcp or
connection), dom stops at write, and the mismatch is rejected at
submission.
#Connection wrappers
A kind: 'connection' ingredient calls through a named connection record and
never touches auth — credential material is injected by the connection
executor at call time, not interpolated in the manifest:
connection_kind |
Required input | Shape |
|---|---|---|
api |
connection, method, path |
plus query.*, header.* (auth-free), body.*, body_raw |
mcp |
connection, tool |
plus args |
notification |
connection, text |
plus title, link_url, subtype-shaped recipient |
The connection field always carries a {{config.<name>}} picker; the
install dialog prompts the user to bind a compatible connection, and an
unbound picker fails with a named error rather than guessing. vault_hints
on a connection wrapper doubles as the enrollment form shown when no
compatible connection exists yet. Every wrapper declares permission and
risk_tier; the gate runs before the adapter, which is a pure IO layer.
#Output mapping
HTTP and connection ingredients map canonical field names to response
paths — the recipe sees step.<id>.deal_name, not a raw payload. An entry
may be ["parse_number", "response.properties.amount"] to apply a transform
during extraction.
DOM ingredients invert the mapping: the key is a CSS selector (or URL
pattern), the value is the field name, and the reserved value trigger
marks the URL pattern the page must match. data-category DOM ingredients
read from the page; action-category ones write input values into it.
#AI-as-function ingredients
The contracted ai-* functions carry no platform suffix — AI is
platform-agnostic — and their signatures, batch mode, and model hints are in
the recipe schema. Author preference order: transform
(deterministic, free) → contracted AI function → ai-prompt (uncontracted
escape hatch).
An ai manifest whose output declares vector is an embeddings ingredient.
It routes only to the dedicated Embeddings slot, never to the free pool or the
fast and quality BYOK slots. Use that shape only for work that explicitly
needs vectors; it is not another route for ordinary generative AI.
#Vault scoping
Vault references in a manifest are publisher-scoped: {{vault.hubspot.token}}
resolves under the ingredient author's namespace, and cross-publisher access
is blocked at runtime. Recipes never reference {{vault.*}} at all — only
ingredients do, which is why forking and same-named recipes cannot leak each
other's secrets.
#Naming and metadata
Slugs follow {action}-{entity}-{platform} with platform always last
(deal-reader-hubspot, search-exa). name, description, tags,
use_cases, and entity_types are marketplace display metadata the engine
ignores. Optional execution_scope narrows where the ingredient may run and
must be a subset of what its kind implies — a dom ingredient cannot claim
to be server-runnable.
#Related
- Recipe schema — the steps that call ingredients.
- Connections — the records connection wrappers bind to.
- Authoring ingredient packs — the Kitchen workflow that validates, previews, and installs these operations.
- Publishing — publishing the pack that carries a custom ingredient.