Skip to content
Recipe API

When Food Search Blinks, Keep the Allergen Filter Attached

Fresh 503 alerts, query-parser failures, and an SDK transport gap show why recipe and food-search fallbacks must preserve query meaning, data snapshots, and use-specific safety policy.

searchreliabilityapi-designallergensdeveloper-experience

Twenty-seven alerts are not twenty-seven root causes

Between 02:34 UTC on September 4 and 13:24 UTC on September 5, the Open Food Facts status repository opened 27 machine-generated issues for its Search API v2 monitor. Representative alerts reported HTTP 503: one interval opened at 07:57 and closed at 08:42 on September 5, while another opened at 12:47 and closed at 13:24.

These are monitor-observed intervals—not 27 proven root causes or exact end-user downtime—and their duration is bounded by monitor checks. They still show a dependency alternating repeatedly between available and unavailable states within one product session.

Two same-day reports expose adjacent boundaries. An Open Food Facts Python SDK issue says ProductResource.search() bypasses the shared request helper, and therefore misses configured timeout, User-Agent, environment-specific authentication, and common error handling. Search-a-licious also received fresh reports for a mixed Boolean query and a whitespace-only query reaching parser errors.

The thesis: a recipe or food-search fallback is safe only when it preserves the validated query, constraint status, data snapshot, and intended product use. Retries and cached arrays can improve availability, but without those bindings they can silently turn a narrow food request into a broader—and sometimes unsafe—answer.

Source map and the repeated angle to avoid

Fresh primary evidence from the last seven days:

Source Date Contribution
Open Food Facts Upptime fixed-date incident list and representative 503 alert September 4–5, 2026 Repeated monitor-observed unavailability, explicit status codes, response times, and recovery timestamps
Open Food Facts Python search transport issue September 4, 2026 Shows one SDK operation bypassing common timeout, authentication, identity, and error-handling configuration; open at publication
Search-a-licious Boolean-query error and blank-query error September 4, 2026 Shows that query interpretation can fail before result retrieval; both reports are open at publication

Recent posts here already separate search totals from displayed cards, classify empty responses, describe versioned SDK capabilities, and let optional AI stages degrade independently. Repeating “add retries,” “unknown is not zero,” or “return typed errors” would add little.

The narrower issue is semantic continuity during failover: did the fallback execute the same food constraints against a declared snapshot, or merely return something that looks like recipe results?

Failure has four layers

A client should not compress every unsuccessful search into results: [] or network error.

Layer Example Safe interpretation
Query acceptance Blank text or unsupported Boolean structure No valid search plan exists; normalize or reject before retrieval
Transport Timeout, DNS failure, authentication mismatch The attempt did not establish an application result
Service availability HTTP 503 The service is temporarily unable to answer; a retry may be appropriate
Result eligibility Hits returned but required allergen evidence is unknown Retrieval succeeded, but the product-use policy may reject some or all hits

Recovery depends on the layer. Trim whitespace before submission; require explicit grouping for ambiguous Boolean grammar; retry a 503 within a budget. Never “recover” missing allergen evidence by relaxing the exclusion.

The Python SDK issue demonstrates why operation-level consistency is part of this model. If product lookup uses the shared timeout and error adapter while search calls the HTTP session directly, two methods from the same SDK can disagree about credentials, timeout, telemetry, and exception shape. A caller cannot apply one reliability policy confidently when the transport path is not uniform.

Compile once, then bind every attempt to that plan

Food search often starts as loose input but should become a typed, canonical plan before any backend is selected:

{
  "text": "weeknight noodles",
  "filters": {
    "all": [
      {"field": "allergenIds", "op": "not_contains", "value": "peanut"},
      {"field": "dietIds", "op": "contains", "value": "vegan"},
      {"field": "totalTimeMinutes", "op": "lte", "value": 30}
    ]
  },
  "market": "GB",
  "locale": "en-GB"
}

Validate fields and operators, resolve stable taxonomy IDs, and publish a canonical representation. Then give the plan an opaque ID such as plan_q7n4; every live, replica, or cache attempt must reference it. Do not rebuild a simpler query from display text during failover.

This prevents several tempting mistakes:

  • sending only weeknight noodles to a backup that cannot express exclusions;
  • turning an unsupported allergen filter into a client-side label check;
  • interpreting a whitespace-only query as “show everything” after rejection;
  • using localized ingredient names as fallback identifiers.

If a provider cannot compile the requested semantics for a target, it should mark that target ineligible. “Backup available” is not the same as “equivalent search available.”

Return an attempt ledger, not an unexplained cache hit

A practical response can keep failover observable without exposing internal query syntax:

{
  "requestId": "req_01K4...",
  "planId": "plan_q7n4",
  "queryStatus": "fully_validated",
  "attempts": [
    {
      "target": "primary_search",
      "outcome": "service_unavailable",
      "httpStatus": 503,
      "durationMs": 815
    },
    {
      "target": "result_cache",
      "outcome": "served",
      "eligibility": "equivalent_plan"
    }
  ],
  "result": {
    "source": "cache",
    "snapshot": "recipes-2026-09-05T12:30:00Z",
    "ageSeconds": 1980,
    "constraintStatus": "fully_applied",
    "items": []
  },
  "suppressedClaims": ["current_retail_availability"]
}

The values are illustrative. The contract is the important part:

  • queryStatus proves the constraints were accepted;
  • eligibility binds the cache entry to the canonical plan;
  • snapshot and age disclose what “stale” means;
  • constraintStatus shows whether filters were applied;
  • suppressedClaims blocks current commerce claims on old evidence.

An empty cached result is still a valid result only if it was computed for the same plan and snapshot policy. Otherwise it is an unavailable fallback, not evidence that no recipes exist.

Choose fallback policy by product use

One global stale-if-error duration is too coarse for food products.

Product use Reasonable behavior after a 503 Constraint that must survive
General recipe browsing Serve a labeled cached page or curated collection Preserve locale and clearly disclose changed personalization
Saved meal-plan reopening Serve the saved recipe revisions Do not silently replace removed or changed recipes
Allergen-sensitive search Use cache only when the exclusion was fully applied and evidence still meets policy; otherwise fail closed Allergen entity, assessment state, policy version, and recipe revision
Nutrition-range search Serve a pinned snapshot if calculation and serving bases are compatible Nutrient identity, basis, units, calculation version, and completeness threshold
Grocery availability or price Preserve the recipe but suppress or qualify stale commerce claims Retailer, location, product match, observation time, and currency

Structured recipe data changes reliability engineering. A sodium filter needs the nutrient basis and calculation version that produced it. A peanut exclusion needs evidence that every returned recipe passed the named policy; a “peanut-free” title is not equivalent.

Retry carefully: recovery traffic can become load

HTTP 503 indicates temporary inability to handle a request; older HTTP semantics guidance allows a server to send Retry-After. Clients should respect it when present, add jitter, cap attempts, and share a circuit breaker rather than letting every recipe card retry independently.

Search is usually safe to retry in the HTTP sense, but autocomplete, facet counts, and cards may fan one keystroke into several calls. Synchronized retries can impede recovery.

A shared SDK request path helps enforce:

  • one timeout budget across connection, response, and enrichment work;
  • bounded exponential backoff with jitter;
  • Retry-After support;
  • cancellation when the user changes the query;
  • consistent authentication and User-Agent identification;
  • typed distinction among invalid query, rate limit, timeout, and 503;
  • telemetry that records operation, attempt, plan ID, and fallback outcome without logging sensitive diet profiles.

Do not retry parser rejection. Normalize blank input locally, and return a stable validation problem for unsupported grammar. Retrying the same invalid expression only adds load.

Edge cases to test

Stale response race. A delayed success for the previous query arrives after the user adds an allergen exclusion. Apply results only when the response carries the active plan ID.

Partial backup semantics. A backup supports inclusion but not exclusion. Mark it ineligible for exclusion-bearing plans rather than filtering a broad result set after retrieval.

Cache-key drift. Taxonomy aliases or locale defaults change while old entries remain. Key on canonical IDs plus taxonomy and policy versions, not display strings.

False safety from old evidence. A cached recipe passed an allergen filter before a compound ingredient was edited. Bind eligibility to recipe revision and invalidate dependent assessments.

Release checklist

Before calling a recipe or food-search integration resilient, verify that it can:

  • compile loose input into one typed, canonical query plan;
  • reject blank or unsupported Boolean expressions before retrieval;
  • prove that every fallback applied the same filters and taxonomy IDs;
  • route every SDK operation through common timeout, auth, identity, and error handling;
  • distinguish validation failure, transport failure, 503, zero matches, and ineligible hits;
  • use bounded retries, jitter, cancellation, Retry-After, and circuit breaking;
  • identify result source, data snapshot, cache age, and recipe revision;
  • apply different freshness rules to browsing, nutrition, allergens, and commerce;
  • suppress claims a stale snapshot cannot support;
  • prevent late responses from replacing a newer constrained query;
  • test flapping, cache-key migration, mixed snapshots, and unavailable backups;
  • expose enough attempt metadata to debug failures without leaking personal food constraints.

For developers and technical buyers, uptime is only the first question. After failover, ask whether the same filters, recipe revisions, and nutrition rules were used, and which claims stale evidence cannot support.

A useful fallback does not merely keep cards on screen. It keeps the user's intent attached to those cards. When food search blinks, the ingredient exclusions, allergen policy, nutrition basis, locale, market, and snapshot must survive—or the system should admit that it cannot safely answer.

Sources

Start Building

One consistent schema on every response. Get a free key and ship in minutes.