{"openapi":"3.1.0","info":{"title":"LocalAI Recipes API","version":"1.0.0","summary":"Agent-first API for AI inference recipes: submit, read, and reconcile launch configurations.","description":"All errors are RFC 9457 application/problem+json. Validation errors carry errors[] with RFC 6901 JSON Pointers. Send X-Agent for provenance. Send an identifying User-Agent header: stock anonymous client UAs (Python-urllib, python-requests, Go-http-client, bare Mozilla/5.0, Scrapy) are 406-filtered by the host edge before this application runs. Storage is pointer-only: weights/containers are referenced by immutable pins, never uploaded.","license":{"name":"CC-BY-4.0","identifier":"CC-BY-4.0"}},"servers":[{"url":"https://localairecipes.com"}],"paths":{"/api/v1/recipes":{"post":{"operationId":"submitRecipe","summary":"Submit a new recipe (409 if the slug already exists \u2014 PATCH it instead)","description":"AGENT INGEST NOTES: authentication is a bearer key issued via POST /api/v1/keys (magic-link email) or the /keys page. Submissions default to tier=community. TIER RULES: an ADMIN KEY \u2014 a key carrying the admin scope OR a site-owner person \u2014 may set tier to curated or community exactly as given (omitted = community); a non-admin key requesting tier=curated is rejected 403 Forbidden and nothing is created; non-admin keys omitting tier or sending community are unchanged. The tier decision is recorded in the recipe.created event detail. This admin predicate (admin scope or site owner) also gates PATCH tier changes, DELETE (retire) and model-family promotion. model.family MUST be an existing family slug (see GET /api/v1/facets.json models inventory) \u2014 an unknown slug 422s and nothing is created. Brand-new model releases are proposed through the /submit form other-flow, which creates a PENDING family that stays invisible to facets/pickers until an operator promotes it. Idempotent retries via idempotency_key in the body or the Idempotency-Key header (same key + same request within TTL replays the original 201 response with replay: true and creates no duplicate; a reused key with a different body 409s). Validation errors are RFC 9457 application/problem+json with errors[] entries {pointer, detail, code} listing every failing field via JSON Pointer. Per-key rate limit 500 writes/day (429 on breach). Structured form fields map 1:1 \u2014 see x-form-mapping; engine.pip_package (pip spec), model.hf_link (full HF URL; hf_repo stays the stored <org>/<name> field), engine name enum now includes ik_llama.cpp, mirantis, exllamav3, atlas and dsx.","x-form-mapping":{"model_family":"model.family","model_name":"model.name (standard-name grammar)","hf_link":"model.hf_link -> model.hf_repo=<org>/<name>","quant":"model.quant","engine":"engine.name","engine_version":"engine.version (free text)","engine_docker":"engine.image_digest (docker.io ref parsed from URL)","engine_git":"engine.git_url","engine_pip":"engine.pip_package","launch_cmd":"config.launch (shell-split argv)","storage_ram_gb":"storage_requirements.ram_gb (number>0)","layouts":"layouts [tp|pp] (1..2 unique)","max_context_len":"max_context_len (integer tokens, optional)","kv_pool_tokens":"kv_pool_tokens (integer tokens, optional \u2014 TOTAL KV-cache across concurrent requests)","hw_family+hw_count":"hardware[] {vendor,card,device_count} resolved against hardware_profiles","pf_tok_s":"performance.tok_s","pf_ctx":"performance.context_len","pf_conc":"performance.points[].concurrency","pf_notes":"performance.notes","slug":"slug (server-derived from title on create \u2014 client value ignored)","title":"title","summary":"summary","tags":"tags[]"},"security":[{"bearerAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Same as body idempotency_key: same key + same request within TTL replays the original 201 with replay: true; a reused key with a different body 409s.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeSubmission"}}}},"responses":{"201":{"$ref":"#/components/responses/RecipeCreated"},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"409":{"$ref":"#/components/responses/Problem"},"413":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/recipes/{slug}":{"patch":{"operationId":"updateRecipe","summary":"Update a live recipe (commits a new revision)","description":"Patch the LIVE revision of an existing recipe. Every accepted patch commits a new git revision and flips the live pointer \u2014 nothing is edited in place. Authorization: you *** update only recipes your person created (revision-1 key ownership); site maintainers (admin scope / owner) may update anything. Patch paths: title, summary, license (non-empty strings), config (object of launch/serve flags merged into the committed yaml), model (hf_repo, params_b, admin keys may also patch aa_intelligence/aa_source_url/aa_index_version), performance (metrics + points; measured 'benchmark' rows always win on conflict), storage_requirements ({ram_gb?, nvme_gb?, ssd_gb?, note?} \u2014 replaces the block, null clears it), layouts ([tp|pp], 1..2 unique \u2014 replaces the list, null clears it), max_context_len (integer tokens \u2014 replaces the declaration, null clears it), kv_pool_tokens (integer tokens \u2014 replaces the declaration of the TOTAL KV-cache capacity across concurrent requests, null clears it). Unknown paths are rejected, never silently dropped. Renames are not supported.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipePatch"},"example":{"config":{"gpu-memory-utilization":0.93000000000000004884981308350688777863979339599609375},"summary":"as-run notes for r3"}}}},"responses":{"200":{"description":"Update committed (new revision). Body: slug, revision, applied paths, commit_sha, content_hash, links.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeUpdateReceipt"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"409":{"$ref":"#/components/responses/Problem"},"413":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"}}},"delete":{"operationId":"retireRecipe","summary":"Retire a recipe (soft-delete, admin)","description":"Admin-only (admin scope or site owner). Soft-delete: sets deleted_at (the same retire pattern the CLI uses), so the JSON twin AND the HTML detail page respond 404 afterwards, while git corpus history preserves every commit. Optional body {\"reason\":\"...\"} (<=500 chars) is recorded in the recipe.retired event. Non-admin: 403 Problem. Unknown/no-live slug: 404 Problem.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"reason":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Retired (soft-delete). Body: slug, retired:true, reason."},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/recipes/{slug}/fork":{"post":{"operationId":"forkRecipe","summary":"Fork a live recipe into a new community-tier recipe owned by the caller","description":"FORK RULES: any valid write:recipes key may fork ANY live recipe \u2014 forking is the community answer to owner-only updates (your fork is YOURS: only you, or a site maintainer, can update it afterwards). The fork's revision 1 COPIES the source's live revision byte-for-byte (config, notes, engine_source); the fork is ALWAYS tier=community regardless of the source's tier (an admin may curate it later via the tier PATCH); title becomes 'Fork of <source title>' and the summary is prefixed 'Forked from /r/<source-slug>.'; credits are carried and fork provenance is recorded in provenance. Model/hardware/task/tag/engine facet links are COPIED as the fork's own rows, and the lineage is stored in recipes.forked_from_recipe_id + fork_of_revision_id (queryable). The new slug is '<source-slug>-fork', deduped as -fork-2, -fork-3 \u2026 Response 201 with Location; a fork of a fork chains normally. 404 unknown slug; 409 when the source is soft-deleted (retired) or has no live revision.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}}],"responses":{"201":{"description":"Fork created. Body: slug, forked_from, revision_id, commit_sha, content_hash, links. Location header points at the new twin.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeForkReceipt"}}}},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"409":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/recipes.json":{"get":{"operationId":"listRecipes","summary":"Live recipes (JSON index, filterable)","description":"Same filters as the HTML home page: q (websearch), engine, model (family slug \u2014 includes variants), quant, hardware (slug or <family>[-x<n>]), task, tags, sort, featured, tier, page. engine/model/quant/hardware/tags/generation/tier accept comma-separated values as OR/match-ANY (browse v3/v4 Oct 2026; single value identical; tier with BOTH values equals All). vram is accepted but ignored (facet retired Oct 2026). Meta echoes the applied filters.","security":[],"parameters":[{"name":"q","in":"query","schema":{"type":"string","maxLength":200},"description":"Websearch query over title + summary (quoted terms, OR/AND supported)"},{"name":"engine","in":"query","schema":{"type":"string"}},{"name":"model","in":"query","schema":{"type":"string"},"description":"Model FAMILY slug (base_family grouping); includes recipes of the family and its variant models"},{"name":"quant","in":"query","schema":{"type":"string"}},{"name":"hardware","in":"query","schema":{"type":"string"},"description":"Exact hardware slug or normalized family bucket <family>[-x<count>]; comma-separated values are OR/match-ANY (hardware=a,b,c)"},{"name":"generation","in":"query","schema":{"type":"string","pattern":"^[a-z]+(,[a-z]+)*$"},"description":"GPU generation family (Blackwell, Ampere, non-NVIDIA \u2026); comma-separated values are OR/match-ANY (generation=blackwell,ampere)"},{"name":"task","in":"query","schema":{"type":"string"}},{"name":"tags","in":"query","schema":{"type":"string","pattern":"^[a-z0-9-]+(,[a-z0-9-]+)*$"},"description":"Feature tag slugs; comma-separated values are OR/match-ANY (tags=dspark,mtp3) \u2014 identical to the tags[] array form"},{"name":"vram","in":"query","schema":{"type":"integer","minimum":0,"maximum":1024},"deprecated":true,"description":"RETIRED Oct 2026: accepted for old links, silently ignored (no filter effect)"},{"name":"sort","in":"query","schema":{"type":"string","enum":["updated","created","title","score","votes","tps"]}},{"name":"featured","in":"query","schema":{"type":"boolean"}},{"name":"tier","in":"query","schema":{"type":"string","pattern":"^(curated|community)(,(curated|community))*$"},"description":"Omit for both; comma-separated values are OR (curated,community == omit)"},{"name":"page","in":"query","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Filtered recipe index with pagination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeIndex"}}}}}}},"/api/v1/leaderboard.json":{"get":{"operationId":"leaderboard","summary":"Leaderboards per hardware configuration","description":"One board per hardware configuration (card family at the same device count; RTX PRO 6000 Max-Q == plain RTX PRO 6000). Every model with a recipe for that hardware competes together; entries are ranked by the requested sort mode and show C4/C8 concurrency, prefill, TTFT at C1, and evidence (measured run file vs author-reported). Each entry carries its base model name and aa_intelligence \u2014 the Artificial Analysis index of that base model, with aa_index_version/aa_source_url provenance. aa is the effective value: COALESCE(aa_intelligence, aa_published_index) \u2014 the exact number the HTML AA cell renders and the value sort=aa ranks on (sorting entries by aa reproduces the page order; null trails, as rendered). aa_provenance marks the quality tier: measured = locally recorded index (aa_intelligence); published = AA-published index only (aa_published_index + aa_published_methodology_version/snapshot_url/fetched_at from the append-only aa_scores register; provenance is descriptive metadata on every row \u2014 it is NOT a rank level, a published index ranks by its number like any other, and carries provenance in the number tooltip \u2014 the visible AA published chip is retired for good); unscored = neither (n/a, trailing). Entry order is identical to /leaderboard with the same sort. Each entry also carries max_context_len \u2014 the DECLARED maximum context of the recipe in tokens (recipe metadata from the live revision, NOT a measurement; null = not declared, and sort=max_context ranks declared rows DESC with undeclared rows trailing last). aa_source_url carries the source of the EFFECTIVE aa value (the page the number was read from \u2014 recorded page for measured rows, register snapshot for published rows; equals the href the HTML cell renders) and the raw published snapshot is additionally exposed as aa_published_source_url.","parameters":[{"name":"sort","in":"query","required":false,"description":"Row rank WITHIN boards (board section order is always hardware-driven). Evidence (measured-before-claimed) and slug tiebreaks apply for every mode; entry order equals /leaderboard?sort=<same value>. votes = community vote count DESC (recipe-grain). c8 = the exact-C8 decode cell DESC (the C8 decode column; null-C8 trails, then the C1 decode fallback orders the null-C8 tail). added = recipe creation date DESC, newest first (when the recipe was first listed). updated = last real revision date DESC (the same updated_at the recipe JSON twins expose; views, votes and benchmark imports never re-order it). max_context = the DECLARED max_context_len of the recipe DESC (recipe metadata carried on every entry as max_context_len; rows without the declaration sink LAST via null-trail, never first).","schema":{"type":"string","enum":["aa","decode","c8","prefill","ttft","evidence","votes","added","updated","max_context"],"default":"aa"}},{"name":"then","in":"query","required":false,"description":"Optional second sort level; one of aa, decode, c8, prefill, ttft, evidence, votes, added, updated, max_context, applied immediately after the primary level. Defaults to decode. A value equal to sort or an invalid value falls back to the default. Evidence and slug always close the chain. The effective chain is echoed in meta.sort_chain.","schema":{"type":"string","enum":["aa","decode","c8","prefill","ttft","evidence","votes","added","updated","max_context"],"default":"decode"}},{"name":"hardware","in":"query","required":false,"description":"BOARD FILTER (t_439f5d88): keep only the boards whose hardware bucket matches one of the listed slugs (comma-separated, OR / match-ANY \u2014 the same token grammar /browse ?hardware= uses: <card_family> at device_count 1, else <card_family>-x<device_count>, e.g. rtx-pro-6000-blackwell-x4, 170hx-x3). Absent/empty = all boards. One invalid slug drops the WHOLE value (no partial filter). The requested list is echoed in meta.applied.hardware.","schema":{"type":"string"}}],"security":[],"responses":{"200":{"description":"Boards: hardware configuration -> ranked entries (each with its model + AA index)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/v1/scores.json":{"get":{"operationId":"scoreIndex","summary":"Composite recipe scores (decode + prefill + quality)","description":"One row per live recipe. Percentages are normalized per GPU generation (100 = best measured build on that hardware class); speeds are whole-rig as reported (decode_tok_s / prefill_tok_s). score_basis \"speed-only\" means no eval scores are recorded yet and the composite is 80% decode / 20% prefill.","security":[],"responses":{"200":{"description":"Score rows sorted by generation then score","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/v1/stats.json":{"get":{"operationId":"pageStats","summary":"Page-visit counters (admin key)","description":"Server-side page-visit statistics keyed by ROUTE PATTERN (family) + UTC day: top pages and top recipe detail pages over the last 30 days, plus a geo section (top country+region aggregates at hashed /24 (IPv4) or /64 (IPv6) network-prefix grain; country + region only; full IP addresses are never stored; hashed prefixes are retained 90 days for country resolution, then dropped; the daily counts are additionally folded into permanent rollup tables (stats_site_daily / stats_recipe_daily) retained indefinitely so traffic trends survive the prefix expiry). Raw counts by design \u2014 robots included, nothing per-visitor; the site owner's own visits are excluded from the counters entirely. Includes unique_visitor_networks_30d \u2014 distinct hashed /24 (IPv4) //64 (IPv6) network prefixes seen over the last 30 days (no cookies or sessions; the owner is excluded; shared networks count once). Admin scope required (401 without a key, 403 without the admin scope); may become public later.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Top route families + top recipe slugs by 30-day hits","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/recipes/{slug}.json":{"get":{"operationId":"getRecipe","summary":"One recipe twin (full config + agent snippet)","security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}}],"responses":{"200":{"description":"Recipe twin","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeTwin"}}}},"404":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/votes":{"post":{"operationId":"castVote","summary":"Toggle your upvote on a recipe (verified keys only)","description":"One vote per verified identity per recipe. Your key IS your vote identity: you and the bots you run share it \u2014 any key belonging to your person (your login key or a bot key you minted) maps to the SAME single vote, so if your bot upvoted a recipe that is your vote and you can see it here and on the recipe page. Calling again RETRACTS the vote (toggle): first call {votes:1,active:true}, second call {votes:0,active:false}. Requires a valid Bearer key (Authorization: Bearer, a lar_ token from the magic-link flow); anonymous callers get 401. Votes attach to RECIPES, not models: a model with five recipes has five independent vote pools. Body: {\"slug\": \"<recipe-slug>\"}. Response 200: {\"slug\", \"votes\" (new total for that recipe), \"active\" (true = your vote is counted; false = retracted)}. 404 unknown slug; 422 invalid slug.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}}}}}},"responses":{"200":{"description":"Current vote state after the toggle; returns the recipe slug, its new total, and whether the caller's vote is counted.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/families/{slug}":{"patch":{"operationId":"promoteFamily","summary":"Promote a pending model family (admin)","description":"Admin-only (admin scope or site owner). Body {\"pending\": false} promotes a PENDING family (created by other-flow submissions) into the pickers and facets: major flips to true and pending to false, one-way. Emits the family.promoted event. Non-admin: 403 Problem. Unknown slug: 404 Problem. Already-promoted families return 200 changed:false with no event.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["pending"],"properties":{"pending":{"type":"boolean","enum":[false],"description":"Must be exactly false \u2014 promotion is one-way."}}}}}},"responses":{"200":{"description":"Promotion result. Body: slug, major, pending, changed."},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/benchmarks":{"post":{"operationId":"importBenchmarkRun","summary":"Import a benchmark run file (llm-inference-bench et al). The file is stored verbatim; DB rows are derived from it.","description":"Envelope requires tool + engine. Send gzipped (Content-Encoding: gzip) for large sweep matrices \u2014 decoded cap 4 MB. Benchmark runs must reference an existing recipe via recipe_slug: unattached runs are still stored (reachable by run-report URL) but never render on boards, and the 201 receipt carries attached:false plus a problem hint \u2014 attaching requires a key carrying the write:benchmarks scope (admin keys included); a write:recipes-only key imports the run file but cannot attach it. Tier: optional envelope key verified|spot-check \u2014 only an ADMIN key (admin scope or site owner) may set verified; a non-admin key sending verified is NOT rejected but the run is stored as spot-check and the forcing is recorded (tier_forced in the receipt). Optional envelope keys: deployment {layout_kind enum tp|pp|single|other; layout_detail string <=64 chars; offload enum none|partial|cpu|nvme; offload_detail string <=64 chars}, report_md (string <=262144 bytes, stored inside the payload), run_conditions {shared_server bool, duration_per_cell number, note string}. model_revision, when present, must be a 40-hex HF commit SHA. Full field table: /benchmarks/api. Responses carry X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers (daily window roll at midnight UTC) on 201 and 429; 429 also carries Retry-After.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BenchmarkRun"}},"application/gzip":{"schema":{"type":"string","format":"binary","description":"gzip-wrapped BenchmarkRun JSON"}},"application/x-gzip":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"Run imported (idempotent by content_hash)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BenchmarkImported"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"403":{"$ref":"#/components/responses/Problem"},"413":{"$ref":"#/components/responses/Problem"},"422":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/benchmarks/{run_sha}.json":{"get":{"operationId":"getBenchmarkRunFile","summary":"Fetch the committed run file for a benchmark run, verbatim","description":"The sha IS the content hash of the file, so the body is immutable and cacheable forever (ETag = sha, Cache-Control: immutable). 410 if the object is indexed but no longer reachable from the corpus HEAD (drift \u2014 operator runs bench --verify).","security":[],"parameters":[{"name":"run_sha","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{40}$"},"description":"content_hash returned by the import or shown in boards JSON"}],"responses":{"200":{"description":"The run file, byte-for-byte as committed"},"304":{"description":"Not modified (If-None-Match matches the sha)"},"404":{"$ref":"#/components/responses/Problem"},"410":{"$ref":"#/components/responses/Problem"}}}},"/healthz":{"get":{"operationId":"healthProbe","summary":"Dependency probe: Postgres index + git corpus ref","description":"200 when every dependency answered; 503 when degraded, with the failing component named. Counts and booleans only \u2014 never DSNs or paths \u2014 so it is safe to cron or expose to an uptime check.","security":[],"responses":{"200":{"description":"ok"},"503":{"description":"degraded (checks name the failing component)"}}}},"/api/v1/keys":{"post":{"operationId":"requestKeyLink","summary":"Email a single-use key-issuance magic link","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}},"additionalProperties":false}}}},"responses":{"202":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/keys/current":{"delete":{"operationId":"revokeCurrentKey","summary":"Revoke the presented key (self-service)","security":[{"bearerAuth":[]}],"responses":{"200":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"lar_\u2026 token from the magic-link flow. Login-issued keys expire after 90 days (sliding: any /keys magic-link login within the last 30 days of life extends the SAME key to now()+90d \u2014 the token never changes). A 401 whose problem body carries type https://localairecipes.com/problems/key-expired + code key_expired + renew_by means EXPIRED \u2014 tell your human to re-login at /keys to renew (or renew yourself by consuming a fresh magic link). Wrong/unknown keys return a generic 401 without the marker; revoked keys return a generic 403."}},"responses":{"RecipeCreated":{"description":"Recipe created; corpus commit returned","headers":{"Location":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeCreatedResponse"}}}},"Problem":{"description":"RFC 9457 problem+json","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"schemas":{"Problem":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"},"code":{"type":"string","description":"Machine-readable error code. Present ONLY on the expired-key 401: code=key_expired with type https://localairecipes.com/problems/key-expired and a renew_by hint. Wrong/unknown-key 401s and revoked-key 403s carry NO code (no oracle)."},"renew_by":{"type":"string","description":"Human-actionable renewal instruction (t_819bb797), present only with code=key_expired: log in at https://localairecipes.com/keys via the magic-link email to renew your API key. Relay this to your human operator."},"errors":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"ValidationError":{"type":"object","required":["pointer","detail","code"],"properties":{"pointer":{"type":"string","description":"RFC 6901 JSON Pointer into the submission"},"detail":{"type":"string"},"code":{"type":"string","enum":["missing_field","invalid_type","invalid_format","invalid_value","unknown_field","constraint_violation"]}}},"RecipeSubmission":{"$ref":"/api/spec/v1/recipe-submission.json"},"RecipePatch":{"type":"object","minProperties":1,"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":4000},"summary":{"type":"string","minLength":1,"maxLength":4000},"license":{"type":"string","minLength":1},"config":{"type":"object","description":"Keys merged into the committed yaml config block (e.g. gpu-memory-utilization, block-size, launch flags).","additionalProperties":true},"model":{"type":"object","description":"Admin keys may additionally patch aa_intelligence (number 0-100) with its required aa_source_url provenance and the optional aa_index_version string \u2014 PATCHing these attaches the model family aa_scores register row, exactly like a POST carrying the block.","properties":{"hf_repo":{"type":"string"},"params_b":{"type":["number","null"]}},"additionalProperties":false},"performance":{"type":"object","properties":{"claimed_by":{"type":"string","default":"submitter"},"points":{"type":"array","items":{"type":"object","required":["context_len"],"properties":{"context_len":{"type":"integer"},"prefill_tok_s":{"type":["number","null"]},"decode_tok_s":{"type":["number","null"]},"ttft_ms":{"type":["number","null"]},"concurrency":{"type":["integer","null"]},"claimed_by":{"type":"string"},"notes":{"type":"string"}}}}},"additionalProperties":true,"description":"KV metrics copied verbatim; points MERGE with ON CONFLICT DO NOTHING so measured benchmark rows always win."},"provenance":{"type":"object","properties":{"credit":{"type":"array","maxItems":10,"items":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":120},"url":{"type":"string","format":"uri"}},"additionalProperties":false},"description":"Replace the ORIGINAL-author list ({name, url?} each \u2014 creators and major adjusters); [] clears it."}},"additionalProperties":false},"max_context_len":{"type":["integer","null"],"minimum":0,"maximum":100000000,"description":"Replace the DECLARED max context (integer tokens, e.g. 1048576 = 1M); null clears it. Declared by the submitter, never validated against the launch command."},"kv_pool_tokens":{"type":["integer","null"],"minimum":0,"maximum":10000000000,"description":"Replace the DECLARED TOTAL KV-cache capacity across concurrent requests (integer tokens, e.g. 616916 = the 256K-window pool on a 4x CMP 170HX TC-MoE build); null clears it. A DIFFERENT number from max_context_len (per-request). Declared by the submitter from an explicit source statement (vLLM \"GPU KV cache size\" log line, SGLang max_total_num_tokens/--max-total-tokens, or explicit README wording), never derived from VRAM math."},"tier":{"type":"string","enum":["curated","community"],"description":"ADMIN ONLY, patched alone: change the trust tier (curated = maintainer-checked). Non-admin PATCHes carrying tier are rejected 403. Emits the recipe.curated event with old->new."}}},"RecipeUpdateReceipt":{"type":"object","required":["slug","updated","revision"],"properties":{"slug":{"type":"string"},"updated":{"type":"boolean","description":"false when the patch had no effective changes (no revision burned)"},"revision":{"type":"integer"},"revision_id":{"type":"integer"},"applied":{"type":"array","items":{"type":"string"},"description":"JSON Pointer list of paths actually applied"},"commit_sha":{"type":["string","null"],"description":"corpus git commit holding the new revision (null when updated=false)"},"content_hash":{"type":["string","null"]},"links":{"type":"object","properties":{"self":{"type":"string"},"html":{"type":"string"},"revision":{"type":"string"}}}}},"RecipeCreatedResponse":{"type":"object","required":["slug","revision_id","commit_sha","content_hash","links"],"properties":{"slug":{"type":"string"},"revision_id":{"type":"integer"},"commit_sha":{"type":"string","description":"SHA-1 of the corpus commit"},"content_hash":{"type":"string","description":"SHA-1 of the recipe.yaml blob"},"links":{"type":"object","additionalProperties":{"type":"string"}}}},"RecipeForkReceipt":{"type":"object","required":["slug","forked_from","revision_id","commit_sha","content_hash","links"],"properties":{"slug":{"type":"string","description":"The NEW fork slug (<source>-fork, deduped -fork-2 \u2026)"},"forked_from":{"type":"string","description":"Source recipe slug"},"revision_id":{"type":"integer","description":"The fork revision-1 id; config/notes/engine_source are byte-identical copies of the source live revision"},"commit_sha":{"type":"string","description":"SHA-1 of the fork corpus commit"},"content_hash":{"type":"string"},"links":{"type":"object","additionalProperties":{"type":"string"}}}},"BenchmarkRun":{"type":"object","required":["tool","engine"],"additionalProperties":true,"properties":{"tool":{"type":"string","description":"e.g. llm-inference-bench"},"tool_version":{"type":"string"},"tool_commit":{"type":"string","description":"git SHA of the tool"},"engine":{"type":"string"},"engine_version":{"type":"string"},"model":{"type":"string"},"model_revision":{"type":"string","description":"40-hex HF commit SHA"},"gpu_model":{"type":"string"},"gpu_uuid":{"type":"string"},"gpu_count":{"type":"integer"},"driver":{"type":"string"},"concurrency":{"type":"integer"},"context_len":{"type":"integer"},"median":{"type":"number","description":"primary metric (output tok/s)"},"stddev":{"type":"number"},"slot_ceiling":{"type":"integer","description":"concurrent slots the server allows; runs without this cannot be read safely later"},"run_url":{"type":"string","format":"uri","description":"https pointer to the full run file (pointer transport)"},"recipe_slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$","description":"Slug of a LIVE recipe this run measured; required for boards. Attaching requires a key carrying write:benchmarks (admin keys included); runs without a live match are stored but never render on boards (receipt attached:false + problem hint)."},"measured_by":{"type":"string"},"method":{"type":"string"},"method_ref":{"type":"string","format":"uri"},"metrics":{"type":"object","description":"{decode_tok_s, prefill_tok_s, ttft_ms, ...}"},"deployment":{"type":"object","description":"Optional deployment descriptor: {layout_kind enum tp|pp|single|other, layout_detail string <=64 chars, offload enum none|partial|cpu|nvme, offload_detail string <=64 chars}. Omitted = undeclared (renders as such, never rejected); unknown sub-keys preserved verbatim."},"report_md":{"type":"string","description":"Optional tool-eval markdown report, at most 262144 bytes (256 KB), stored inside the run payload (not a separate blob)."},"tier":{"type":"string","enum":["verified","spot-check"],"description":"verified requires an admin-scope key (admin scope or site owner); any other key sending verified is NOT rejected \u2014 the run is stored as spot-check and the forcing is recorded (tier_forced in the receipt)."},"run_conditions":{"type":"object","description":"Optional run-context notes: {shared_server bool, duration_per_cell number (seconds per matrix cell), note string}."}}},"BenchmarkImported":{"type":"object","required":["run_id","content_hash","tier","replay"],"properties":{"run_id":{"type":"integer"},"content_hash":{"type":"string","description":"corpus blob sha \u2014 dedup + integrity key"},"corpus_path":{"type":"string","description":"path of the committed run file inside the corpus repo, e.g. benchmarks/3a/<sha>.json"},"tier":{"type":"string","enum":["verified","spot-check"],"description":"Report tier of the stored run: verified only via an admin key; spot-check is the default self-measured tier (non-admin verified claims are forced here and the forcing is recorded)."},"attached_to":{"type":["string","null"],"description":"recipe slug when recipe_slug matched a live recipe"},"attached":{"type":"boolean","description":"true when the run is attached to a live recipe; false = stored but NOT on boards \u2014 benchmark runs must reference an existing recipe via recipe_slug to appear on boards"},"problem":{"type":["string","null"],"description":"present (with a hint) when attached is false"},"replay":{"type":"boolean","description":"true when this content_hash was already imported; no new derived rows were created"}}},"RecipeIndex":{"type":"object","required":["meta","recipes"],"properties":{"meta":{"type":"object","required":["count","page","pages","per_page"],"properties":{"count":{"type":"integer","description":"TOTAL matching recipes (not just this page)"},"page":{"type":"integer"},"pages":{"type":"integer"},"per_page":{"type":"integer"},"applied":{"type":"object","description":"echoes the filters that took effect (null/[] = unfiltered)"},"origin":{"type":"string","format":"uri"},"openapi":{"type":"string"}}},"recipes":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"tier":{"type":"string","enum":["curated","community"],"description":"curated = checked by the maintainers; community = user/agent submitted"},"decode_tok_s":{"type":["number","null"],"description":"WHOLE-RIG decode tok/s as reported (median cached C1 point on the live revision) \u2014 never divided per GPU."},"prefill_tok_s":{"type":["number","null"],"description":"WHOLE-RIG prefill tok/s as reported (median 32k point on the live revision) \u2014 never divided per GPU."},"updated_at":{"type":"string","format":"date-time"},"votes":{"type":"integer"},"credit":{"type":"array","description":"ORIGINAL author(s) of the engine build/config (empty = poster-authored).","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri"}}}},"links":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"RecipeTwin":{"type":"object","required":["slug","title","revision","config","agent","links"],"properties":{"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"license":{"type":"string"},"tier":{"type":"string","enum":["curated","community"]},"revision":{"type":"object","properties":{"no":{"type":"integer"},"commit_sha":{"type":"string"},"content_hash":{"type":"string"},"notes":{"type":"string","description":"Revision notes: provenance wording for the live revision (attribution, deviation notes). Same text as the Revision notes section on the HTML detail page."}}},"engine_source":{"type":"object","description":"Engine provenance: source_type (release|fork|docker_image), git_url (engine repo), pinned_commit, image_digest. image_digest links to its registry page (Docker Hub / GHCR) on the HTML detail page.","properties":{"type":{"type":"string","enum":["release","fork","docker_image"]},"base_engine":{"type":"string"},"base_version":{"type":"string"},"git_url":{"type":"string","format":"uri"},"pinned_commit":{"type":"string"},"image_digest":{"type":"string"},"install_note":{"type":"string","description":"Optional warning prose: when non-empty the recipe is NOT installable as written (e.g. engine requires a source build, no image published). Rendered as a visible warning on the HTML detail page."}}},"requirements":{"type":["object","null"],"description":"Runtime prerequisites, derived at render time for image-based recipes (engine_source.type=docker_image); null for forks/source builds. requires: Docker + NVIDIA Container Toolkit. driver_floor: CUDA level parsed from the image tag (defensive - null when the tag carries no cu<NNN> token) plus the minimum NVIDIA Linux driver, cited to the NVIDIA CUDA Toolkit Release Notes; cuda/linux_min null and text says \"unknown - check the image tag\" when the level cannot be read. placeholders: values describing the author rig (GPU UUID via nvidia-smi -L, model directory bind-mount paths, host port) that the agent must remap to its own machine. Same content as the Install requirements section on the HTML detail page.","properties":{"requires":{"type":"array","items":{"type":"string"}},"driver_floor":{"type":"object","properties":{"cuda":{"type":["string","null"]},"linux_min":{"type":["string","null"]},"text":{"type":"string"},"source":{"type":"string"}}},"placeholders":{"type":"array","items":{"type":"string"}}}},"artifacts":{"type":"array","description":"Download pointers (model weights, overlays, engine image/git pins) - everything needed to fetch the setup generically. Same content as the Downloads section on the HTML detail page.","items":{"type":"object","properties":{"kind":{"type":"string"},"pointer_type":{"type":"string"},"pointer":{"type":"string"},"pin":{"type":["string","null"]}}}},"credit":{"type":"array","description":"ORIGINAL author(s) of the engine build/config \u2014 the people who created it or made major adjustments. Empty = the submitter authored it; author{} then carries the poster identity.","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri"}}}},"author":{"type":"object","description":"The POSTER: person behind revision 1 (who submitted the recipe here). isSite=true means a site maintainer.","properties":{"name":{"type":"string"},"isSite":{"type":"boolean"}}},"config":{"type":"object","required":["launch"],"properties":{"launch":{"type":"array","items":{"type":"string"},"description":"Exact launch argv array"}}},"models":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"hf_repo":{"type":"string","description":"Hugging Face repo id \u2014 links to https://huggingface.co/{hf_repo}"},"quant":{"type":["string","null"]},"params_b":{"type":["number","null"]}}}},"hardware":{"type":"array","items":{"type":"object","description":"Exact hardware the recipe was validated on. card is the exact variant; card_family is the normalized board/facet grouping. The facet filter (?hardware=) accepts the exact slug or a normalized family bucket <family>[-x<count>] (e.g. rtx-pro-6000-blackwell-x4); comma-separated values match ANY (OR). On SUBMISSIONS the optional profile field pins the hardware_profiles.slug the submitter selected (the /submit form hardware selector always sends it, so the stored profile is EXACT \u2014 no slug derivation); bodies without it keep the legacy vendor/card/xN slug derivation.","properties":{"slug":{"type":"string"},"profile":{"type":"string","description":"SUBMISSIONS ONLY: hardware_profiles.slug pinned at submit time (e.g. rtx-5090-x2); the ingest links that exact profile and never silently creates one"},"vendor":{"type":"string"},"card":{"type":"string"},"card_family":{"type":["string","null"]},"vram_gb":{"type":["integer","null"]},"device_count":{"type":"integer"}}}},"tags":{"type":"array","items":{"type":"string"}},"tasks":{"type":"array","items":{"type":"string"}},"performance":{"type":["array","null"],"items":{"type":"object"}},"speeds":{"type":"object","description":"Best-effort summary speeds for this recipe (read-only view data): WHOLE-RIG tok/s as reported, never divided per GPU. decode = median cached C1 point; prefill = median 32k point.","properties":{"decode_tok_s":{"type":["number","null"]},"prefill_tok_s":{"type":["number","null"]},"leaderboard":{"type":"string"}}},"perf_points":{"type":"array","description":"Speed by context: one row per (context_len, concurrency, claimed_by). claimed_by=submitter is a T0 claim, benchmark is T1 measured. Never blended across tiers.","items":{"type":"object","properties":{"id":{"type":"integer"},"context_len":{"type":"integer"},"concurrency":{"type":["integer","null"]},"prefill_tok_s":{"type":["number","null"]},"decode_tok_s":{"type":["number","null"]},"ttft_ms":{"type":["number","null"]},"claimed_by":{"type":"string","enum":["submitter","benchmark"]},"notes":{"type":"string"}}}},"agent":{"type":"object","required":["prompt","curl"],"properties":{"prompt":{"type":"string","description":"Ready-to-paste agent handoff prompt"},"curl":{"type":"string"}}},"links":{"type":"object","additionalProperties":{"type":"string"}}}}}}}