From an idea
to a real sample.
Formulate a flavor, quote a trial, and retrieve the lab’s observations. One API for your application or agent.
https://labs.deconfound.com/api/v2Read GET /lab-status for the current intake and payment mode.
Connect once. Build your flow.
Send your key in X-Deconfound-Api-Key. Use the setup below, then run the numbered examples in order. JavaScript examples share the api() helper.
export API_BASE="https://labs.deconfound.com/api/v2"
export DECONFOUND_API_KEY="YOUR_API_KEY"
# Create once. Save these values for retries of each operation.
export CREATE_KEY="$(uuidgen)"
export GENERATE_KEY="$(uuidgen)"
export ORDER_KEY="$(uuidgen)"
export PAYMENT_KEY="$(uuidgen)"Authentication An Authorization / Bearer header is not accepted. ChatGPT and Sites tokens do not authenticate this API.
Retry keys Persist each UUID and its request body before sending. Reuse both for a retry; generate a new key only for a new operation.
Find your ingredients
/ingredientsSearch the flavor library for ingredient IDs and formulation profiles. Use the returned catalog version when submitting an exact recipe.
curl --fail-with-body "$API_BASE/ingredients?q=lime&limit=5" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY"{
"catalogVersion": "<current catalog version>",
"ingredients": [
{
"id": "flavor.lime",
"name": "Lime"
}
]
}The customer catalog does not expose supplier names, purchasing details or internal costs.
Describe an idea
/ideasSave a brief for a beverage or water-based flavor. This creates an idea; generation is a separate request.
curl --fail-with-body "$API_BASE/ideas" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $CREATE_KEY" \
--data '{
"prompt": "A crisp still lime drink with a dry finish.",
"kind": "beverage",
"batchMassG": 500,
"includeJuices": false
}'{
"idea": {
"id": "<idea ID>",
"status": "created",
"revision": 1,
"recipes": []
}
}Save idea.id as IDEA_ID and idea.revision as IDEA_REVISION. Prompts accept 3–1,500 characters; the standard orderable batch is 100–500 g.
Generate a recipe
/ideas/{id}/generateTwo AI passes develop the approach and formulate against the library. The request can run for up to 240 seconds; allow a 300-second client timeout.
curl --fail-with-body --max-time 300 \
"$API_BASE/ideas/$IDEA_ID/generate" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $GENERATE_KEY" \
--data "$(printf '{"revision":%s}' "$IDEA_REVISION")"{
"idea": {
"id": "<idea ID>",
"status": "succeeded",
"revision": 3,
"recipes": [
{
"name": "Crisp Still Lime",
"batchMassG": 500,
"status": "untested",
"requiresAcceptance": false,
"variantId": null,
"ingredients": [
{
"ingredientId": "flavor.lime",
"sourceMassG": 0.5
},
{
"ingredientId": "acid.citric",
"sourceMassG": 0.6
},
{
"ingredientId": "sweet.sucrose",
"sourceMassG": 30
},
{
"ingredientId": "base.water",
"sourceMassG": 468.9
}
],
"services": {
"quote": {
"currency": "USD",
"totalCents": 6900
}
}
}
],
"reason": null,
"gaps": []
}
}Use the returned revision for quoting. A succeeded idea can still have an empty recipes array: inspect reason and gaps. Before quoting an alternative, obtain the customer’s acceptance and set recipe.acceptAlternative to true.
Generation develops an untested recipe. Only recorded kitchen work produces test results.
Get the customer’s price
/order-quotesRecipe trials have recipe-specific prices. Quote the exact recipe and deliverables: ingredients, quantities, preparation and selected versions determine the price. The scope hash binds the order to this quote.
# Use the latest revision returned by generation.
SELECTION=$(cat <<JSON
{
"recipe": {
"source": "idea",
"ideaId": "$IDEA_ID",
"ideaRevision": $IDEA_REVISION,
"replicates": 1
},
"deliverables": ["photo", "taste"],
"variationIds": []
}
JSON
)
curl --fail-with-body "$API_BASE/order-quotes" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY" \
-H "Content-Type: application/json" \
--data "$SELECTION"{
"quote": {
"kind": "service_quote",
"status": "provisional",
"currency": "USD",
"totalCents": 6900,
"scopeHash": "<64-character quote hash>",
"versions": 1,
"preparations": 1,
"batchMassG": 500,
"deliverables": [
"photo",
"taste"
],
"binding": false
}
}Save the selection unchanged and quote.scopeHash as QUOTE_HASH. Show the price and scope to your customer before submitting an order. Always use the returned price. Only a complete supported quote can be ordered. A null total needs a supported alternative or corrected selection, never a review order.
Have an exact recipe?
Skip idea generation. Replace selection.recipe with this object, using the current catalog version. Quantities are grams of the source ingredient; these sum to 500 g, including 468.9 g water. Custom preparation is also supported; see the exact-recipe schema in OpenAPI.
{
"source": "ingredients",
"name": "Still lime drink",
"catalogVersion": "<current catalog version>",
"batchMassG": 500,
"carbonated": false,
"ingredients": [
{
"ingredientId": "flavor.lime",
"sourceMassG": 0.5
},
{
"ingredientId": "acid.citric",
"sourceMassG": 0.6
},
{
"ingredientId": "sweet.sucrose",
"sourceMassG": 30
},
{
"ingredientId": "base.water",
"sourceMassG": 468.9
}
]
}Add research sample shipping
Keep the same recipe and merge these fields into your quote selection. The API estimates overnight cool-packed shipping for eligible contiguous-US destinations. It is a planning estimate, not a carrier booking. Shipped samples are for research only and are not for human or animal consumption. Include the recipient, street address and city in shippingAddress when ordering.
{
"deliverables": [
"photo",
"taste",
"ship"
],
"variationIds": [],
"destination": {
"country": "US",
"region": "CA",
"postalCode": "94133"
}
}Compare recipe versions
Inspect trialProposal for available variations before selecting reduced_20 and reduced_40 in variationIds. The original and selected versions are prepared separately, with a record for each. The quoted price and preparation count update with your selection.
Start checkout
/ordersShow the exact recipe, quoted total and trial terms from /order-api.md. After the customer agrees, submit the same selection and quote hash with checkout.acceptQuote:true. Keep the returned order ID.
# Run only after customer approval. Set CONTACT_EMAIL first.
curl --fail-with-body "$API_BASE/orders" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $ORDER_KEY" \
--data "$(cat <<JSON
{
"selection": $SELECTION,
"quoteHash": "$QUOTE_HASH",
"shareWithTeam": true,
"contactEmail": "$CONTACT_EMAIL",
"checkout": {"acceptQuote": true}
}
JSON
)"{
"order": {
"id": "<order ID>",
"kind": "order",
"orderType": "service_order",
"status": "new",
"orderStatus": "awaiting_checkout",
"revision": 1,
"payment": {
"id": "<payment ID>",
"provider": "stripe",
"mode": "test",
"status": "pending",
"amountCents": 6900,
"currency": "USD",
"fundsCaptured": false
}
}
}contactEmail is required for API-key requests. This saves the fixed quote and starts checkout without a lab-confirmation step. It does not start kitchen work, schedule shipping or capture a payment.
The example uses Stripe sandbox. If /lab-status instead returns test, use the internal simulator: POST /orders/{id}/test-payment records a checkout outcome with {"outcome":"succeeded"} and its own idempotency key. No funds are captured, and success puts the order in Ordered - Pending Lab.
{"outcome":"succeeded"}Open secure checkout
/orders/{id}/stripe-checkoutWhen /lab-status reports stripe_test, create a hosted Stripe sandbox checkout for the accepted order. Send the returned URL to the customer to complete checkout. No real money is charged.
curl --fail-with-body "$API_BASE/orders/$ORDER_ID/stripe-checkout" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $PAYMENT_KEY" \
--data "$(printf '{"paymentId":"%s"}' "$PAYMENT_ID")"{
"orderId": "<order ID>",
"revision": 2,
"orderStatus": "awaiting_checkout",
"payment": {
"id": "<payment ID>",
"provider": "stripe",
"mode": "test",
"status": "pending",
"amountCents": 6900,
"currency": "USD",
"fundsCaptured": false
},
"checkout": {
"url": "https://checkout.stripe.com/c/pay/cs_test_example",
"sessionId": "cs_test_example",
"expiresAt": "<expiry timestamp>"
}
}Save order.payment.id as PAYMENT_ID. The server uses the saved total; never send your own amount. After checkout, read GET /orders/{id} until the verified payment is reflected. Display ordered_pending_lab as Ordered - Pending Lab. Returning from Stripe does not prove payment. Keep the same body/key after a lost response.
Retrieve the lab report
/orders/{id}/resultsRead the recorded batches, ingredient amounts, preparation, photos and observations as the lab adds them. Results begin empty and include reasons for omitted assessments.
# Save the returned order ID as ORDER_ID.
curl --fail-with-body "$API_BASE/orders/$ORDER_ID/results" \
-H "X-Deconfound-Api-Key: $DECONFOUND_API_KEY"{
"orderId": "<order ID>",
"fulfillmentStatus": "pending",
"output": null,
"results": {
"status": "not_started",
"expectedBatches": 1,
"recordedBatches": 0,
"batches": [
{
"recipeId": "original",
"label": "Original",
"replicate": 1,
"targetMassG": 500,
"record": null
}
],
"history": []
}
}Read output first for the published sample descriptions, photos and analysis; output.completedAt marks completion. Advanced results can still be not_started. Reported means records were submitted, not that every test passed.
Handle the next step.
Errors include a code, message, retryability flag and request ID. Preserve that ID when reporting a problem; use the response to decide whether to retry or ask the customer to act.
401 / 403Check authentication and scope
Use an active key with the required permissions. Keys can only access their owner’s resources.
409Refresh state or resolve a conflict
QUOTE_CHANGED: obtain and show a fresh quote. IDEMPOTENCY_CONFLICT: the key was reused with a different body. LAB_PAUSED: wait for intake to reopen.
422Correct the request
Check validation details, supported ingredients and methods. Set acceptAlternative: true only after the customer accepts the proposed compromise, before quoting or ordering it.
429 / 5xxRetry deliberately
Honor Retry-After and error.retryable. If the result of a write is uncertain, retry the unchanged body with its original key; do not create a second order.
A generation request timed out?
Read GET /ideas/{id} first. While it is running, wait and check again. Retrying the same generation with the same key and body can return 202. After a confirmed failure, read the latest revision before starting a new attempt with a new key.
API keys
Give your application or agent its own key. Keep it on the server; never ship it in a public browser bundle.
One key. Your recipes and orders.
Sign in to create and manage keys. The documentation is open to everyone.
Bring your agent.
Agents can use the same REST endpoints, scoped API keys and explicit quote approval flow.
Use /mcp with the X-Deconfound-Api-Key header. Copy connection and tool examples.
