Integration guides ยท 2026-08-29
Connecting OpenStory to the LLMTR gateway with a team key
How the OpenStory LLMTR branch resolves per-team keys, maps registry ids onto catalogue ids, widens the adapter model union and accounts for spend from token counts, plus the modalities it leaves untouched.
Scope of the branch: which calls reach the gateway
OpenStory produces images and video, but the 1265-llmtr-gateway-provider branch in the knowhycodata/openstory repository is not about generation jobs. The note in the environment file draws the line clearly: once the key is set, LLM calls for the models the gateway carries go through it instead of OpenRouter. The script analysis model registry is where those calls originate.
The technical basis is that LLMTR's wire format and its model listing payload match what the existing OpenRouter adapter expects. No new adapter is written; the same adapter is driven with a swapped server address. The base URL is https://llmtr.com/v1, and the version suffix is deliberate because the SDK appends its paths onto that value.
The narrow scope is a decision rather than an omission. Media job ids are already scoped to whoever they were submitted to, so polling stays pinned to that provider; changing the route of text calls does not disturb it.
Where the key goes: team key and platform key
There are two entry points. The first is the settings area for teams bringing their own key, where LLMTR sits alongside the other providers. The second is an environment variable that applies across the deployment. Work belonging to a team with no key of its own falls back to it.
The distinction matters in a multi-team install. A team adding a key decides not only who is billed but which door its calls leave through. The end-to-end suite never sets the variable, so existing fixtures keep exercising the previous path and adding the gateway does not move those expectations.
On the LLMTR side keys carry a fixed prefix and travel in the authorization header. The block below shows the deployment-wide variable; for a team key the same value is entered in the settings field.
The deployment-wide variable and the team key equivalent
# Fallback key for the whole deployment
LLMTR_API_KEY=llmtr-your_key
# A team key is entered in the interface:
# Settings -> API Keys -> LLMTR
# Either way the request carries it as:
# Authorization: Bearer llmtr-your_key
Resolution order: where the team key takes over
Key resolution lives in one function and runs in order. The vendor's own interface keeps priority for the Grok family. Next comes the team's LLMTR key whenever the gateway carries the selected model, ahead of that same team's OpenRouter key. The branch states the reasoning plainly: a team that adds an LLMTR key chose that gateway deliberately.
The load-bearing detail is that the condition holds in two places at once. Key resolution and adapter creation ask the same question about whether the id is routable. If the two disagree, the key belongs to one door and the request goes to another. The branch flags this as a trap on purpose.
When the model is not carried, the LLMTR key is skipped and the call continues along its previous path. Adding the gateway therefore breaks nothing it does not cover.
| Order | Key used | Selected when |
|---|---|---|
| 1 | The vendor's own interface | The model is in the Grok family and a vendor key exists |
| 2 | The team's LLMTR key | The model id has a match in the gateway map |
| 3 | The team's OpenRouter key | The team has no LLMTR key, or the model is not carried |
| 4 | The team's fal key | Neither of the previous team keys is present |
| 5 | Platform variables | The team holds no key at all, in the same order of preference |
The routability map and namespace drift
The pivotal file in the branch maps a registry model id onto the id LLMTR publishes. It exists because namespaces differ: the same vendor can be spelled with a different prefix in two catalogues. The map spells every equivalence out one by one rather than inventing a pattern rule.
An id absent from the map is not routable. The code treats that as a routing decision rather than an error, and the call keeps its previous path. Falling back to a near neighbour was rejected deliberately, because quietly substituting a model the caller never asked for is the most expensive kind of mistake. The three ids the branch says are not carried have no match in the public catalogue today, and for one of them the vendor itself is absent from it.
The four renamed ids are collected in a separate list, because the model union generated for the OpenRouter adapter does not recognise those names and the factory is widened with exactly them. That widening has a cost: a renamed id necessarily loses the other catalogue's per-model metadata, including the fast path that combines tools and schema in a single call.
- Vendor prefixes can differ between catalogues; the map pairs them by hand.
- An id missing from the map never reaches the gateway; it stays with the previous provider.
- Renamed ids widen the adapter's generated model union.
- Those widened ids carry none of the other catalogue's per-model metadata.
Verifying the spelling before adding an id to the map
# The catalogue is readable without a key
curl -s https://llmtr.com/v1/models | jq -r '.data[].id' > ids.txt
# Look for the exact spelling of the id you want to add
grep -x 'zai/glm-5.2' ids.txt
grep -x 'mistral/mistral-small-latest' ids.txt
# When a prefix difference is suspected, list by vendor
grep -E '^(xai|zai|mistral)/' ids.txt | head
Spend accounting and key validation
The second technical topic is cost recording. LLMTR responses carry token counts but no ready-made per-request amount. Accounting that reads only such a field would record these calls as zero, so the branch prices the call from the route it resolved and keeps its own rate table. The table is transcribed from the catalogue rather than estimated, and the branch notes openly that these rows have not been reconciled against an invoice.
The pricing logic is consistent with how LLMTR bills: catalogue rates carry no per-model markup, and the 8 percent margin applies only to credit top-ups. A rate table kept on your side therefore tracks the vendor list prices, and it has to be re-read whenever the catalogue changes or a vendor re-prices.
Key validation is a separate trap. The model listing route is public and succeeds for an invalid key, so it cannot validate anything. The branch sends a single-token completion instead, on one of the free catalogue rows; the row it picks is qwen/qwen3.6-27b-free, which we confirmed is live in the public catalogue.
The modalities this branch does not route
The scope note from the opening becomes concrete here. The map covers text models only; image, video, embedding and rerank work keeps its existing path. The public LLMTR catalogue does publish live rows bound to those operations, but wiring them into OpenStory's production pipeline is not what this branch does and was not verified in this version.
Keeping the boundary intact is what leaves job polling and generation billing untouched. Routing the image or video side through the gateway as well would mean building the same kind of map for that modality, matching the request body of the relevant endpoint, and solving cost recording separately.
| Modality | Routed to the gateway here | Basis |
|---|---|---|
| Text calls | Yes, for ids present in the map | The environment note and the model map |
| Image generation | No | The map lists text models only |
| Video generation | No | Media jobs stay with the provider they were submitted to |
| Embeddings and rerank | No | This branch defines no matching mapping |
Frequently asked questions
If a team holds both an OpenRouter and an LLMTR key, which one is used?
The LLMTR key wins whenever the selected model has a match in the gateway map, which the branch defines as a deliberate precedence: adding the key is treated as choosing that door. When the model is not in the map, the call proceeds on the team's OpenRouter key along its previous path.
Does selecting a model missing from the map make the call fail?
No. An id with no match is not considered routable, key resolution skips the gateway, and the request goes to its previous provider. The code refuses to fall back to a nearby model, because quietly substituting one hides a wrong result rather than reporting it.
Are image and video jobs billed through this key too?
Not in this branch. The change moves the route of text calls; generation jobs stay with the provider they were submitted to and their records follow from there. The catalogue does carry live rows bound to image and video operations, but this branch does not route them.
Why is the key not validated against the model list?
The model listing route is public and returns success for an invalid key, so it can validate nothing. The branch sends a single-token completion against a free catalogue row instead and reads the status of that response.