Run mem0 against a custom OpenAI-compatible base URL.

Updated 2026-07-16

mem0's OpenAI provider takes an openai_base_url config key. Set it to https://api.apisrouter.com/v1, pass one key, and the model that extracts and updates memories can be any id in the catalog, Claude and DeepSeek included, without touching the rest of your memory pipeline.

Quick answer: one config key inside the llm block.

mem0's OpenAI LLM provider resolves its endpoint as config first, environment second, default third: self.config.openai_base_url, then the OPENAI_BASE_URL environment variable, then https://api.openai.com/v1. So the cleanest override is one key in the llm config dict: set openai_base_url to https://api.apisrouter.com/v1, set api_key alongside it (or export OPENAI_API_KEY), and every memory-extraction call routes through the gateway. This is upstream mem0 behavior, readable in mem0/llms/openai.py, not a fork. The TypeScript SDK exposes the same pair in camelCase: openaiBaseUrl and apiKey. Values in the config dict override environment variables, which override defaults, so a config-level base URL wins even on machines where OPENAI_BASE_URL points somewhere else.

config = {
    "llm": {
        "provider": "openai",
        "config": {
            "model": "claude-sonnet-4-6",
            "openai_base_url": "https://api.apisrouter.com/v1",
            "api_key": os.environ["APISROUTER_API_KEY"],
        },
    }
}

What mem0 actually does with its LLM.

mem0 (mem0ai on GitHub, roughly 61K stars) is a memory layer for AI agents. Every add() call runs a pipeline: the LLM reads the new conversation turns, extracts candidate memories, compares them against what is already stored, and decides per memory whether to add, update, delete, or skip. That is real reasoning work, and it happens on every write, so the LLM slot fires far more often than most people expect when they bolt memory onto a production agent. Retrieval is the other half, and it does not use the LLM at all: search() embeds the query and runs vector similarity against the store. Two different clients, two different models, configured in two different blocks (llm and embedder). This split is the single most important thing to understand before rerouting anything, because it means you can move the extraction workload to a multi-vendor gateway while the embedder keeps its existing provider and index untouched. The provider stays "openai" in the config; mem0 passes the model field through as a plain string over /v1/chat/completions. When the endpoint behind openai_base_url serves multiple vendors, that string can be a Claude, GPT, DeepSeek, or GLM id, and swapping the extraction model becomes a one-line config change instead of a provider migration.

Full setup: config dict or environment variable.

The config-dict path is the precise one: it moves only the LLM. Build the dict, hand it to Memory.from_config, and use the memory API as normal. The api_key field keeps the gateway key out of your vector-store and embedder settings entirely. The environment path also exists: mem0's OpenAI classes read OPENAI_BASE_URL when the config key is absent. It is one exported variable and zero code changes, but note the scope: the embedder's OpenAI class reads the same variables (it also honors the older OPENAI_API_BASE name, which the LLM class does not). Export OPENAI_BASE_URL and you have moved both components, which is only correct if the endpoint serves your embedding model too. When in doubt, prefer the config dict and leave the environment alone.

import os
from mem0 import Memory

config = {
    "llm": {
        "provider": "openai",
        "config": {
            "model": "claude-sonnet-4-6",   # any catalog id
            "openai_base_url": "https://api.apisrouter.com/v1",
            "api_key": os.environ["APISROUTER_API_KEY"],
            "temperature": 0.1,
        },
    },
    # embedder block unchanged: keeps its own provider and key
}

m = Memory.from_config(config)
m.add("I prefer window seats and vegetarian meals.", user_id="alice")
print(m.search("seat preference?", user_id="alice"))

Choosing the extraction model.

The practical loop: hold your embedder fixed, run the same conversation fixtures through two or three extraction models, and diff the stored memories. Behind one endpoint that comparison is a config-string edit per candidate, and the per-key usage log prices each candidate's run for you.

  • Extraction quality is memory quality. The LLM decides what is worth remembering and whether new information contradicts old; a model that misses an update pollutes retrieval for every future session. claude-sonnet-4-6 and gpt-5.5 are the reliable middle of this trade.
  • Volume is on every write. A chat product calling add() after each exchange runs extraction thousands of times a day, which is where a fast id like claude-haiku-4-5-20251001 or deepseek-v4-flash keeps the memory layer from dominating the token bill.
  • Contradiction-heavy domains (preferences that change, facts that expire) benefit from a stronger model on add() even if it costs more per call, because a wrong update decision is expensive to detect later.
  • Temperature belongs low. Extraction is a structured-decision task, not creative writing; mem0 exposes temperature in the same config block, and around 0.1 keeps add/update/delete decisions consistent.

Pay-as-you-go · transparent per-model pricing

Selected models are priced below official list prices. Exact input, output, cache, and per-request prices are shown for each model.

ModelOfficial PriceOur Price
Claude Sonnet 4.6$3.00 / $15.00 per M$2.40 / $12.00 per M
GPT-5.5$5.00 / $30.00 per M$4.00 / $24.00 per M
Claude Haiku 4.5 20251001$1.00 / $5.00 per M$0.80 / $4.00 per M
DeepSeek V4 Flash$0.14 / $0.28 per M$0.13 / $0.25 per M
GLM-5.2$1.14 / $4.00 per M$1.03 / $3.60 per M

The failure modes specific to mem0.

A lingering OPENROUTER_API_KEY hijacks routing. mem0's OpenAI LLM class special-cases that variable: when it is set, the class switches to OpenRouter's endpoint and ignores your intent. If requests are not reaching the base URL you configured, check for this variable first and unset it. The environment variable moves more than you meant. OPENAI_BASE_URL is read by both the LLM and the embedder. If the gateway does not serve your embedding model, an env-level override breaks search() while add() keeps working, which presents as "memory writes fine but retrieval is empty or erroring." Scope the override to the llm config block and the embedder never notices. Config keys are per-SDK. Python is snake_case (openai_base_url, api_key); TypeScript is camelCase (openaiBaseUrl, apiKey). A camelCase key in a Python dict is silently ignored and you fall through to the default endpoint, which looks exactly like the override "not working." Model ids are exact strings. mem0 does not validate the model field; it forwards it. A typo surfaces as a model-not-found error from the gateway on the first add(), and the /v1/models listing is the authoritative spelling. Changing the embedder is an index decision, not a config decision. Embeddings from different models live in different vector spaces, so repointing the embedder invalidates similarity against existing vectors. Moving the LLM is free; moving the embedder means re-embedding the store. Plan them as separate migrations.

Who routes mem0 through a gateway.

  • Agent builders adding persistent memory to assistants. Extraction runs on every write, so a single billing surface with per-key usage beats a second vendor dashboard bolted onto the stack.
  • Teams that want Claude-quality extraction behind an OpenAI-shaped config. The provider string stays "openai"; only the base URL and the model id change.
  • High-volume chat products controlling the memory layer's unit cost by pairing a frontier chat model with a fast extraction id, each addressable through the same endpoint.
  • Developers evaluating extraction models side by side. Each candidate is one model string against fixed fixtures, not a new provider integration per vendor.
  • Developers without access to a given vendor's billing. Top-up based access with no card requirement removes the per-provider sign-up dependency.

Verify the endpoint and debug the first add().

Confirm the gateway lists the model you configured before running the pipeline; the model field must match a served id exactly. First-run failures follow a pattern. A 401 means the key the LLM resolved is wrong for the endpoint it resolved, and because both come from a config-over-env cascade, print both effective values rather than assuming; a config api_key with an env base URL (or the reverse) is a classic mismatch. A model-not-found error is an id typo. Requests visibly going to openrouter.ai mean the OPENROUTER_API_KEY special case fired. And if add() succeeds while search() fails, you moved the embedder by accident via the environment; scope the base URL into the llm block. Once memories flow, the APIsRouter console shows per-request model, token counts, and spend. Extraction calls are small but relentless, and the usage view is how you see what the memory layer genuinely costs per thousand writes rather than estimating it.

curl -s https://api.apisrouter.com/v1/models \
  -H "Authorization: Bearer $APISROUTER_API_KEY" | head -50

FAQ

Which config key points mem0 at a custom OpenAI-compatible endpoint?

openai_base_url inside the llm provider config in Python (openaiBaseUrl in TypeScript). Config values override the OPENAI_BASE_URL environment variable, which overrides the default https://api.openai.com/v1, so the config dict is the most deterministic place to set it.

Can mem0 extract memories with Claude or DeepSeek models through this setup?

Yes. The provider stays "openai" and mem0 forwards the model field as a plain string over /v1/chat/completions. Any id served by the endpoint behind openai_base_url works, including Claude, DeepSeek, and GLM ids.

Does setting OPENAI_BASE_URL affect the embedder too?

Yes. mem0's OpenAI embedder reads the same environment variables (plus the older OPENAI_API_BASE name). If you only want to move the LLM, set openai_base_url inside the llm config block and leave the environment untouched.

Do I need to change my embedder or vector store to use this?

No. The llm and embedder blocks are independent clients. The extraction LLM can route through the gateway while the embedder keeps its current provider and your existing vectors stay valid. Repointing the embedder is a separate migration that requires re-embedding the store.

Why are my mem0 requests going to OpenRouter instead of my base URL?

mem0's OpenAI LLM class special-cases the OPENROUTER_API_KEY environment variable: when set, it reroutes to OpenRouter regardless of your base URL. Unset that variable and the openai_base_url configuration takes effect.

Does this apply to the hosted Mem0 platform or the open-source SDK?

The open-source SDK (Memory / Memory.from_config), where you control the LLM config. The hosted Mem0 platform manages its own model calls server-side, so a custom base URL applies when you self-host the memory layer.