An ingredient is one operation a recipe can use. An operation is one thing it can do, like a read or a call. It is written as a small file called a manifest. The manifest says what goes in, what comes out, and how risky the operation is. Recued checks the manifest when you install it or submit it. The one gate every action goes through reads the manifest too. Nothing is guessed while a recipe runs.
#Anatomy
Here is an ingredient that reads one deal from HubSpot:
{
"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"
}
}Each input key maps a target to a value. The value can be a plain value, a
{{ref}}, or null. A null means the caller must give the value. If a
null is never filled in, that is an error at install time. It is not a
surprise at run time. A key with no known prefix (like deal_id) is one of
the ingredient's own parameters. Other values can point to it as
{{deal_id}}.
#Enabled kinds
The kind field is required. It names the adapter that runs the ingredient.
An adapter is the part of Recued that does one kind of work. The Kitchen
editor offers these adapters. Recued checks each one at install:
kind |
Executes | Where it runs |
|---|---|---|
http |
An HTTP or REST call to the outside | Server |
dom |
A page trigger, read, or write on a live website | The Browser Bridge, told what to do by the server |
ai |
A call to an AI model, or an embeddings call | Server |
mcp |
An MCP tool call | Server |
service |
A long-running service that was set up on the server | Server |
storage |
The warehouse, shared records, enrichments, and files | Server |
connection |
A call through a named connection | Server |
Each kind has rules about which input keys it must have and must not have.
An http manifest must declare url. An ai manifest must have at least
one llm.* key, and it may not have url. And so on. A manifest with the
wrong kind fails when you submit it. It never fails at run time. The url
string does a second job. It is a promise the validator holds you to. At
publish time, the validator pulls out its pattern. At run time, the real URL
must match that pattern.
#Categories and risk tiers
The category field says what the operation is. A data operation reads. An
ai operation runs a model. An action operation changes something outside.
The risk_tier field says how Recued guards it:
| Tier | Governance |
|---|---|
read |
Runs quietly once it is granted |
write |
Needs your approval |
admin |
Needs your approval, plus a scope (which things it may touch) |
destructive |
Needs your approval, plus a clear extra yes |
Some operations are big but can be undone. They spend money, or they start a
costly render. Want Recued to ask every single time? Keep the tier at write
or admin, and set the catalog operation's approval to always. The owner
can still say yes for a whole session. Each kind allows only the tiers that
make sense. An ai manifest can only be read. Running a model cannot
destroy anything by itself. A destructive tool call belongs to mcp or
connection. A dom manifest stops at write. A tier that does not fit its
kind is rejected when you submit.
#Connection wrappers
A kind: 'connection' ingredient calls through a named connection. It never
touches the secret key. The connection executor adds the key at call time.
The key is never written into the manifest:
connection_kind |
Required input | Shape |
|---|---|---|
api |
connection, method, path |
plus query.*, header.* (never auth), body.*, body_raw |
mcp |
connection, tool |
plus args |
notification |
connection, text |
plus title, link_url, and a recipient whose shape depends on the subtype |
The connection field always holds a {{config.<name>}} picker. At install,
the dialog asks the user to pick a connection that fits. A picker left empty
fails with a named error. It never guesses. A connection wrapper can also
carry vault_hints. That doubles as the set-up form for a new connection, shown
when no fitting connection exists yet. Every wrapper declares permission and risk_tier.
The gate runs before the adapter. The adapter only moves data in and out.
#Output mapping
HTTP and connection ingredients map neat field names to paths in the
response. So the recipe sees step.<id>.deal_name, not the raw reply. An
entry can also be ["parse_number", "response.properties.amount"]. That
applies a transform while the value is pulled out.
DOM ingredients flip the mapping. The key is a CSS selector (a pattern
that picks one part of the page) or a URL pattern. The value is the field name. The special value trigger marks the
URL pattern the page must match. A DOM ingredient in the data category
reads from the page. One in the action category writes input values into
the page.
#AI-as-function ingredients
The contracted ai-* functions have no platform at the end of their name.
AI does not belong to one platform. Their inputs, outputs, batch mode, and
model hints are in the recipe schema. Pick in this order.
First, try a transform. It always gives the same answer, and it is free.
Second, try a contracted AI function. Third, use ai-prompt. That last one
is the escape hatch with no fixed shape.
An ai manifest whose output declares vector is an embeddings ingredient.
It goes only to the dedicated Embeddings slot. It never goes to the free pool
or to the fast and quality BYOK slots (your own paid keys). Use that shape
only for work that truly needs vectors (lists of numbers that stand for
meaning). It is not another road for normal
AI.
#Vault scoping
A vault reference in a manifest belongs to its publisher. For example,
{{vault.hubspot.token}} is looked up under the ingredient author's own name
space. One publisher can never read another publisher's vault. Recued blocks
that at run time. A recipe never uses {{vault.*}} at all. Only an
ingredient does. That is why a fork, or two recipes with the same name,
cannot leak each other's secrets.
#Naming and metadata
A slug follows {action}-{entity}-{platform}. The platform always goes last
(deal-reader-hubspot, search-exa). The fields name, description,
tags, use_cases, and entity_types are for the marketplace to show. The
engine ignores them. The execution_scope field is optional. It narrows
where the ingredient may run. It must stay inside what its kind already
allows. A dom ingredient cannot claim it can run on the server.
#Related
- Recipe schema — the steps that use ingredients.
- Connections — the saved connections that wrappers use.
- Authoring ingredient packs — how to build, check, preview, and install these operations in the Kitchen.
- Publishing — how to publish the pack that holds your ingredient.