New: quote and order photos, taste reports, shipped samples and ingredient comparison trials
Customer ordering: use recipe → fixed quote → checkout → Ordered - Pending Lab. Supported orders need no second lab confirmation. Test payments capture no funds. The private /lab-orders worksheet and research-intake protocols below are separate from customer checkout.
Request a recipe.
Describe a beverage or flavor. Let the AI choose from the ingredient bank, or give it a smaller set to work with. You receive complete proposed recipes with quantities including water and preparation steps, or a concise explanation of what cannot be fulfilled.
Submit exact quantities and instructions · Download example client · Agent instructions · Full API reference
1. Choose the ingredients
Search GET /api/v2/ingredients?q=lime, or omit q to read the bank. Use exact IDs from the response. GET /ingredients/{ingredientId} describes the basic composition, carrier, allergen and compatibility information available for a name such as Strawberry. Recipe amounts refer to that source product, including its carrier.
{
"prompt": "A crisp, still strawberry-lime drink with a dry finish",
"batchMassG": 500,
"ingredientSelection": {
"availableIds": ["flavor.strawberry", "flavor.lime", "acid.citric", "sweet.sucrose"],
"requiredIds": ["flavor.strawberry", "flavor.lime"]
}
}
Save this as request.json. You supply ingredients; the AI chooses the quantities. Omit ingredientSelection to use the entire ingredient bank. A minimal request is {"prompt":"A crisp strawberry-lime drink"}.
For named directions, add variants to the same request. All directions share its ingredient selection, batch mass and format constraints:
"variants": [
{"id":"bright","prompt":"Emphasize bright citrus aroma"},
{"id":"soft","prompt":"A softer citrus aroma"}
]
| Field | Meaning |
|---|---|
| prompt | Required, 3–1,500 characters. Describe taste, format and any constraints. |
| batchMassG | 100–2,000 grams; default 500. This is mass, not milliliters. |
| ingredientSelection.availableIds | If selecting ingredients: 1–20 unique known IDs. A hard allowlist; the AI may use a subset. |
| ingredientSelection.requiredIds | 0–8 unique IDs, all also available. Every required ID must have a positive amount in every returned recipe. If it cannot be used, the affected variant has a gap and no recipe. Default empty. |
| variants | Optional array of up to six {id,prompt} directions with unique IDs. The main prompt, batch mass and ingredient constraints apply to every variant. Omit for one recipe assessment. Some variants may have no recipe. |
| constraints | Optional {noAddedSweetener, excludedIngredientIds}. These are hard restrictions, retained by alternative requests. |
| kind | Default beverage. Flavor means an unsweetened, still water-based evaluation, not a production concentrate. |
| includeJuices | Default true for beverages. Set false to prohibit juice. Flavor mode always excludes juice. |
Water is calculated automatically. Request carbonation in the prompt; do not list water or CO₂. Unknown IDs are rejected. Known ingredients awaiting sourcing or dose qualification can be selected, but an unqualified required ingredient prevents a recipe for the affected variant. Its limitation appears in gaps; no quantities or development plan are invented. Selecting juice with juices disabled, or sweetener in flavor mode, is also rejected. Selection records permitted candidates, not confirmed physical inventory.
2. Send the request
Create a lab API key in API keys with ingredients:read, ideas:read and ideas:write. This API accepts your Deconfound API key. See connection details. Keep the key in your server environment.
# Node.js 22 or later. Set these through your environment/secret manager: # DECONFOUND_API_KEY node request-recipe.mjs request.json recipe-job.json
The downloadable client saves request keys and the idea ID in recipe-job.json; it does not store credentials. Keep that file to recover the same request. Use a different filename for a new idea. Checkpoints also bind the API deployment; preserve old Sites checkpoints for that deployment and use a new filename for a new native request.
The client performs this sequence:
POST /api/v2/ideas
Content-Type: application/json
Idempotency-Key: <unique create key>
X-Deconfound-Api-Key: <lab API key>
<contents of request.json>
→ 201 {"idea":{"id":"…","revision":1,"status":"created",…}}
POST /api/v2/ideas/<returned ID>/generate
Content-Type: application/json
Idempotency-Key: <different generation key>
<same X-Deconfound-Api-Key header>
{"revision":1}
→ 200 {"idea":{"status":"succeeded","revision":3,"recipes":[{…}],"recipe":{…},"reason":null,"gaps":[],…}}
GET /api/v2/ideas/<returned ID>
<same X-Deconfound-Api-Key header>
Use the revision returned by the server; the numbers above illustrate a first attempt. Generation runs two actual AI passes: develop an approach, then challenge, improve and finalize. The server validates the result against the selected ingredients and mass balance. The generate call runs in the foreground; use a 300-second client timeout. Another request can read the idea’s stage while it runs.
3. Read the recipes and gaps
idea.recipes is the canonical list of complete candidates. It may contain one recipe, several requested variants, a subset of them, or no recipes. Read idea.reason and idea.gaps separately for limitations. reason is null when any complete recipe is returned. Each gap contains variantId (or null), a machine-readable code and a concise message, with an optional capability. Preserve an explicit null reason instead of replacing it with a legacy summary. Each candidate identifies its requested direction with variantId, or null for an unlabelled single request. requestedCount and returnedCount show requested directions and complete returned recipes. The deprecated idea.recipe alias contains the first returned recipe or null; it is insufficient for a request with multiple variants.
This shortened example shows one returned candidate; its quantities are illustrative, not a validated formula.
{
"recipes": [{
"variantId": null,
"name": "Strawberry lime",
"batchMassG": 500,
"status": "untested",
"requiresAcceptance": false,
"carbonated": false,
"approach": "Balance strawberry and lime aroma with a light tart finish.",
"ingredients": [
{"ingredientId":"flavor.strawberry","name":"Strawberry","sourceMassG":0.5,"stockFraction":1,"dispenseMassG":0.5},
{"ingredientId":"flavor.lime","name":"Lime","sourceMassG":0.2,"stockFraction":1,"dispenseMassG":0.2},
{"ingredientId":"acid.citric","name":"Citric acid","sourceMassG":0.3,"stockFraction":1,"dispenseMassG":0.3},
{"ingredientId":"base.water","name":"Water","sourceMassG":499,"stockFraction":1,"dispenseMassG":499}
],
"processSteps": ["Weigh the source products, dissolve the acid, then blend with the calculated water."]
}],
"reason": null,
"gaps": [],
"requestedCount": 1,
"returnedCount": 1
}
All masses are grams. New API recipes use stockFraction:1, so source and dispensed masses match. Each recipe independently balances water plus dispensed ingredients to its requested batch mass. Carbonation is a separate process, outside this liquid mass balance. Steps are AI proposals; a lab order supplies the reviewed bench worksheet after equipment and methods are configured.
| Result | What to do |
|---|---|
| One or more complete recipes | Review each candidate. A complete alternative has requiresAcceptance:true; obtain requester acceptance of its stated compromise before ordering. |
| A subset of requested variants | Use only returned recipes. Read the per-variant gaps for omitted directions. Do not treat a gap as a recipe or fill it with unqualified ingredients. |
| No complete recipes | recipes:[], deprecated recipe:null, a concise reason, and separate gaps. No development materials, process plans, benchmarks or held order are proposed. |
All these assessments can have idea.status:"succeeded": the AI completed its assessment. A provider or validation failure instead has status:"failed" and error. Fulfillment labels in the full result remain useful context, but do not imply a recipe exists; read the canonical array. No result implies the drink was made, tested, lab-qualified or ready for others to consume.
4. Send it to the lab, when ready
POST /api/v2/lab-orders
<same authentication headers, application/json, and a new Idempotency-Key>
{
"source": "idea",
"ideaId": "<returned idea ID>",
"ideaRevision": 3,
"replicates": 1
}
Use the latest revision. Requires orders:write. For an explicitly accepted alternative, add "acceptAlternative":true. For a named variant, add its exact variantId to the order body. Omitting it selects the first complete recipe for legacy compatibility; explicitly select the intended direction when several recipes are returned. Order only a complete returned candidate; empty results and gaps cannot be ordered. Ordering by idea preserves the saved formula. To supply your own exact quantities and method, use a direct ingredient order; it accepts explicit water when all quantities total the batch mass.
The order freezes exact source products, quantities and a guided worksheet in the Lab workspace. It does not buy ingredients or dispatch a physical job. Default tests record actual batch mass without inventing pass targets. See assembly and results for equipment configuration, review, steps and measured evidence.
Errors and recovery
After a timeout or lost response, keep the original body, keys and checkpoint. GET the idea if you have its ID, or replay the create request with its original key to recover it. A running generation replay returns 202; wait for Retry-After and read the idea again. Generation is not a background queue: an interrupted worker may eventually return GENERATION_INTERRUPTED.
To retry a terminal failed generation, read the current idea revision and POST /ideas/{id}/generate using that revision and a new generation key. The example client stops on terminal failure so you can inspect it; it does not retry paid generation automatically. For a changed idea or selection, create a new idea with a new create key and checkpoint.
| Error | Action |
|---|---|
| 422 VALIDATION_ERROR | Fix field types, limits, duplicates or required IDs missing from the allowlist. |
| 422 UNKNOWN_INGREDIENT / AUTOMATIC_INGREDIENT / INGREDIENT_REQUIRES_REVIEW / INGREDIENT_NOT_SOURCED / INGREDIENT_SELECTION_CONFLICT | Correct the selection using the ingredient bank. No generation has started. |
| 409 CATALOG_CHANGED | The bank changed since this selected-ingredient idea was created. Create a new idea from the current bank. |
| 409 REVISION_CONFLICT / IDEMPOTENCY_CONFLICT | Read the current resource, or use a new key for a genuinely different request. |
| 422 AI_INVALID_RESULT | The proposal failed server validation even after repair. No executable recipe was saved. Inspect or revise the request. |
| 429 / 502 / 503 / 504 | Respect Retry-After; recover saved state before attempting another generation. |
Selection arrays normalize as sets. Omitted selection preserves whole-bank behavior. Catalog version is assigned by the server; clients cannot overwrite it. Existing saved ideas remain readable after catalog changes. Historical command replays may predate recipes, reason or gaps; GET the idea for the current view. When reading an older server, a non-null recipe can be wrapped in an array only if recipes is absent. A present empty array takes precedence over any stale singular alias. See the full reference for authentication errors, quotas and the common error envelope.
Ingredient bank and scope
We formulate nonalcoholic drinks and water-based flavor evaluations. Cookie-inspired soda and banana-bread lemonade are in scope. Actual cookies, solid food, fermentation, distillation and new molecule synthesis are outside scope and return an unsupported result without a recipe.
The Library shows the complete ingredient catalog. Bread, sweet cream and butter profiles describe aroma, not bread solids, dairy or texture. Nothing is marked purchased or qualified by being selected.
GET /api/v2/ingredients?bank=launch returns the shortlist plus launchBank and scope. Use bank=extended or bank=review, or omit the filter to search all ingredients. Native MCP also provides search_ingredients and get_ingredient with the same scoped key. See MCP connection examples. Each profile exposes its ingredient ID, name, short description and draftEligible status. The detail endpoint/tool includes available composition, carrier, allergen, compatibility and appearance information.
Omit ingredientSelection to let AI consider the full catalog, preferring launch candidates when suitable. Results identify these in result.brief.studio.ai.extendedIngredientIds. An explicit allowlist remains binding. Beverage requests default includeJuices to true; set false to prohibit juice. Flavor evaluation mode always excludes juice.
Real juice clarification, missing dairy/oat/cocoa matrices and unqualified dosing can prevent a complete recipe. The affected variant then has no recipe; its concise reason and gaps explain the limitation without proposing development materials or plans. Unknown allergen or carrier suitability cannot be inferred from a flavor name. Tests require actual preparation and recorded readings.
When the original cannot be made
Every unsupported result includes an alternatives array. Show the proposal’s title, description and changes alongside the original reason. It is a new direction, not an executable recipe or a claim that the original request can be fulfilled.
When action is generate, let the customer choose it, then submit the complete alternative.request to POST /api/v2/ideas with a new idempotency key and generate that idea. Keep its structured constraints, ingredient selection, required IDs, batch mass and juice setting unchanged. The newly validated recipe returns its own quote. An alternative never places an order.
When action is edit, request is null: explain the needed change and let the customer revise their ingredient restrictions. Do not silently remove exclusions or required ingredients. Partial variant results provide an alternative for each unavailable variant.