Skip to content
Journal / embed

How to avoid vendor lock-in with embedding APIs

Embedding vendor lock-in is real: vectors from different models never mix. Pinning strategies, re-embedding cost math, and model field discipline.

Most API lock-in is soft. Switch scraping providers and you rewrite a client and move on; yesterday’s scrapes do not care who fetches today’s. Embeddings are the exception, and it is worth being precise about why, because the lock-in is structural, permanent, and almost never priced in when teams pick a model.

Vectors from different models do not mix

An embedding is only meaningful relative to the model that produced it. A vector from OpenAI’s text-embedding-3-small and a vector from bge-large are points in two unrelated spaces; computing cosine similarity between them produces a number, and the number means nothing. Often the dimensions do not even match (and matching dimensions would not help).

The consequence: the day you switch embedding models, every vector you have ever stored becomes incompatible with every vector you will store next. There is no adapter, no migration script, no gradual transition where old and new vectors coexist in one index. You re-embed the entire corpus or you run two parallel worlds. This is categorically different from swapping search or parse providers, and it is why I treat embeddings as the one category where the provider choice deserves real deliberation up front. My embedding pricing breakdown covers the per-token economics; this post is about the exit costs.

The good news: re-embedding dollars are small

Here is the part that surprises people in the other direction. Using routed prices from our catalog (list plus 20%), re-embedding a corpus costs:

Corpus sizeDeepInfra bge-large ($0.012/1M)OpenAI 3-small ($0.024/1M)Mixedbread mxbai ($0.072/1M)
100M tokens$1.20$2.40$7.20
1B tokens$12$24$72
10B tokens$120$240$720

Ten billion tokens is a genuinely large corpus, and re-embedding it costs a few hundred dollars. So the API bill is not the lock-in. The lock-in is everything around the bill:

  • Pipeline time. Re-embedding means re-running chunking, batching, and indexing over everything, at whatever throughput your pipeline and rate limits allow.
  • Dual-write windows. During migration you either freeze writes or dual-embed new documents into both indexes, and dual-anything breeds bugs.
  • Tuned thresholds and evals. Every similarity cutoff, reranker setting, and retrieval eval you calibrated is calibrated to the old space. New model, new numbers, recalibrate everything.
  • The stored vectors themselves. If you deleted source text and kept only vectors anywhere in your system, those vectors are now unreadable archives.

Model field discipline

The single cheapest insurance policy, and the one I see skipped constantly: store the model identifier next to every vector. Not in a README, not implied by the table name. A model field on every row, written at embed time.

This sounds bureaucratic until the first time it saves you. Without it, a half-finished migration or a misconfigured worker leaves you with a poisoned index: two incompatible vector populations that look identical, producing retrieval results that are subtly wrong rather than loudly broken. Silent quality degradation is the worst failure mode in retrieval because nothing errors; relevance just decays. With a model field, the bad state is at least queryable, and your retrieval layer can filter to one space while you clean up.

The same discipline extends to query time: the query must be embedded with the same model as the index it searches. Pin that pairing in configuration, in one place, and treat a mismatch as a hard error rather than a shrug.

Pinning strategies that actually work

  • Pin explicitly, not by default. Choose the model deliberately and encode it in config. Never rely on a provider’s “default embedding model,” which is a pointer someone else can move.
  • Version your indexes. Name indexes after the model that fills them (docs_bge_large_v1). Migration becomes building a new index beside the old one and flipping a pointer, with instant rollback.
  • Keep source text forever. Vectors are derived data. As long as the text exists, any migration is money and time; if the text is gone, it is impossible.
  • Budget re-embedding into the switch decision. The table above makes this easy: the dollars are usually noise, so evaluate switches on quality and ops effort, not API cost.
  • Rehearse on a slice. Re-embed 1% of the corpus with the candidate model and run your retrieval evals before committing the fleet.

Where a router honestly helps, and where it does not

I should be candid about my own product here: routing cannot make embedding spaces compatible. Nothing can. What a router gives you is discipline made ambient: on route.tools you specify the model explicitly per request, the response records exactly what ran and what it cost, and failover applies within a model, not across models, because swapping models silently would corrupt your index. The comparison numbers above come from the same open catalog, side by side on the embedding comparison page.

Pick deliberately, tag every vector, keep your text. Lock-in you planned for is just a dependency; lock-in you discovered is a crisis.

When you are choosing the model to pin, start with the current prices and quality scores on the embedding API comparison.