Listing your models
Heimdallm pulls your catalogue from you: you serve one JSON document describing your models, and the marketplace keeps itself in sync with it. You set prices, you keep your infrastructure, and you are paid monthly for the traffic you serve, minus a 10% platform share. This page is the complete provider contract — the flow, every schema field, the health gate, what Heimdallm measures, and how the money works.
The flow
- Apply. Sign in and apply as a provider from your account: display name, a slug, website and a contact email — payout details are not part of the application (see getting paid). An administrator reviews the application; a rejected application can be corrected and re-submitted.
- Add a source. Once approved, your provider dashboard unlocks. A source is a base URL
serving an OpenAI-compatible API (an Anthropic-dialect source is also supported), plus the
credential Heimdallm should call you with. For an OpenAI-compatible source the base URL is the one
an OpenAI SDK takes as
base_urland ends in/v1: inference is sent to<base URL>/chat/completions,<base URL>/embeddings,<base URL>/rerank,<base URL>/systemone,<base URL>/videos/generationsand the other rails your models declare, and your model list is fetched from<base URL>/modelsunless you register a different URL. An Anthropic-dialect source keeps the origin as its base URL, is called at<base URL>/v1/messages, and its model list defaults to<base URL>/v1/models. - Get pulled. Heimdallm fetches the list with
Authorization: Bearer <your credential>every 60 seconds by default (configurable per source, 1 minute to 24 hours) and on demand from the dashboard, which also shows every pull run with its outcome and any findings. - Pass health probes. Each new entry becomes an endpoint in
pending_health. Probes call it through the same inference path customers use; once two of its last three probes succeed, the endpoint goes live, enters routing and appears on the marketplace — no manual step. A failing endpoint is retried on a growing backoff (1, 5, 15, then every 60 minutes) and never forgotten. Opening the endpoint on the dashboard's Evidence tab names the path the last failed probe called and the status your own server answered it with, beside the status the marketplace recorded; Probe now re-runs the check at once, at most once a minute per endpoint, so a fix does not wait out the backoff.
The model list document
Serve JSON at your models URL: { "data": [ … ] } or a bare array, one entry per model, each
carrying schema_version: "2.4". That is the format published for provider monitoring at
openrouter.ai/docs/guides/community/for-providers, so the one document you maintain lists a model
in both places when it uses the published vocabulary. Marketplace metadata rides under heimdall.
An entry using a mesh output and its mesh pricing unit, a decisions output, or strict
exclusive_min or exclusive_max parameter bounds, or a free-text { "type": "string" } descriptor
lists on Heimdallm alone. The provider service omits such entries from its format=openrouter view and
keeps them in its native model list. Descriptor description prose is accepted and removed before the
entry is stored.
The mesh rail cannot ride under heimdall at all, because an output modality's type is what
selects a route and that key names no route. Limits per
pull: 5 MB, 2000 entries, 15 seconds. Entries validate independently: an invalid entry
lists nothing and is reported in your dashboard against the field that failed, as a path you can
find in your own document (data[0].input_modalities[1].pricing[2].unit), while the entries that
validate still update. An endpoint no valid entry lists is withdrawn until a pull that validates
lists it again, and then returns through pending_health with no manual step. A key the schema does not name is refused against the entry that carries it rather than
kept and ignored, since it is either a typo for a field that is read or a declaration nothing acts
on; openrouter.slug is the one exception, accepted and read by nothing. Nothing is ever invented
for a field you leave out: it is either read off the Hugging Face repository you linked
(below) or shown as absent.
An entry describes a model as typed modality objects. input_modalities holds one object per kind
of content the endpoint accepts and output_modalities one per kind of output it produces, and each
object owns what belongs to it: an input owns the constraints on what it accepts
(supported_inputs), an output owns the parameters it honours (supported_parameters), and either
owns the pricing and capacity arrays for the units it meters. Only a price or a limit scoped to
the whole request — a flat rate per request, a web search, a concurrency ceiling — sits at the entry
root, beside the identity, operational and compliance fields.
The short document is a complete one. Almost everything a listing shows is published on
huggingface.co beside the weights, and Heimdallm reads it from there. What you write is
hugging_face_id, your prices, is_ready and your live capacity; the name, description, release
date, context window, weight format, tags, licence, parameter count, storage dtype, recommended
sampling and base model are read off the repository on the pull. Put your engine’s parameter
settings beside its completion price. Missing tools and tool_choice members can be filled from
the published prompt interface; other parameters need explicit declarations. A priced entry is
schema_version, id, hugging_face_id, a text input carrying the prompt price, a text output
carrying the completion price and the parameters you honour, is_ready and
heimdall.live_capacity:
{
"schema_version": "2.4",
"id": "qwen/qwen3.8-27b",
"hugging_face_id": "RedHatAI/Qwen3.8-27B-INT4",
"input_modalities": [
{ "type": "text", "pricing": [{ "type": "prompt", "unit": "token", "cost_usd": "0.00000005" }] }
],
"output_modalities": [
{
"type": "text",
"supported_parameters": {
"temperature": { "type": "range", "min": 0, "max": 2 },
"max_tokens": { "type": "integer", "min": 1, "max": 69632, "unit": "token" },
"tools": { "type": "boolean" }
},
"pricing": [{ "type": "completion", "unit": "token", "cost_usd": "0.00000015" }]
}
],
"is_ready": true,
"heimdall": { "live_capacity": { "current": 3, "total": 8 } }
}
An entry this short can list once the repository supplies its required name. Optional facts such as
the release date and token window remain absent when the repository does not publish them. A private,
misspelled or unavailable repository can leave the name unfilled, and the pull reports that missing
field. A gated repository may still expose public metadata even when its files require access. Declare
a field the repository answers only where your deployment differs from the weights as published — an
engine that quantises on the way in declares its quantization, and a window served shorter than
the checkpoint holds declares its max_context_length — because what you declare is kept exactly as
you declared it. Everything else is optional, and this entry exercises it: a second input with its
own constraints, a cache SKU with a lifetime, a reasoning rate, a request-scoped price and limit,
passthrough parameters at two scopes, and every member of heimdall.
{
"schema_version": "2.4",
"id": "deepseek/deepseek-v4-flash-0731-abliterated",
"name": "DeepSeek: DeepSeek V4 Flash 0731 (abliterated)",
"created": 1786677579,
"description": "An abliterated rebuild of DeepSeek V4 Flash 0731...",
"hugging_face_id": "cebeuq/DeepSeek-V4-Flash-0731-abliterated",
"quantization": "fp8",
"tokenizer": "DeepSeek",
"input_modalities": [
{
"type": "text",
"supported_inputs": { "max_context_length": { "value": 131072, "unit": "token" } },
"pricing": [
{ "type": "prompt", "unit": "token", "cost_usd": "0.0000001" },
{ "type": "cached_prompt", "unit": "token", "cost_usd": "0.000000037" },
{ "type": "cache_write", "unit": "token", "ttl_seconds": 300, "cost_usd": "0.000000125" }
],
"capacity": [{ "type": "prompt", "unit": "token", "per": "minute", "value": 2000000 }]
},
{
"type": "image",
"supported_inputs": {
"sources": { "type": "enum", "values": ["url", "base64"] },
"formats": { "type": "enum", "values": ["image/png", "image/jpeg", "image/webp"] },
"references": { "type": "integer", "min": 0, "max": 4 },
"max_content_size_bytes": { "value": 20971520, "unit": "byte" }
}
}
],
"output_modalities": [
{
"type": "text",
"max_length": { "value": 32768, "unit": "token" },
"streaming": true,
"supported_parameters": {
"temperature": { "type": "range", "min": 0, "max": 2, "default": 1 },
"top_p": { "type": "range", "min": 0, "max": 1 },
"max_tokens": { "type": "integer", "min": 1, "max": 32768, "unit": "token" },
"seed": { "type": "integer", "min": 0, "max": 2147483647 },
"stop": { "type": "array", "items": { "type": "unknown" }, "max_items": 4 },
"tools": { "type": "boolean" },
"tool_choice": { "type": "unknown" },
"response_format": { "type": "unknown" },
"structured_outputs": { "type": "boolean" },
"reasoning_effort": { "type": "enum", "values": ["none", "low", "medium", "high"] }
},
"passthrough_parameters": {
"chat_template_kwargs": {
"type": "object",
"properties": { "enable_thinking": { "type": "boolean" } }
}
},
"pricing": [
{ "type": "completion", "unit": "token", "cost_usd": "0.0000004" },
{ "type": "internal_reasoning", "unit": "token", "cost_usd": "0.0000004" }
],
"capacity": [
{ "type": "completion", "unit": "token", "per": "minute", "value": 400000 },
{ "type": "concurrency", "unit": "request", "value": 32 }
]
}
],
"pricing": [{ "type": "request", "unit": "request", "cost_usd": "0.0001" }],
"capacity": [{ "type": "request", "unit": "request", "per": "minute", "value": 600 }],
"passthrough_parameters": { "service_tier": { "type": "enum", "values": ["standard", "priority"] } },
"is_ready": true,
"datacenters": [{ "country_code": "CA", "region": "ca-central" }],
"deployment_region": "CA",
"compliance": { "zdr": true, "hipaa": false },
"heimdall": {
"serving_id": "dsv4s",
"base_model": "deepseek/deepseek-v4-flash-0731",
"base_model_name": "DeepSeek: DeepSeek V4 Flash 0731",
"base_model_hugging_face_id": "deepseek-ai/DeepSeek-V4-Flash-0731",
"license": "MIT",
"parameter_count": 284000000000,
"weights_dtype": "F8_E4M3",
"tags": ["abliterated", "moe"],
"data_policy": { "training": false, "retention_days": 0 },
"live_capacity": { "current": 1, "total": 4 },
"default_parameters": { "temperature": 0.6, "top_p": 0.95 }
}
}
A provider running the reference provider service writes neither by hand. It edits the service's
configuration file — one doc block per model, in the service's own vocabulary — and the service
builds this document from it at boot, validates it with the parser a pull runs, and publishes it at
/v1/models. A worked example shows every block of that file, and the
entry the service publishes from one of them.
Fields
The entry root is the published schema. Every field is optional unless marked, and a field marked
with an asterisk is read off the repository where hugging_face_id names one that states it, and
required only where it does not — see
what Hugging Face fills in for you.
| Field | Required | Meaning |
|---|---|---|
schema_version | yes | "2.4". |
id | yes | author/slug, lowercase. The author is the creator of the weights, never you. A modified rebuild gets its own slug (e.g. -abliterated) under the original author. Two providers publishing the same id serve the same model. Two independent rebuilds of one base are two models: give each a slug naming its own rebuild (glm-5.3-flash-abliterated-dealign beside glm-5.3-flash-abliterated-jangq), keep both under the original author, and heimdall.base_model groups them into one family. One id per quantization is one endpoint; a second entry with the same id and quantization is a duplicate and is refused. |
name | yes* | Display name, conventionally Author: Model Name. |
created | yes* | The model's release date, Unix seconds. At most 2147483647, so a stamp in milliseconds is refused. A listed model carries one. |
input_modalities | yes* | One object per kind of content the endpoint accepts, typed text, image, video, audio or file. Each owns its constraints, its prices and its limits (input modality objects). A listed entry states its context window in tokens: max_context_length on the text input, or, where there is no text input, max_length on the text output. |
output_modalities | yes* | One object per kind of output the endpoint produces, typed text, image, video, speech, transcription, embeddings, rerank, decisions, audio or mesh. An entry takes traffic on the outputs the marketplace routes: text, embeddings, rerank, decisions, image, video, speech, transcription or mesh. audio output is asked for inside a chat turn rather than on a route of its own, so an entry naming no routed output has no request it can be sent and is refused. Each owns the parameters it honours, its ceiling, its prices and its limits (output modality objects). |
hugging_face_id | no | This model's weights repository: the EXACT id as it appears on huggingface.co, case-sensitive. Only when the weights are actually published there — never guessed. |
description | no* | Shown on the model page. |
quantization | no* | One of int4, int8, fp4, mxfp4, nvfp4, fp6, fp8, mxfp8, fp16, bf16, fp32, or null for weights stored full width. Distinguishes two endpoints of one model from one provider. An absent field is a format nobody stated. |
tokenizer | no | The tokenizer family (GPT, Claude, Llama3), which describes the whole model. |
pricing | no | Request-scoped prices only: request per request (pricing entries). Every token, media and character rate lives on the modality that meters it. |
capacity | no | Request-scoped limits: request, web_search and concurrency (capacity entries). A declared ceiling, not the live occupancy report, which is heimdall.live_capacity. |
passthrough_parameters | no | Provider-specific parameters forwarded as sent, request-wide, as a map from name to a descriptor. A parameter one input or one output owns is declared on that object instead. A customer sends them under provider.options keyed by your slug; a name you declare no descriptor for is refused rather than forwarded. |
is_ready | no | false lists the endpoint but keeps it out of routing. Default true. A live endpoint leaves routing on the second consecutive pull declaring false, so one flickered reading costs nothing and a maintenance window takes hold one pull later. It returns through pending health. |
is_free | no | Marks the endpoint free, and it is billed that way: every unit your document prices is charged at zero whatever the pricing arrays declare, and a unit you price nowhere is unpriced rather than free. :free routes only to endpoints declaring it, so a zero-priced endpoint without the flag is billed at its zeros and never selected by the suffix. |
discount_to_user | no | The fraction taken off the price a customer sees and pays, below 1; a negative value is a markup. At 1 the price is zero and beyond it negative, which is refused. |
deprecation_date | no | 2027-01-31 or 2027-01-31T15:00:00Z, after which the endpoint is withdrawn. |
datacenters | no | Where inference runs, up to 20: [{ "country_code": "CA", "region": "ca-central" }], the country as ISO 3166-1 alpha-2 and the region a label of your own. |
deployment_region | no | A provider label for a deployment served from several regions under distinct model ids. Stored with the endpoint; not published or used to filter routing. |
compliance | no | zdr (zero data retention), hipaa, and other boolean claims. Only zdr and hipaa reach the public catalogue. hipaa is display-only; other claims are stored without publication or routing filters. A zdr claim is vetted per provider: pending until approved, and only an approved claim is displayed as ZDR or reached by provider.zdr: true. Your dashboard shows the ZDR verdict and denial reason. That verdict is permanent for your provider account: changing an endpoint source or base URL does not reset it, and a later ZDR claim inherits it. |
openrouter.slug | no | The slug this model carries on the other marketplace; accepted so one document serves both, and read by nothing here. |
heimdall | no | Heimdallm's own key: what the marketplace serves that the published schema has no field for (the heimdall key). |
Discounts
discount_to_user sits on the entry, and the document has no field that discounts the list as a whole.
The provider dashboard's catalogue discount applies one provider-funded
fraction to every listed model without changing the document. The larger of the endpoint and catalogue
discount applies. A marketplace-funded site promotion covers only any additional discount offered to
customers. The reference provider service can also read a fraction from its DISCOUNT_TO_USER setting
and fill it in on every entry that declares none.
An explicit "discount_to_user": 0 is a declaration rather than a blank. It publishes 0, which states
that the endpoint takes nothing off its published prices, where omitting the field states nothing about a
discount at all. A generator fanning one fraction across the document leaves such an entry as it found
it.
Input modality objects
One object per type, and one type declared once: a second object of the same type would leave its constraints, prices and limits with two answers.
| Field | Required | Meaning |
|---|---|---|
type | yes | text, image, video, audio or file. |
supported_inputs | no* | The constraints this modality has and nothing else. text: max_context_length and max_prompt_length, each { "value": …, "unit": "token" }; the first is the whole window, input and output together, and the second is the input alone, declared only when it differs. image: sources, formats, detail_levels, references, role, max_content_size_bytes. video and audio: sources, formats, max_duration_seconds, max_content_size_bytes. file: sources, formats, references, max_content_size_bytes. sources is url and base64; detail_levels is auto, low, high and original; role is reference, first_frame and last_frame; formats is the MIME types the request plane carries. |
pricing | no | What this input bills: prompt, cached_prompt or cache_write, per token, image, megapixel, second or character (pricing entries). |
capacity | no | Declared limits on the same types, per minute, hour or day (capacity entries). |
passthrough_parameters | no | Provider-specific parameters this input owns, forwarded as sent. |
Routing refuses an endpoint whose declared sources, formats, detail_levels,
max_content_size_bytes or max_duration_seconds a request breaks, judging only what the request
states.
Output modality objects
| Field | Required | Meaning |
|---|---|---|
type | yes | text, image, video, speech, transcription, embeddings, rerank, decisions, audio or mesh. |
supported_parameters | yes | The request parameters this output honours, as a map from name to a descriptor. Routing selects only endpoints declaring every optional setting the caller explicitly requests. Missing tools and tool_choice members can be inferred from the linked checkpoint. Other absent names remain unsupported; { "type": "unknown" } is a parameter accepted with no value domain described. Declare only what you honour. {} omits explicit parameters; use heimdall.disabled_parameters to opt out of tool inference. |
max_length | no | text only: the output ceiling, { "value": …, "unit": "token" }. |
streaming | no | Whether the output arrives as an SSE stream. Not declared on embeddings, rerank, decisions or mesh, none of which produces an answer a piece at a time. |
pricing | no | What this output bills: completion or internal_reasoning, per token, image, megapixel, second, character, request or mesh (pricing entries). mesh is accepted on a mesh output alone, since it prices one delivered asset and no other rail produces one. |
capacity | no | Declared limits on the same types per window, plus concurrency (capacity entries). |
passthrough_parameters | no | Provider-specific parameters this output owns, forwarded as sent. |
Pricing entries
A pricing entry is a type (what is billed), a unit (the basis it is counted in) and cost_usd, a
decimal string in USD with at most 24 digits either side of the point, which is the precision the
marketplace bills at ("0.0000004" is $0.40 per million). It sits on the modality that meters it,
and a type or unit outside its scope is refused, because the figure it carries has no meter behind
it. Each rail declares the rate its own meter reads and nothing else: a "0" written where there is
no meter is a published price nothing is ever charged at, which reads on the marketplace as a free
rail, while an omitted entry is a unit with no published price, recorded as unpriced and flagged
rather than billed at zero.
| Entry | Where | Meaning |
|---|---|---|
prompt per token | the text input | USD per prompt token. The rate of one meter: declared by an entry that bills tokens, left out by one that bills megapixels, characters or seconds. |
prompt per image | the image input | USD per image supplied in the prompt, charged per image whatever its size. For an endpoint that meters images it is handed rather than tokenising them into the prompt; published as image. |
cached_prompt per token | the text input | USD per cached prompt token (cache read). Declaring it also marks the endpoint as cache-capable in the catalogue. ttl_seconds names the cache lifetime a rate applies to and implicit marks caching your engine starts unasked; two entries differing in a qualifier are two SKUs. |
cache_write per token | the text input | USD per prompt token written to cache, with the same qualifiers. |
completion per token | the text output | USD per completion token. |
internal_reasoning per token | the text output | USD per reasoning token, when priced apart from completion. |
completion per token | the audio output | USD per audio token a reply generates, for an endpoint whose answer carries speech. A different unit from the seconds prompt per second prices on the input side; published as audio_output. |
request per request | the entry root | A flat price per request. |
completion per image | an image output | USD per delivered image, with explicit overrides for supported settings. |
completion per megapixel | an image output | USD per 1,000,000 pixels of delivered image area; fractional megapixels are retained. |
completion per second | a video output | USD per second of delivered video, including fractional seconds. |
completion per mesh | a mesh output | USD per delivered mesh, whole. The face ceiling does not divide it: sampling the surface is the dear step and decimating it the cheap one, so a mesh is priced as one asset rather than per face. A tier for an untextured mesh is an overrides row on the same entry ({ "when": { "generate_texture": { "equals": false } }, "cost_usd": … }); published as mesh_output and mesh_output_untextured. |
completion per token | an image output | Published as an image-output token tariff. Dedicated media routes lack a finite image-token reservation bound and refuse positive rates before dispatch. |
prompt per second | the audio input | USD per second of audio decoded from a request (a transcription), which also prices the seconds a chat reply's own speech container carries. A completion per second on a transcription output is read the same way. |
completion per character | the speech output | USD per character of text handed to a voice, for a speech endpoint. The marketplace counts the characters off the request, so the charge is settled before the endpoint answers. Not seconds of speech produced: a dedicated synthesis's own output carries no price and records no token, so a speech entry declares no rate per token at all. |
Legacy documents may contain web_search per search at the entry root. The parser accepts
and stores this declaration, but the gateway cannot count provider-side searches. The price is
not charged or published in customer pricing; omit it until search usage can be metered.
An entry may carry overrides: a list of { "when": …, "cost_usd": … } whose when is a map from
parameter name to one condition (equals, gte, lte or min_items), or a composition with
allOf, anyOf or not. Predicates name parameters the owning modality declares, or quantities
the request produces such as prompt_tokens; overrides are read in order and the last match wins,
which is what makes a price matrix a list of rows. On the token rails prompt_tokens is the one
condition billing evaluates, against the prompt estimate at the credit hold and the reported prompt
count at settlement; a token-rail condition on anything else is never matched and its rate is never
charged. Time is not a request parameter, so a time-of-day price is a separate entry with
utc_start and utc_end (HHMM clock times in UTC, declared
together, read as the half-open window between them, which may wrap midnight) and optionally
utc_days; an entry with no window is the base rate for every hour the windows do not cover. Two
entries of one type and unit under the same qualifiers are refused, naming the entry already
holding that SKU.
Capacity entries
A capacity entry is a type (what is limited), a unit (its basis), a per window of minute,
hour or day, and a positive integer value. Types and units follow the pricing scope that owns
them, so an input entry limits prompt, cached_prompt or cache_write, an output entry limits
completion or internal_reasoning, and a root entry limits request or web_search. Output and
root entries may also declare concurrency with unit: "request" and no per, which counts work
in flight rather than work over a window. An entry's identity is its type, unit and window
together: a per-minute burst and a daily quota on one dimension coexist, and two entries with the
same identity are refused. Limits in different scopes are independent buckets that apply at once,
and an absent array is an undeclared limit, not a limit of zero. A declared ceiling is what you
commit to serve; how much of it is in use right now is heimdall.live_capacity.
Capability descriptors
supported_parameters, passthrough_parameters and the supported_inputs constraints share one
grammar: a map from name to a typed descriptor. A descriptor carries only the fields its kind has —
values on a range, or min on an enum, is refused rather than ignored. Numeric kinds may carry
a unit (second, pixel, byte, token or character), and any kind may carry a default.
| Kind | Shape | Meaning |
|---|---|---|
range | { "type": "range", "min": 0, "max": 1 } | Any number in [min, max] |
integer | { "type": "integer", "min": 1, "max": 128000, "unit": "token" } | Any integer in [min, max] |
boolean | { "type": "boolean" } | Supported by being present |
enum | { "type": "enum", "values": ["standard", "priority"] } | A discrete allowlist |
array | { "type": "array", "items": { … }, "max_items": 4 } | A list of what items describes |
object | { "type": "object", "properties": { … } } | A nested object with a descriptor per key |
unknown | { "type": "unknown" } | Accepted, with no machine-readable value domain |
A description on a descriptor is accepted and dropped rather than refused, on every kind and at
every depth. The published descriptor has no member for prose, so a sentence written there reaches
nothing the marketplace stores, serves or shows, and your entry lists as though the sentence were
absent. Refusing it would cost you the entry instead: a model no valid modality lists is withdrawn
whole and re-enters pending_health on the pull that lists it again. Each pull names every descriptor
that carried a sentence, under descriptor_prose_dropped, beside the place the sentence does list.
For a reference member that place is heimdall.reference_inputs.<rail>.description, which is
published and shown beside the picker.
The heimdall key
The published root is closed, so what the marketplace serves and the published schema has no field for lives under one key. Every member is optional.
| Field | Meaning |
|---|---|
serving_id | The model value Heimdallm sends you when it differs from id — e.g. your engine knows the model as dsv4s. |
base_model, base_model_name, base_model_hugging_face_id | The base model this is a variant of. Variants group under their base as a family. base_model_hugging_face_id is the BASE model's weights repository, same exact-casing / never-guess rule, and lets a variant with unpublished weights still link its family to the base. base_model is a marketplace id, never a Hugging Face repository: the family is the id exactly as written, so a repository id names a family of its own. |
license | The licence the weights are published under, as an SPDX id where one covers it. An explicit null declares it unknown. |
parameter_count | Parameters the weights hold, as an integer. |
weights_dtype | The dtype most of those parameters are stored in, as safetensors names it (BF16, F8_E4M3, I8): the storage width, not the quantization a buyer filters on. |
tags | Up to 12 short tags. Unioned over every provider serving the model, so yours count even when another provider's listing owns its metadata. Two carry meaning beyond display: no-benchmarks opts the model out of every scored benchmark, keeping its health probes and its measured output rate, and vision-only positions an image-reading model as a vision model alone, kept out of the text exams. |
data_policy | training (prompts may be used for training), retention_days. provider.data_collection: "deny" excludes endpoints that declare training — and endpoints that declare nothing. |
live_capacity | Live serving occupancy: total concurrent request slots across all replicas serving the model, current slots in use. Integers ≥ 0; current above total is clamped, not rejected — a live counter can race. A total of 0 declares no slots and is read as no report at all, which ranks the endpoint with those that report nothing rather than behind those that are full. Read on a fast poll (below). |
image_envelope | The sizes an image endpoint draws inside: min_edge, max_edge, edge_multiple and max_megapixels, read together. An endpoint declaring an image output and no envelope takes no render request, rather than inheriting bounds nobody stated. |
video_envelope | The clips a video endpoint renders: a frame envelope read by the same rules, max_seconds, fps, frame_step and audio. |
decisions_envelope | The typed questions a decisions endpoint accepts: optional question_types, max_questions and option_token_budget. The text input context window bounds one state plus one question and its options; an engine must refuse an oversized row rather than trim the state. |
mesh_envelope | The geometry a mesh endpoint builds: max_faces and min_faces, the ceiling and the floor it decimates to, and texture, whether it bakes one. A floor as well as a ceiling because a decimator handed a target below the surface it can close lands above the target in silence. An endpoint declaring a mesh output and no envelope takes no generation request, rather than inheriting bounds nobody stated. |
speech_envelope | The voices and formats a speech endpoint answers to, and the min_speed and max_speed it honours, declared together or not at all; an endpoint declaring neither speaks at its own pace. Where it is absent, the same facts are read from the speech output's own descriptors: its voice enum, its response_format (or output_format) enum and its speed range. |
default_parameters | The sampling a request naming none is served under, published so a caller can read what these weights are meant to be run at: temperature, top_p, top_k and repetition_penalty, each optional and each within the range the API accepts for it. Any other member is dropped rather than refused, so a document carrying the frequency_penalty and presence_penalty another marketplace publishes here keeps its listing. Declaring the object at all is a declaration, and the repository is not read for the members it leaves out. |
Three rules matter more than the rest:
- The author in
idis the creator of the weights. If you serve a modified rebuild, give it its own slug under the original author and pointheimdall.base_modelat the original. Where two rebuilds of one base are both listed, each slug names its own rebuild, so no two sets of weights share an id. - Prices are per unit, as decimal strings, on the modality that meters them. A unit you consume but did not price is never billed at zero — it is recorded as unpriced and flagged for review, into nobody's favour.
- Declare only what is true.
supported_parameters,complianceandheimdall.data_policyare routing inputs for customers; measured behaviour (below) sits next to your declarations on the marketplace. supported_parametersis a promise, not a wish list. Declaringtoolson an engine started without tool calling does not win you the traffic — it wins you the traffic and then the failures, because every agent request reaching that endpoint is rejected and every rejection counts against your uptime. Omitting a parameter costs you only the requests that use it. The same cuts the other way for thinking models: an engine serving a thinking checkpoint without its reasoning parser (vLLM:--reasoning-parser) returns deliberation as prose inside the answer, so there is noreasoningchannel to declare — start the parser. If the model exposes effort control, declarereasoning_effortas anenumof the levels you serve; per-level checks verify each value separately. An enum promises only its listed values; omitting the domain does not declare support for every API effort.max_context_lengthandmax_lengthboth steer routing. A request asking for more output than you declare — or more than your window holds, if you declare no ceiling — goes to an endpoint that can serve it. A request that fits your ceiling but not beside its own prompt is trimmed to the room you have rather than refused, because an engine reserves the completion budget up front. Declaringmax_lengthequal tomax_context_lengthis therefore honest but optimistic: it is only reachable with an empty prompt.
What Hugging Face fills in for you
Almost every checkpoint served here is published on huggingface.co with its description already
written: the repository states the window its weights were trained for, the width they are stored
at, the day it was created, the labels its card carries and the prose it opens with. So declare
hugging_face_id and leave the rest of these fields out. Each one you omit is read off that
repository on the pull, and each one you declare is kept exactly as you declared it. The reading
happens on the served body, before the document is parsed, so a filled value passes exactly the
validation a declared one does.
The context reader uses the language model’s max_position_embeddings at the root or under
text_config, llm_config, lm_config, thinker_config or language_config. Audio encoder
position limits are not text context windows.
All source paths below are relative to https://huggingface.co; {repo} is the exact
case-sensitive repository id, including its owner.
| Field | Read from | Source path |
|---|---|---|
name | The repository id as the Hub spells it, as owner: Repo-Name; for a copy of a declared base, the base's id (below) | GET /api/models/{repo} → id; for a copy of a declared base, GET /api/models/{base} → id |
description | The prose the model card opens with, up to 600 characters. Headings, badges, images and code are not prose and are never read as a description | GET /{repo}/raw/main/README.md → opening prose |
created | The day the repository was created on the Hub, which is the earliest its weights can have been released | GET /api/models/{repo} → createdAt |
input_modalities[].supported_inputs.max_context_length | max_position_embeddings in config.json, less any positions the architecture reserves, written onto the text input where no input of yours declares supported_inputs. A rope_scaling block changes nothing: the published figure already counts the scaling it declares, which is the window a serving engine loads the checkpoint at | GET /{repo}/resolve/main/config.json → architecture context settings |
input_modalities, output_modalities | Specific task/interface evidence checked against recognized configuration tasks, plus observed text/image/video/audio inputs. Each array is filled independently; generic feature extraction alone does not establish an embedding endpoint. Explicit arrays retain precedence. | GET /api/models/{repo} → pipeline_tag, library_name, cardData.library_name, tags, cardData.model-index[].results[].task.type; config.json → recognized architecture, model-type and tower keys; chat template → input parts |
output_modalities[].supported_parameters | Missing tools and tool_choice members, merged into an existing text-output parameter map. Supplied descriptors and explicit disabled parameters take precedence. Other parameters remain yours to declare. | Embedded and named chat templates, tokenizer configuration and recognized native prompt encoders |
output_modalities[].supported_parameters.dimensions | The truncated widths an embedding checkpoint publishes, written as an enum of those widths, defaulting to the widest, on an embeddings output that declares no dimensions. A list holding anything but positive integers fills nothing | GET /{repo}/resolve/main/config.json → matryoshka_dimensions |
output_modalities[].supported_parameters.language | The languages a transcription or speech checkpoint serves, written as an enum on a transcription or speech output that declares no language. The configuration's own list answers first; the card's general language list answers where it names none | GET /{repo}/resolve/main/config.json → support_languages; GET /api/models/{repo} → cardData.language |
heimdall.tool_call_format | The "deepseek-dsml" format where the checkpoint publishes a recognized DSML prompt encoder, so native tool blocks return as standard tool_calls. Any other checkpoint fills nothing and the format stays "openai". The encoder is read as text and never executed | GET /api/models/{repo} → siblings[].rfilename; GET /{repo}/resolve/main/encoding/encoding*.py |
heimdall.default_parameters | generation_config.json in the repository, which is where an author records the sampling they recommend for these weights: temperature, top_p, top_k and repetition_penalty, each read only where the value is one the API accepts. A config declaring do_sample: false recommends greedy decoding and states no sampling value at all | GET /{repo}/resolve/main/generation_config.json |
quantization | The quantization_config or MLX quantization block in config.json; otherwise, a root dtype unless the repository lists only GGUF weights; otherwise, an explicit format in the repository name (…-FP8, …-w4a16). Unsupported formats stay undeclared. | GET /{repo}/resolve/main/config.json → quantization_config, quantization, dtype or torch_dtype; GET /api/models/{repo} → siblings[].rfilename; repository id fallback |
heimdall.tags | The card's task tag and plain labels, plus tools and reasoning where the published text-generation interface establishes them, up to 12. Explicit tags take precedence; library and namespaced tags are omitted | GET /api/models/{repo} → tags, pipeline_tag; inspected chat template and thinking tokens |
heimdall.license | license on the model card, an SPDX id for most repositories. A card publishing under other names the real licence in license_name, and that is the answer; a listing with no card block answers from its license:* tag | GET /api/models/{repo} → cardData.license, cardData.license_name, tags |
heimdall.parameter_count | The element count summed over every tensor in the safetensors index | GET /api/models/{repo} → safetensors.total |
heimdall.weights_dtype | The dtype holding most of those parameters, as safetensors names it (BF16, F8_E4M3, I8). A checkpoint quantised to int8 keeps its embeddings and norms in BF16, so the majority answers and the minority is the parts left alone | GET /api/models/{repo} → safetensors.parameters |
heimdall.base_model | The base the model card names, in its metadata or its base_model:* tag, and only where that repository is itself a model listed here or a family root the catalogue already records | GET /api/models/{repo} → cardData.base_model, tags; existing marketplace families |
A filled name names the model, not whoever packaged it. Where heimdall.base_model_hugging_face_id
names a repository other than the one you load, and the loaded repository's name is the base's name
plus nothing but precision or packaging tokens (unsloth/gemma-4-31B-it-GGUF,
RedHatAI/Qwen3.8-27B-INT4, or Comfy-Org/HiDream-O1-Image for HiDream-ai/HiDream-O1-Image), the
name is the base's id as the Hub spells it. Any other word in the name, such as -abliterated,
-Instruct or an unrecognised build label, marks different weights, and the entry keeps the loaded
repository's name. So does an entry whose declared base has not yet been read or has no readable listing; the pull's notice says which repository the name came from.
A read that fails for a reason that says nothing about the repository (408, 425, 429 or 5xx, a
timeout or a dropped connection) never replaces what that file last answered. That holds for the
listing, config.json, generation_config.json, the model card and the chat-template lookup, which
counts as failed when any file it tried failed that way and it found no tool template or DSML prompt encoder. Each keeps
filling every field it filled, the name included, for as long as the Hub keeps failing, and the
repository is read again every 30 minutes meanwhile. Any other answer replaces it: a file read, any
other 4xx, or a body that does not parse. On the listing, a 401 or 404 records that there is no public
repository, and any other answer leaves the repository's availability unknown.
Recognized audio-codec, diarization and forced-alignment configurations retain their known inputs but do not receive inferred embedding or transcription outputs. If the task remains unknown or conflicts with the broad Hub label, the entry needs an explicit declaration of the missing endpoint modalities. An unreadable ASR configuration leaves its output unknown and retries after 30 minutes. These metadata facts do not prove that an engine serves an inference API.
Five rules govern it:
- What you declare wins. A field your document carries is never read, never compared and never
replaced. An explicit
nullis a declaration too:"quantization": nullsays these weights are stored full width, and the fill leaves it alone. The modality arrays and theheimdallkey are read the same way: declared modality types and existing descriptors are preserved, and aheimdallyou declare has only the members you left out filled in beside the ones you wrote. - Absence stays absence. A field the repository does not state is left empty and shown as unknown: a model with no licence stated is not unlicensed, and weights no index counts do not hold zero parameters. Nothing is guessed from a model name. If your linked weights have no readable tool channel, an explicitly linked base repository can supply that channel; a readable loaded template takes precedence.
- Lookups are bounded and best effort. A pull starts at most 10 repository readings while its 20-second lookup budget remains; each HTTP request has an 8-second timeout. Available facts can still fill when other files are gated or unavailable. Your dashboard records the filled fields and unresolved task evidence. An entry missing required fields after the fill is refused.
quantizationis about the stored weights. The quantisation block where the config declares one, thedtypeit is saved in otherwise, and a width the repository name states where the config answers neither — all three describe the weights as published, not what your engine holds them as. If you quantise on the way in, declare it.- Inferred tools are checked against your endpoint. Missing
toolsandtool_choicemembers inherit the published tool interface. Capability probes and demanded-call fidelity can withhold tools if the engine fails to serve them. Prose undertool_choice: "auto"is permitted and does not refute support.reasoning_effort,response_formatandstructured_outputsstay explicit.
If your engine cannot serve tools, set "heimdall": { "disabled_parameters": ["tools"] }.
This suppresses both inferred tool members and the inferred tools tag. Disabling only "tool_choice"
leaves tool inference available. Do not also declare a disabled name in supported_parameters.
heimdall.upstream_dialect names the wire your engine reads, which decides how a request is spelled on
the way to it and how its reply is read back. It accepts "openai" and "dynamo". Declare nothing and
your endpoint is read as the reference front end, which is what a base URL reached the way the OpenAI
SDK reaches one describes; that wire forwards each member under its own name, and forwards a knob you
name in supported_parameters under that name too.
Declare "dynamo" for an NVIDIA Dynamo deployment. That wire takes the reference request plus the
sampling extras its worker reads at the top level — top_k, min_p, repetition_penalty,
min_tokens, ignore_eos, the guided_* family, chat_template_kwargs, thinking and nvext — and
its validator answers anything else with 400. The marketplace refuses such a member at its own
boundary rather than spending one of your generations to learn the same thing, so naming a knob in
supported_parameters that this wire has no reading of does not carry it: declare it as a passthrough
parameter instead, and it arrives inside nvext.
For speech, Dynamo reads input, voice, response_format, speed, instructions,
max_new_tokens and user at the top level and the seed inside nvext, and it captures any other
top-level member for the worker, so on that rail a declared knob and a declared passthrough parameter
both arrive at the top level. A voice sample reaches your worker as ref_audio, a data URL, and
ref_text under the Base task, which the marketplace writes from the request's input_references
item. The request always asks for the audio itself (data_source), never for a path to a file. Declare
your speech output's voice, response_format and speed parameters so routing can match a request
to the voices, codecs and rates you serve. The health check asks inside that declaration: one fixed
sentence as a WAVE where you declare one, otherwise as the first of mp3, flac, opus, aac and the
raw encodings you declare, and in your first declared voice unless one is spelled default. A reply that
is not audio in that codec fails the check.
Recognized DeepSeek DSML encoders automatically select heimdall.tool_call_format: "deepseek-dsml".
Native tool calls are then returned as standard OpenAI tool_calls, including streaming and tool-result
conversations. Existing structured calls pass through. An explicit "openai" value overrides this
normalization. Repository code is read as text and never executed.
weights_dtype is not quantization. quantization is the format a buyer filters on (fp8,
int4); weights_dtype is the storage width the safetensors index reports, so a build packing
4-bit values into int32 containers reads I32 there and declares its own format here.
base_model has one condition of its own: it is filled only where the base the card names is a
model listed here or a family root the catalogue already records, because grouping your listing
under an id that names nothing would publish a family page with one member and no root. The match
ignores case, since the casing a card states its base under and the casing you typed are two
spellings of one repository, and a model is never made a variant of itself.
The repository is taken from hugging_face_id. A link to the repository's page is read the same
way, in hugging_face_id or in a field that names the model's own page (url, model_url,
weights_url, repository, repo) — never out of prose, where a link usually names the model a
build came from rather than the build.
A repository is read once for the whole marketplace and the reading is kept for a week, so two providers serving the same weights cost the Hub one lookup. A pull that meets several repositories it has no reading for reads a few of them and fills the rest on later pulls; the dashboard names the entries that are waiting.
Pull mechanics and lifecycle
- Every pull is recorded — HTTP status, outcome, a hash of the body, and each finding with the entry and field it concerns. The dashboard shows the runs.
- Withdrawal follows your document. Every endpoint no valid entry of a successfully read
document lists is withdrawn — an entry omitted and an entry that fails validation alike (history
kept, stats intact — a later pull whose entry validates revives the same endpoint through
pending_health). A live endpoint is withdrawn on the second consecutive such pull, the first counting as oneis_ready: falsereading. A document whose entries all fail takes your endpoints out until the next pull that validates. An unreachable, unauthorised or refused source withdraws nothing: your endpoints stay up on the strength of their health probes, so an outage of your models URL never delists you. - Your URLs must resolve to public addresses. Both your models URL and your base URL are reached
from inside the marketplace's own network, so an address that is private, loopback, link-local or
reserved is refused at connect time and at every redirect hop. A refused models URL ends the run
blocked_addressand withdraws nothing. A refused base URL means requests routed to your endpoints cannot be served: each is recorded asblocked_address, which is charged to nobody's uptime, and your dashboard says so. - Shared models, one metadata owner. When several providers list the same id, the model's display metadata comes from its canonical endpoint; values other providers disagree on are reported as warnings, and an administrator can pin the metadata.
- Manual pulls from the dashboard are rate-limited to one per source per minute.
- Capacity rides a faster clock. Besides the full pull, Heimdallm reads your models URL about
every 15 seconds and takes only
heimdall.live_capacityfrom it. Fresh reports steer routing: endpoints with known free slots are tried first, saturated ones last (never excluded — when everything is full, requests still route). A report older than about a minute counts as no report, so an outage of your models URL costs you the boost, never your listing. Keepcurrentlive — an honest counter around your serving path, not a constant.
What Heimdallm measures
Declared fields come from you; performance never does. From real customer traffic and periodic probes, per endpoint:
| Figure | How |
|---|---|
| Uptime | Success rate over 1-day and 7-day windows, counting successes and your own errors; aborted callers, marketplace faults and callers' bad requests count on neither side. |
| Health tier | From your newest 20 attempts, not a clock window: healthy ≥ 90% of them, degraded ≥ 60%, down below, unknown under 5 attempts. Two consecutive probe failures short-circuit to degraded and four to down. Routing prefers healthier tiers. |
| Time to first token | p50/p75/p90/p99 over 30 minutes, in seconds. |
| Throughput | Completion tokens per second during generation, same percentiles. |
| Tool-call success | Share of tool-requesting generations that produced a tool call, over 2 hours. |
| Benchmarks | Health checks run about every 5 minutes and benchmark items about every 15 minutes per endpoint. Probes are ordinary paid requests: they count in public usage figures and in your earnings. |
Each figure stays absent until its window holds enough samples — a number built on three requests would be noise presented as measurement.
Earnings, statements and getting paid
- Recorded per generation. Every request you serve — Heimdallm's own probes included — records your earnings at your listed price: the gross, the 10% platform share, and your net. Your dashboard shows the running month and a per-model breakdown, as exact decimal strings.
- Monthly statements. Each UTC calendar month closes into a statement — gross, platform share, net, generation count. A statement claims every earning of yours that no statement has claimed yet, so an earning that settles into a month after that month's statement was already paid is picked up by the next statement rather than lost: the statement paying it names its own month, and the earning's settlement date still names the month it was earned in. Your earnings view splits each month by the statement claiming each part of it, and names the part no statement has claimed yet. Re-computation can refresh an unpaid statement; a paid one is never rewritten, and neither are the earnings it counted.
- Payouts are USDC on Ethereum mainnet, to a deposit-proven wallet. The destination must be a wallet linked to your account that has made at least one credited deposit: bind the wallet and deposit from it on your account's Credits tab, then select it as your payout address in the provider dashboard's settings. An administrator settles the statement and marks it paid with the transaction hash, which then shows on the statement — the statement is the invoice, the hash is the receipt. A statement cannot be marked paid while no qualifying address is configured.
- The address must exist on Ethereum. Payouts are sent on Ethereum mainnet, no matter which network your qualifying deposit arrived on — deposits are accepted on several, payouts go out on one. A contract wallet (a Safe, for example) exists only on the networks it was deployed to, so one deployed on the network you deposited from but not on Ethereum does not exist at that address here and will not receive them — use an address you control on Ethereum.
Customers pay no deposit fees, so your listed price is exactly what the market compares.
Next
- The provider dashboard — the surface this contract is driven from: sources, pulls, earnings, statements, payout settings.
- Operating a source — pull outcomes, health, capacity and the common findings mapped to fixes.
- Pricing & demand — how price steers routing and how listings grow.
- A worked example — seven models over chat, vision, embeddings, transcription, images and video, served through the reference provider service, and what changes once there is more than one box.
Generated video sound
Declare generate_audio on the video output in supported_parameters, using type: boolean.
Publish default only when the engine uses it. Each admitted explicit value is a routing promise: true must produce generated sound and false must produce silent
video. Sound-capable endpoints also set heimdall.video_envelope.audio: true.
The video reply reports mime_type (video/mp4 or video/webm) and has_audio for the actual file.
An audio selection is frozen with dispatch and the selected tariff. Bundled audio/video generation
is billed by the declared output-video rate; an audio toggle does not create an extra audio charge.
Supplied audio references and audio-only synthesis require their own supported interface.