Skip to content
Heimdallm
Provider console/Integration guide
Documentation · For providers

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

  1. 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.
  2. 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_url and ends in /v1: inference is sent to <base URL>/chat/completions, <base URL>/embeddings, <base URL>/rerank, <base URL>/systemone, <base URL>/videos/generations and the other rails your models declare, and your model list is fetched from <base URL>/models unless 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.
  3. 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.
  4. 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.

FieldRequiredMeaning
schema_versionyes"2.4".
idyesauthor/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.
nameyes*Display name, conventionally Author: Model Name.
createdyes*The model's release date, Unix seconds. At most 2147483647, so a stamp in milliseconds is refused. A listed model carries one.
input_modalitiesyes*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_modalitiesyes*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_idnoThis 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.
descriptionno*Shown on the model page.
quantizationno*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.
tokenizernoThe tokenizer family (GPT, Claude, Llama3), which describes the whole model.
pricingnoRequest-scoped prices only: request per request (pricing entries). Every token, media and character rate lives on the modality that meters it.
capacitynoRequest-scoped limits: request, web_search and concurrency (capacity entries). A declared ceiling, not the live occupancy report, which is heimdall.live_capacity.
passthrough_parametersnoProvider-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_readynofalse 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_freenoMarks 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_usernoThe 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_dateno2027-01-31 or 2027-01-31T15:00:00Z, after which the endpoint is withdrawn.
datacentersnoWhere 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_regionnoA provider label for a deployment served from several regions under distinct model ids. Stored with the endpoint; not published or used to filter routing.
compliancenozdr (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.slugnoThe slug this model carries on the other marketplace; accepted so one document serves both, and read by nothing here.
heimdallnoHeimdallm'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.

FieldRequiredMeaning
typeyestext, image, video, audio or file.
supported_inputsno*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.
pricingnoWhat this input bills: prompt, cached_prompt or cache_write, per token, image, megapixel, second or character (pricing entries).
capacitynoDeclared limits on the same types, per minute, hour or day (capacity entries).
passthrough_parametersnoProvider-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

FieldRequiredMeaning
typeyestext, image, video, speech, transcription, embeddings, rerank, decisions, audio or mesh.
supported_parametersyesThe 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_lengthnotext only: the output ceiling, { "value": …, "unit": "token" }.
streamingnoWhether 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.
pricingnoWhat 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.
capacitynoDeclared limits on the same types per window, plus concurrency (capacity entries).
passthrough_parametersnoProvider-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.

EntryWhereMeaning
prompt per tokenthe text inputUSD 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 imagethe image inputUSD 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 tokenthe text inputUSD 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 tokenthe text inputUSD per prompt token written to cache, with the same qualifiers.
completion per tokenthe text outputUSD per completion token.
internal_reasoning per tokenthe text outputUSD per reasoning token, when priced apart from completion.
completion per tokenthe audio outputUSD 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 requestthe entry rootA flat price per request.
completion per imagean image outputUSD per delivered image, with explicit overrides for supported settings.
completion per megapixelan image outputUSD per 1,000,000 pixels of delivered image area; fractional megapixels are retained.
completion per seconda video outputUSD per second of delivered video, including fractional seconds.
completion per mesha mesh outputUSD 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 tokenan image outputPublished as an image-output token tariff. Dedicated media routes lack a finite image-token reservation bound and refuse positive rates before dispatch.
prompt per secondthe audio inputUSD 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 characterthe speech outputUSD 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.

KindShapeMeaning
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.

FieldMeaning
serving_idThe 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_idThe 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.
licenseThe licence the weights are published under, as an SPDX id where one covers it. An explicit null declares it unknown.
parameter_countParameters the weights hold, as an integer.
weights_dtypeThe 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.
tagsUp 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_policytraining (prompts may be used for training), retention_days. provider.data_collection: "deny" excludes endpoints that declare training — and endpoints that declare nothing.
live_capacityLive 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_envelopeThe 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_envelopeThe clips a video endpoint renders: a frame envelope read by the same rules, max_seconds, fps, frame_step and audio.
decisions_envelopeThe 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_envelopeThe 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_envelopeThe 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_parametersThe 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 id is the creator of the weights. If you serve a modified rebuild, give it its own slug under the original author and point heimdall.base_model at 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, compliance and heimdall.data_policy are routing inputs for customers; measured behaviour (below) sits next to your declarations on the marketplace.
  • supported_parameters is a promise, not a wish list. Declaring tools on 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 no reasoning channel to declare — start the parser. If the model exposes effort control, declare reasoning_effort as an enum of 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_length and max_length both 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. Declaring max_length equal to max_context_length is 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.

FieldRead fromSource path
nameThe 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
descriptionThe prose the model card opens with, up to 600 characters. Headings, badges, images and code are not prose and are never read as a descriptionGET /{repo}/raw/main/README.md → opening prose
createdThe day the repository was created on the Hub, which is the earliest its weights can have been releasedGET /api/models/{repo} → createdAt
input_modalities[].supported_inputs.max_context_lengthmax_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 atGET /{repo}/resolve/main/config.json → architecture context settings
input_modalities, output_modalitiesSpecific 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_parametersMissing 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.dimensionsThe 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 nothingGET /{repo}/resolve/main/config.json → matryoshka_dimensions
output_modalities[].supported_parameters.languageThe 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 noneGET /{repo}/resolve/main/config.json → support_languages; GET /api/models/{repo} → cardData.language
heimdall.tool_call_formatThe "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 executedGET /api/models/{repo} → siblings[].rfilename; GET /{repo}/resolve/main/encoding/encoding*.py
heimdall.default_parametersgeneration_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 allGET /{repo}/resolve/main/generation_config.json
quantizationThe 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.tagsThe 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 omittedGET /api/models/{repo} → tags, pipeline_tag; inspected chat template and thinking tokens
heimdall.licenselicense 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:* tagGET /api/models/{repo} → cardData.license, cardData.license_name, tags
heimdall.parameter_countThe element count summed over every tensor in the safetensors indexGET /api/models/{repo} → safetensors.total
heimdall.weights_dtypeThe 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 aloneGET /api/models/{repo} → safetensors.parameters
heimdall.base_modelThe 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 recordsGET /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 null is a declaration too: "quantization": null says these weights are stored full width, and the fill leaves it alone. The modality arrays and the heimdall key are read the same way: declared modality types and existing descriptors are preserved, and a heimdall you 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.
  • quantization is about the stored weights. The quantisation block where the config declares one, the dtype it 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 tools and tool_choice members inherit the published tool interface. Capability probes and demanded-call fidelity can withhold tools if the engine fails to serve them. Prose under tool_choice: "auto" is permitted and does not refute support. reasoning_effort, response_format and structured_outputs stay 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 one is_ready: false reading. 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_address and withdraws nothing. A refused base URL means requests routed to your endpoints cannot be served: each is recorded as blocked_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_capacity from 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. Keep current live — 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:

FigureHow
UptimeSuccess 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 tierFrom 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 tokenp50/p75/p90/p99 over 30 minutes, in seconds.
ThroughputCompletion tokens per second during generation, same percentiles.
Tool-call successShare of tool-requesting generations that produced a tool call, over 2 hours.
BenchmarksHealth 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.

SourcesThe model-list documents the marketplace pulls on a scheduleModelsEvery endpoint your documents publish, its status and its pricesHealth & errorsEvery request put to your endpoints and the figures they add up toRoutingThe requests your endpoints did not receive, and the rule that kept them awayTipsWhat the marketplace reads off your deployment that you can act onEarningsYour share of the traffic you served, by month and by statementCatalogue discountOffer one discount across every model in your catalogueMembersWho can operate this providerSettingsPayout wallets and the provider profilePerformance metricsProvider observability and monitoringIntegration guideConnect and operate inference capacity