Provider endpoints, and what each entry rests on
Warning
Some of the steps on this page are performed in a product this project does not control. Any screen, menu, or field named here can be renamed or moved there at any time. Where this page and that product's own documentation disagree, the product's documentation is right.
MailFathom reaches a model through one client construction that speaks the OpenAI wire protocol, so pointing a deployment at a service is a configuration entry rather than a feature request. Embedding generation § an endpoint is any service that speaks the OpenAI wire protocol holds the mechanism. What the mechanism cannot supply is the thing an operator actually needs before they write the entry: whether this service, today, serves the route the role needs, at which address, against which credential, and whether it honours the vector width the declaration asks for.
"It speaks the OpenAI wire protocol" does not answer any of those. A service can serve chat completions and no
embeddings route at all; can serve both and reject the dimensions parameter an embedding declaration may send; can
name that parameter something else; can serve the responses API on one model family and not another. This page is the
register of what was checked, entry by entry, with what each check rests on.
Two claims this page does not make
Presence is a check at a point in time, not a supported-provider list. Every entry below is what a third party's own current documentation said on the date the entry carries, or what a call through this project's own adapter established. None of these services is under this project's control, and any of them may change the answer next week without anybody here touching anything. An entry that stops being true is a defect in this page rather than in the deployment that trusted it.
Absence is not a refusal. A service missing from this page is not blocked, unsupported, or known to fail — it is unchecked. The mechanism reaches any service that speaks the protocol, and an operator who points a deployment at one is doing an ordinary thing. What they do not get is this page's word for it.
Neither claim is a review of the service's terms, and this page is not the place one is recorded. MailFathom itself
calls two of these — the two its own deployment may be pointed at without an operator choosing anything further — and
THIRD_PARTY_LICENSES.md reviews those
two under Hosted services, with what each one retains and what the operator has to hold to send mail text to it. Every
other entry below is named rather than adopted, which is exactly why it has no row there.
Declaring any of them sends mail content out of the deployment. An embedding endpoint receives the prepared passage text of the mail this instance holds; a chat endpoint receives the question a caller asked together with the passages retrieved to answer it. That is personal data of the operator's own correspondents, and choosing where it goes is the decision this page exists to inform rather than one it makes. What leaves your instance when you ask states it from the reader's side, and a server the operator runs themselves is the entry that answers it differently.
What "checked" means here
Each entry says which of two kinds of evidence it rests on, because they are not the same claim:
- Called — a request went through MailFathom's own adapter to the real service and the answer was what the port publishes. The provider-contract tests are the instrument, and how to exercise an entry yourself below is how one is run against any address. It carries no date, because the tests are run on request rather than on a schedule and the last run is what a pipeline record says rather than what this page could.
- Documented — the service's own current documentation was read on the date the entry carries, and nothing was called. It establishes that the route exists and what it accepts; it does not establish that MailFathom's request is the shape that route accepts.
Nothing here is inferred from a model's name, from another service serving the same open-weights model, or from a compatibility claim on a landing page.
What an entry has to establish
Five things, because those are what a declaration writes and what a first call fails on:
| What | Why it decides the entry |
|---|---|
| Which roles it serves | Embeddings and chat are separate declarations reaching separate routes, and a compatibility layer commonly serves one and not the other |
| The address | Address carries the whole base path the service documents, version segment included; empty means the provider library's own default, which is first-party OpenAI |
| The credential shape | Exactly one of ApiKey, EntraCredential, and Unauthenticated is declared, and a credential over a plain http address is refused at startup |
| Whether a requested width is honoured | SupportsRequestedDimension defaults to true, so an endpoint that ignores or rejects dimensions needs it written false |
| Which chat API it serves | Chat:Api names ChatCompletions or Responses, and a service serving only one refuses the other as request refused |
Configuration reference § Embeddings and § Chat
are the inventory of every key named here.
Vendor APIs with a compatibility layer
| Service | Roles | Address |
Credential | SupportsRequestedDimension |
Evidence |
|---|---|---|---|---|---|
| OpenAI | embeddings, chat | (empty — the library's default) | ApiKey |
true |
Called, through the provider-contract tests |
| Azure OpenAI | embeddings, chat | the resource's own address ending /openai/v1/ |
ApiKey or EntraCredential |
true |
Called, through the provider-contract tests |
| Google Gemini | embeddings, chat | https://generativelanguage.googleapis.com/v1beta/openai/ |
ApiKey |
false |
Documented, 2026-08-12 |
| Mistral | embeddings, chat | https://api.mistral.ai/v1 |
ApiKey |
false |
Documented, 2026-08-12 |
| Cohere | embeddings, chat | https://api.cohere.ai/compatibility/v1 |
ApiKey |
false |
Documented, 2026-08-12 |
| xAI | chat only | https://api.x.ai/v1 |
ApiKey |
n/a | Documented, 2026-08-12 |
Azure is not a special case in the code and is not one here: its v1 data plane is OpenAI-compatible, so a deployment is
the same client pointed at the resource with the deployment's own name as RoutedModelName. It is the one entry on the
page that takes a Microsoft Entra credential, and embedding generation § authentication has three
shapes holds the four non-interactive shapes that
covers.
Cohere is the only vendor entry that refuses the width parameter in writing: its compatibility API lists dimensions
among the parameters it does not support. Gemini's compatibility page documents an embeddings request of model and
input and nothing else. Mistral does accept a requested width, under the name output_dimension — which is not the
member MailFathom sends, so the effect is the same and the reason is worth knowing, because a reader comparing the
vendor's page against this one will otherwise think this entry is wrong.
Aggregators and hosted open models
| Service | Roles | Address |
Credential | SupportsRequestedDimension |
Evidence |
|---|---|---|---|---|---|
| Groq | chat only | https://api.groq.com/openai/v1 |
ApiKey |
n/a | Documented, 2026-08-12 |
| Together AI | embeddings, chat | https://api.together.ai/v1 |
ApiKey |
false |
Documented, 2026-08-12 |
| Fireworks AI | embeddings, chat | https://api.fireworks.ai/inference/v1 |
ApiKey |
per model | Documented, 2026-08-12 |
| DeepInfra | embeddings, chat | https://api.deepinfra.com/v1/openai |
ApiKey |
unestablished | Documented, 2026-08-12 |
| Nebius Token Factory | embeddings, chat | https://api.tokenfactory.nebius.com/v1/ |
ApiKey |
unestablished | Documented, 2026-08-12 |
| OpenRouter | embeddings, chat | https://openrouter.ai/api/v1 |
ApiKey |
unestablished | Documented, 2026-08-12 |
| Hugging Face Inference Providers | chat only | https://router.huggingface.co/v1 |
ApiKey |
n/a | Documented, 2026-08-12 |
The Hugging Face router is the entry most likely to be assumed wrong, so it is worth stating outright: the router serves embedding models, and its OpenAI-compatible surface does not. Its own documentation says the compatible endpoint is "currently available for chat completion tasks only" and directs every other task, embeddings included, at the Hugging Face inference clients — which are a different protocol and out of this mechanism's reach.
Fireworks documents dimensions as accepted but honoured only by particular models, so the value is a property of the
model an entry names rather than of the service. It also documents normalize as defaulting to false, which is a
declaration to check against NormalizeVectors rather than to assume.
"Unestablished" is not "no". It means the service's request schema, as published, does not name the parameter and no
call was made to find out. false is the safe declaration until a call says otherwise, because an endpoint sent a
parameter it does not accept refuses the request rather than ignoring it.
A model server you run yourself
These are the entries an operator can exercise without an account or a per-token invoice, and they share one shape: the
OpenAI wire protocol on a private or loopback address over plain HTTP with nothing in front of it. That shape is
declared with the plain address and "Unauthenticated": true, and embedding generation § a model server you run
yourself holds what it gains, what it gives up,
and the startup warning an instance writes about the hop.
| Server | Roles | Address shape |
Credential | SupportsRequestedDimension |
Evidence |
|---|---|---|---|---|---|
| Ollama | embeddings, chat | http://<host>:11434/v1 |
Unauthenticated |
true |
Documented, 2026-08-12 |
llama.cpp (llama-server) |
embeddings, chat | http://<host>:8080/v1 |
Unauthenticated |
unestablished | Documented, 2026-08-12 |
| vLLM | embeddings, chat | http://<host>:8000/v1 |
Unauthenticated |
per model | Documented, 2026-08-12 |
| LM Studio | embeddings, chat | http://<host>:1234/v1 |
Unauthenticated |
unestablished | Documented, 2026-08-12 |
| LocalAI | embeddings, chat | http://<host>:8080/v1 |
Unauthenticated |
true |
Documented, 2026-08-12 |
| Text Embeddings Inference | embeddings only | http://<host>:8080/v1 |
Unauthenticated |
false |
Documented, 2026-08-12 |
Each port above is what the server's own documentation shows and is the part most likely to be wrong in a given
deployment, because every one of these is routinely published on another one. Text Embeddings Inference is the entry
where that gap is already visible: its container listens on 80 and the docker run its quick tour shows maps that to
8080, so the number in the table is a mapping rather than the server's own default. The address is whatever the
operator's network says it is; the port is recorded so a reader recognises the shape rather than copies it.
Several of these accept an optional key — llama.cpp, vLLM, LM Studio, and Text Embeddings Inference each document a
flag that turns authentication on — and an entry that uses one declares ApiKey and an https address instead,
because a credential over a plain address is refused at startup. Declaring Unauthenticated against a server that does
require a key is a credential rejected, which is not repeated. Ollama is the case in between: its compatibility layer
documents a key as required by the client and ignored by the server, so Unauthenticated is the correct declaration
and the request carrying no authorization header at all is what the server expects.
vLLM is the one entry where the width parameter fails loudly rather than quietly: it accepts dimensions and returns
an error naming the model for one that was not trained for Matryoshka representation, so SupportsRequestedDimension
must follow the model an entry names, not the server. Text Embeddings Inference serves the OpenAI-compatible embeddings
route and publishes no width parameter on it at all.
llama.cpp serves the embeddings route only for a model loaded with a pooling type, and a deployment that wants both
roles from it runs two servers with two declarations rather than one — which the chain and the separate Chat section
already express.
A cloud platform whose own API is something else
AWS Bedrock and Google Vertex AI are the two entries an operator most often expects to be absent, because neither
platform's native API is this protocol: Bedrock's own is InvokeModel and Converse under SigV4, and Vertex's is
generateContent. Both nonetheless publish an OpenAI-compatible surface beside it, and on both of them the credential
that surface accepts is a bearer token — which is exactly what an ApiKey declaration presents. So each is an ordinary
entry with one operational condition attached rather than a platform out of reach.
| Service | Roles | Address |
Credential | SupportsRequestedDimension |
Evidence |
|---|---|---|---|---|---|
| AWS Bedrock | chat only | https://bedrock-mantle.<region>.api.aws/v1 |
ApiKey |
n/a | Documented, 2026-08-12 |
| Google Vertex AI | chat; embeddings unestablished | https://<location>-aiplatform.googleapis.com/v1/projects/<project>/locations/<location>/endpoints/openapi |
ApiKey |
unestablished | Documented, 2026-08-12 |
The credential on both of these expires, and that is the condition. Bedrock issues a short-term key lasting at most
twelve hours, which is the kind AWS recommends for production, and a long-term key lasting until a configured expiry,
which its own documentation marks for exploration only. A Vertex bearer is a Google Cloud access token, which for a
service account lasts an hour by default. MailFathom holds either as an ordinary secret reference and resolves it per
request, so a process beside the deployment that mints a fresh token and rewrites the file is picked up on the next
call — no restart, no configuration reload, nothing to invalidate. A systemd timer or a Kubernetes CronJob writing
the same path is the whole arrangement.
What it costs is worth reading before either entry is declared. A window in which the file holds an expired token is a credential rejected, which is deliberately not retried, and a refresher that dies silently takes the AI features down until somebody notices — mail synchronization is unaffected, which bounds it but does not remove it. Give the interval real margin against the lifetime rather than matching it, and treat the refresher as a component of the deployment.
Bedrock's address is the part most likely to be written wrong, because AWS documents two endpoints and its own pages
show more than one base path. bedrock-mantle is the one AWS recommends and the only one serving the responses API;
bedrock-runtime serves chat completions as well, at a base path its current examples write as /v1 and its guardrails
example and legacy reference write as /openai/v1. Which models each endpoint carries differs too, so the region, the
endpoint, and the routed model identifier are one choice rather than three — take the address from the endpoint the
model is actually on rather than from this row.
Neither platform is reachable for what its compatible surface does not serve, and for Bedrock that is embeddings
entirely. Titan Text Embeddings, Titan G1, Cohere Embed, Nova Multimodal Embeddings, and Marengo each answer through
Bedrock's own invocation APIs and through neither OpenAI-compatible route, so a deployment taking chat from Bedrock
declares its embedding chain against one of the entries above — which costs nothing, because the two were always
separate declarations. An operator who specifically needs a Bedrock-hosted embedding model runs a gateway that speaks
this protocol outward and InvokeModel inward, and MailFathom reaches the gateway as an ordinary entry. That gateway
sees every passage of mail text on its way past, which makes it a component to secure rather than a translation layer
to forget about.
Vertex's embeddings answer is genuinely open rather than negative, and the distinction is the reason this row says
unestablished instead of chat only. Google's REST reference does publish an openapi.embeddings method beside
chat.completions, but it documents that method around a model the operator deployed to an endpoint of their own with
invokeRoutePrefix set, while every page written about reaching Google's managed models through the OpenAI libraries
is written about chat completions. Whether a managed embedding model answers at .../endpoints/openapi/embeddings is
therefore something a call would settle and reading has not.
ADR 0011 records why MailFathom writes neither a Bedrock adapter nor a credential shape that mints Google tokens for itself, and what would change either answer.
What has no embeddings route at all
Groq, xAI, the Hugging Face router, and AWS Bedrock each serve chat and no OpenAI-compatible embeddings route. That is a
fact about the service rather than a failure, and it costs nothing structurally: the chat endpoint and the embedding
chain are separate declarations with separate aliases and separate credentials, so a deployment may take chat from one
of these and embeddings from an entry above. What it must not do is declare one of them under Embeddings:Endpoints and
wait for the first call to say so.
Which chat API an entry serves
Chat:Api defaults to ChatCompletions, which every chat-serving entry on this page documents. A Responses path is
documented by OpenAI, Azure OpenAI, xAI, Groq (in beta), Ollama, llama.cpp, LM Studio, and vLLM. The Gemini, Mistral,
Cohere, and Vertex AI compatibility layers document none, and a deployment stating Responses against one of those is
answered request refused. Every other entry above was checked for the embeddings route rather than for this path, so
its answer here is unestablished in the same sense the width column uses.
Bedrock is the entry where the two APIs are two addresses. bedrock-mantle documents both paths and bedrock-runtime
documents only chat completions, so Chat:Api and Address are one decision there rather than two independent keys.
Serving the path is not the whole of it. MailFathom conducts every responses call statelessly and asks for the reasoning content it will hand back on the next turn, for the reasons chat generation § the responses API is used statelessly gives, and a server implementing part of that surface may accept the path and refuse the request. Ollama, for one, documents its responses route as serving no stateful request at all — which is what MailFathom asks for anyway, and is why the distinction is worth reading before an entry is trusted.
The other half is capability rather than protocol. The ask_mail run offers the model function tools and the model
calls them when it decides it needs mail, so a model that cannot be given tools cannot answer here whichever API it is
reached over. That surfaces as request refused on the first question.
How to exercise an entry yourself
The provider-contract tests in tests/IntegrationTests/ProviderAdapters/ are what turn a Documented entry into a
Called one. They run MailFathom's own adapter against whatever address they are given and assert the four things
only a real service can establish: that the adapter speaks the protocol, authenticates, classifies a real refusal, and
returns an answer or a vector in the shape the port publishes.
They are skipped unless asked for, and asking for one without what it needs fails the run rather than skipping it.
MAILFATHOM_AI_CONTRACT_TESTS turns them on; the embedding half then reads MAILFATHOM_EMBEDDING_ADDRESS,
MAILFATHOM_EMBEDDING_MODEL, MAILFATHOM_EMBEDDING_DIMENSION, and MAILFATHOM_EMBEDDING_API_KEY, with
MAILFATHOM_EMBEDDING_ROUTED_MODEL where the routed name differs, and the chat half reads the corresponding
MAILFATHOM_CHAT_* variables. The address is the same string an Address key would carry, so an entry is checked
exactly as it would be deployed. Local development holds how the suite is run at all.
One limit of the instrument is worth knowing before an entry is trusted. A contract run reads the key variable as
required and presents what it reads, so it exercises the ApiKey shape and never the Unauthenticated one. Against a
server that ignores an authorization header it did not ask for — which is what a self-hosted server started without its
own key flag does — the run still establishes the protocol, the route, and the vector shape, which is what the entry
claims. What it does not establish for that entry is the credential column, and that is why every row in a model
server you run yourself reads Documented.
A run against a hosted service costs whatever that service charges for the handful of calls the tests make. It is the only evidence this page treats as stronger than reading, which is why the distinction is on every row rather than in a footnote.
What this page deliberately does not carry
Continuous verification. Nothing here runs on a schedule or in a pull-request check. A suite that re-checked every entry would need an account and a budget with each of these services, which is a different decision from writing the register, and the dates on the rows exist precisely because nothing renews them automatically.
Anything that does not speak this protocol. A service reachable only through its own SDK or its own wire format is out of the mechanism's reach entirely, and pointing a deployment at one is not a configuration entry. Adding a second protocol is an architectural decision rather than an addition to this table, and ADR 0011 is where that decision was taken and what would reopen it.
A verdict on quality. Which model retrieves or answers better is not what any of this establishes. Every entry says only that the protocol, the route, the credential, and the width behaved as recorded.