About
Rebel Food API powers the Rebel scanner and mobile apps. It uses OpenAI Vision to identify foods, classify them on the NOVA scale (1–4), detect barcodes, and generate Maria's short opinion. Scans are persisted in Supabase; barcodes are enriched from Open Food Facts.
Demo
Try the scan flow interactively with Supabase phone login: /demo
Typical flow
POST /analyze— send a photo, get AI resultsPOST /analyze/save— persist image + results (returnsscanId)- Optional follow-up: re-call
/analyzewithfollowUpAnswer, then/analyze/savewithscanId - Optional barcode:
POST /barcodewithscanIdto enrich from Open Food Facts
Authentication
All API endpoints require an app key via X-API-Key (or
legacy Authorization: ApiKey <key> / Authorization: Bearer <key>). Separate keys exist for
web and mobile clients.
For logged-in users, also send the Supabase access token as Authorization: Bearer <jwt>. The API validates the
JWT and derives userId server-side — a userId in the body without a matching token returns 401; a mismatch returns 403.
Anonymous usage works with anonymousId only (no JWT).
To obtain an API key, email vincent.battaglia@gmail.com with your use case.
Endpoints
/analyzeAnalyze a food image with GPT-4o Vision. Returns NOVA category, explanation, optional follow-up question, barcode hint, nutrients, and Maria's opinion.
Content-Type: multipart/form-data
image (File, required)language? (string, en | fr — default: en)followUpAnswer? (string)previousContext? (string, required with followUpAnswer)
novaCategory (1–4 | null)category (string)explanation (string)needsFollowUp (boolean)followUpQuestion? (string | null)barcode? (string | null)nutrients (string[])mariaOpinion? ({ message, emotion })rawResponse (string)
/analyze/saveUpload the image to Supabase Storage and persist the scan. Creates a new row or updates an existing one after a follow-up answer.
Content-Type: multipart/form-data
image (File, required)scanData (JSON string, required)scanId? (string, for follow-up updates)anonymousId? (string, anonymous users)userId? (string, ignored — derived from JWT when logged in)source? (string, web | ios | android — default: web)
success (boolean)scanId (string)
scanData fields: novaCategory, category, explanation, needsFollowUp, followUpQuestion, rawResponse, followUpAnswer?, nutrients, mariaOpinion?
Logged-in users must send Authorization: Bearer <supabase_jwt>.
Daily scan limits apply per user_id or anonymous_id when configured.
/barcodeLook up a barcode in Open Food Facts (cached locally). Updates the scan with product data and may override the final NOVA score.
Content-Type: application/json
barcode (string, required)scanId (string, required)language? (string, en | fr)anonymousId? (string, for anonymous scan ownership)
found (boolean)product ({ barcode, product_name, brands, ingredients_text, nutriments, … })novaChanged (boolean)aiNovaCategory (number | null)finalNovaCategory (number | null)mariaOpinion? ({ message, emotion })
Scans owned by a user require Authorization: Bearer <supabase_jwt>.
/barcode/decodeDecode a barcode from a photo using AI vision.
Content-Type: multipart/form-data
image (File, required)
barcode? (string)error? (string)
/scans/maria-opinionUpdate Maria's opinion on an existing scan (message + emotion).
Content-Type: application/json
scanId (string, required)mariaOpinion ({ message, emotion }, required)anonymousId? (string)
success (boolean)
emotion: pleased | confident | joyful | disappointed | annoyed | bored | surprised
/scoreCompute a daily, weekly, or monthly eating score (0–100) from stored scans via AI.
Content-Type: application/json
userId? (string, derived from JWT when logged in)anonymousId? (string)periodType? (daily | weekly | monthly — default: daily)date? (string, ISO date)language? (string, en | fr)
periodType, periodKey, scansCountscore (number | null)ultraProcessedPoints, nutritionalQualityPoints, proteinsPoints, fibersPoints, varietyPointsultraProcessedPercent? (number | null)summary, details
/score/saveSave or update a computed score for a given period.
Content-Type: application/json
userId? (string, derived from JWT when logged in)anonymousId? (string)periodType (daily | weekly | monthly, required)periodKey (string, required)scansCount? (number)score? (number | null)ultraProcessedPoints?, nutritionalQualityPoints?, proteinsPoints?, fibersPoints?, varietyPoints? (number | null)ultraProcessedPercent? (number | null)summary?, details? (string | null)
success (boolean)