DeepSeek user_id isolation is the provider-documented use of the user_id parameter to distinguish application users served through the same API account. DeepSeek assigns the field three purposes: content-safety isolation, KV-cache isolation, and scheduling isolation. It is useful production metadata, but it is not authentication, authorization, conversation memory, or a substitute for tenant-aware application security.
We tested the field with 80 live requests to the official DeepSeek Chat Completions endpoint on July 25, 2026. The benchmark used two synthetic users, repeated prompt prefixes, omitted-ID controls, a documentation-aligned stateless response control, and a low-load concurrency smoke test. It also challenged the documented user_id validation rules.
The strongest result came from the cache experiment. In all three independent groups, Tenant A warmed a synthetic prefix and then received a 99.59% cache-hit ratio or better. Tenant B’s first request used the same body except for user_id and received zero cache-hit tokens. After Tenant B warmed its own lane, its hit ratio rose to 99.67%. All three groups were consistent with DeepSeek’s documented KV-cache isolation.
The validation result was less tidy. DeepSeek documents an allowed character set of letters, numbers, hyphens, and underscores. The live endpoint nevertheless accepted a space, a dot, and an exclamation mark in three test IDs. It did reject a 513-character ID with HTTP 400 and accepted a 512-character ID. The safe production choice remains to follow the published contract rather than depend on temporary backend leniency.
Scope: this is an independent test of the official hosted API using deepseek-v4-flash, not a test of DeepSeek Chat, a third-party gateway, self-hosted weights, or deepseek-v4-pro. No customer data, private user identifiers, or real tenant content was used. Chat-Deep.ai is independent and is not affiliated with DeepSeek.
Quick answer: what does DeepSeek user_id do?
Pass a stable, pseudonymous user_id with each request when multiple application users share one DeepSeek account. With raw OpenAI-format HTTP, it is a top-level request field. With the Python OpenAI SDK, DeepSeek says to send it through extra_body. With the Anthropic-compatible endpoint, it belongs in metadata.user_id.
user_id can help with | user_id does not provide |
|---|---|
| Separating DeepSeek context-cache lanes | User authentication |
| Distinguishing application users for content-safety handling | Authorization to your database, tools, or files |
| Scheduling isolation under DeepSeek’s documented rules | A separate account quota for every regular user |
| Keeping stable application users in distinct documented cache lanes | Conversation history or server-side memory |
| Giving the provider a stable pseudonymous identity | A place to send email addresses, names, or other private data |
The practical pattern is:
- Authenticate the person in your application.
- Derive the tenant and user context on your server.
- Convert that context into a stable pseudonymous identifier.
- Pass the result as
user_id. - Enforce authorization, data isolation, quotas, and audit rules in your own systems.
For the surrounding API setup, see the DeepSeek API guide. For broader concurrency and 429 handling, use the dedicated DeepSeek API rate-limits guide.
Live benchmark results at a glance

The run sent 80 calls in about 100 seconds. Seventy-nine returned HTTP 200, and the expected 513-character rejection returned HTTP 400. Seventy-seven of 80 calls passed every predefined expectation. The three expectation failures were not server failures: they were the space, dot, and exclamation-mark IDs that the test expected the API to reject but the endpoint accepted.
| Suite | Calls | Live result | Careful interpretation |
|---|---|---|---|
user_id validation | 9 | 8 HTTP 200; 1 HTTP 400; 6/9 expectations passed | Length enforcement matched the docs; three out-of-contract characters were accepted |
| KV-cache isolation | 21 | 21/21 passed | All three A/B groups supported the documented isolation behavior |
| Omitted-ID cache control | 6 | 6/6 passed | Results were consistent with one shared fallback cache lane when no identity was sent |
| Stateless response control | 12 | 12/12 output contracts passed | Constrained responses matched the documented stateless design; this was not an independent hidden-memory test |
| Scheduling smoke test | 32 | 32/32 passed | Request/response association survived eight-way client concurrency; fairness and limits were not tested |
| Full benchmark | 80 | 79 HTTP 200, 1 expected HTTP 400, 77/80 expectation passes | Evidence for this endpoint, model, date, and small synthetic sample |
All 80 requests completed on their first attempt. Every successful call ended with finish_reason: "stop". The observed backend fingerprint was fp_8b330d02d0_prod0820_fp8_kvcache_20260402.
The API reported:
- 153,256 prompt tokens
- 104,704 cache-hit prompt tokens
- 48,552 cache-miss prompt tokens
- 1,292 completion tokens
- 0 reasoning tokens
- $0.007452 calculated cost at the prices checked on the test date
- 274 ms minimum, 303 ms median, 513 ms p95, and 587 ms maximum response-header availability
The overall prompt cache-hit share was 68.32%. At the then-current Flash rates, the same prompt and output token counts would have cost approximately $0.021818 if every prompt token had been billed as a cache miss. The observed cache counters reduced that calculation by about 65.8%. That is a benchmark cost comparison, not a future price guarantee.
The calculation used the official rates checked on July 25, 2026: $0.0028 per million cache-hit input tokens, $0.14 per million cache-miss input tokens, and $0.28 per million output tokens.
| Suite | Prompt tokens | Cache-hit tokens | Completion tokens | Calculated cost |
|---|---|---|---|---|
| Validation | 424 | 0 | 48 | $0.00007280 |
| KV-cache isolation | 113,298 | 80,640 | 458 | $0.00492615 |
| Omitted-ID control | 36,218 | 24,064 | 102 | $0.00179750 |
| Statelessness | 916 | 0 | 152 | $0.00017080 |
| Scheduling smoke | 2,400 | 0 | 532 | $0.00048496 |
| Total | 153,256 | 104,704 | 1,292 | $0.00745221 |
The documented DeepSeek user_id contract

The official Rate Limit & Isolation documentation says user_id can distinguish users on the developer’s side under one account. It assigns the field three specific functions.
Content-safety isolation
DeepSeek says it uses the identifier to distinguish application-side user identities for content-safety handling. The normal Chat Completions response does not expose a user safety profile, score, enforcement history, or moderation dashboard field. Therefore, this aspect was not independently verified by the benchmark.
Do not translate “content-safety isolation” into a claim that user_id moderates your application for you. You still need an appropriate use policy, input and output controls, incident handling, and deterministic authorization around tools and data.
KV-cache isolation
DeepSeek says user_id isolates KVCache for privacy management. Its separate Context Caching guide explains that caching is enabled by default and operates on reusable prompt prefixes. Responses expose prompt_cache_hit_tokens and prompt_cache_miss_tokens, which made this behavior observable without inspecting provider infrastructure.
Context caching remains best effort. DeepSeek says cache construction takes seconds, a hit is not guaranteed, and unused cache data is normally cleared within hours to days. A single miss cannot prove isolation. The meaningful comparison is a warm same-user result against a first identical-prefix request in a different user’s lane.
Read the full DeepSeek Context Caching guide for prefix-unit rules and cost optimization. This article focuses on the identity boundary around that cache.
Scheduling isolation
DeepSeek also uses user_id for scheduling isolation, but the wording matters. For regular API accounts, all user_id values are still combined when calculating account concurrency. The documented account limits are 500 concurrent requests for deepseek-v4-pro and 2,500 for deepseek-v4-flash.
For accounts that receive increased concurrency quotas, DeepSeek says it applies both a total account limit and per-user_id limits. The published per-ID figures are 500 for Pro and 2,500 for Flash. An empty ID is treated as a special ID for this purpose.
This does not document weighted fairness, reserved capacity, priority classes, RPM, TPM, or a guaranteed minimum for every tenant. Multiple API keys under one account also share the account-level concurrency calculation. Build your own per-tenant queue and quotas instead of assuming the provider will implement your product’s fairness policy.
How the 80-call test worked
The benchmark called:
POST https://api.deepseek.com/chat/completions
Every model request used:
{
"model": "deepseek-v4-flash",
"thinking": {
"type": "disabled"
},
"temperature": 0,
"top_p": 1,
"stream": false
}
Output limits varied from 50 to 80 tokens according to the suite. JSON mode was used for mechanically checkable responses. Temperature zero reduced an avoidable source of variation, but it is not a documented deterministic seed.
Tenant A and Tenant B were synthetic. The result files store labels, lengths, and SHA-256 hashes rather than relying on real user details. Synthetic English reference records provided the long cache prefixes. A six-second pause followed the second warm-up call before each main cache probe.
The checks were defined around HTTP status, token-accounting consistency, finish_reason, valid JSON, and exact expected fields. Cache tests were not retried, so a retry could not manufacture a cleaner cache sequence. The temporary API credential was never written to the benchmark outputs.
Live user_id validation: documentation versus observed behavior

DeepSeek documents a maximum length of 512 characters and the character set [a-zA-Z0-9\-_]+. It also says not to include private user information. We sent nine controlled cases directly in the JSON request body.
| Test value | Documentation expectation | Live status | Benchmark result |
|---|---|---|---|
| Field omitted | Accepted | 200 | Passed |
| Empty string | Observational case | 200 | Accepted in this run |
| Letters, numbers, and hyphen | Accepted | 200 | Passed |
| Underscore | Accepted | 200 | Passed |
| Exactly 512 characters | Accepted | 200 | Passed |
| Contains a space | Should fall outside documented set | 200 | Unexpectedly accepted |
| Contains a dot | Should fall outside documented set | 200 | Unexpectedly accepted |
Contains ! | Should fall outside documented set | 200 | Unexpectedly accepted |
| 513 characters | Rejected | 400 | Passed |
The 513-character response identified user_id as too long and stated the 512-character maximum. The three punctuation cases returned normal model completions instead of validation errors.
This is precisely why an evidence-first guide must distinguish the documented contract from one observed implementation. The correct production rule is still to generate IDs that contain only ASCII letters, digits, _, and -, and keep them at 512 characters or fewer. A lenient server can become strict without notice. A different endpoint, SDK layer, regional deployment, or future release can also enforce the published pattern.
Do not use the unexpected acceptance as permission to put an email address into the field. DeepSeek explicitly says not to include private information in user_id. Sections 3.3 and 5.5 of the DeepSeek Open Platform Terms also place downstream end-user disclosure and personal-information obligations on the developer: disclose the applicable processing rules and obtain consent or another lawful basis where required. The terms say DeepSeek’s own privacy policy does not cover the personal-information rules for end users of a developer’s downstream application.
How to send user_id correctly

Raw HTTP or cURL
With the OpenAI-format endpoint, put user_id at the top level of the request body. Keep messages as an array.
curl https://api.deepseek.com/chat/completions \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-flash",
"messages": [
{
"role": "system",
"content": "Return a concise support-ticket summary."
},
{
"role": "user",
"content": "The synthetic test customer cannot reset a password."
}
],
"user_id": "tenant_7f93-user_214c",
"thinking": {"type": "disabled"},
"max_tokens": 120
}'
One official isolation-page example currently displays messages as a single object. The Chat Completions schema requires an array, so the example above uses the schema-correct form.
Python with the OpenAI SDK
DeepSeek’s documentation says to place the nonstandard field in extra_body when using the OpenAI Python SDK.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com",
)
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[
{"role": "system", "content": "Return a concise support-ticket summary."},
{"role": "user", "content": "Synthetic ticket TKT-204 needs review."},
],
max_tokens=120,
extra_body={
"user_id": "tenant_7f93-user_214c",
"thinking": {"type": "disabled"},
},
)
usage = response.usage.model_dump()
print(response.choices[0].message.content)
print("cache hit:", usage.get("prompt_cache_hit_tokens", 0))
print("cache miss:", usage.get("prompt_cache_miss_tokens", 0))
Do not assume the OpenAI user parameter is the same control. DeepSeek documents user_id, and the Python example specifically uses extra_body. If a framework only exposes user, verify the raw outgoing JSON before claiming that tenant isolation is active.
Anthropic-compatible requests
On DeepSeek’s Anthropic-compatible route, the documented placement is metadata.user_id.
import os
from anthropic import Anthropic
client = Anthropic(
api_key=os.environ["DEEPSEEK_API_KEY"],
base_url="https://api.deepseek.com/anthropic",
)
message = client.messages.create(
model="deepseek-v4-flash",
system="Return a concise support-ticket summary.",
messages=[
{"role": "user", "content": "Synthetic ticket TKT-204 needs review."}
],
metadata={"user_id": "tenant_7f93-user_214c"},
max_tokens=120,
)
print(message.content)
The official Anthropic API compatibility guide says metadata.user_id is supported while other metadata fields are ignored.
Live cache-isolation test with two synthetic users

Each of the three cache groups used a unique synthetic reference containing 160 generated records. The repeated prefix was approximately 5,376 cacheable tokens. The probe bodies for Tenant A and Tenant B were byte-equivalent after removing user_id; the harness recorded matching request-body hashes to verify that no prompt difference explained the result.
Each group ran this seven-call sequence:
- Tenant A warm-up 1
- Tenant A warm-up 2
- After a six-second cache-build wait, Tenant A probe
- Tenant B first identical-prefix probe
- Tenant B second warm-up
- After a second six-second wait, Tenant B probe
- Tenant A return probe
The same pattern appeared in all three groups.
| Phase, aggregated across 3 groups | Prompt tokens | Cache-hit tokens | Cache-miss tokens | Observed meaning |
|---|---|---|---|---|
| Tenant A first probe after warming | 16,194 | 16,128 | 66 | Tenant A’s prefix was warm |
| Tenant B first cross-tenant probe | 16,194 | 0 | 16,194 | Tenant B did not inherit A’s warm prefix |
| Tenant B probe after its own warming | 16,182 | 16,128 | 54 | Tenant B then had its own warm prefix |
| Tenant A return probe | 16,182 | 16,128 | 54 | Tenant A’s lane remained warm |
At the individual-call level, the same-tenant probes produced cache-hit ratios from 99.59% to 99.67%. Every first Tenant B cross-tenant probe produced 0%. Fifteen of the 21 cache-suite calls reported hits; the six misses were the three initial Tenant A warm-ups and the three first Tenant B cross-tenant probes.
This result supports the documented isolation behavior in this run. It does not prove that no cache-isolation bug can ever exist, inspect DeepSeek’s internal storage, or establish a formal confidentiality guarantee. The only provider-reported cache signal available to the harness was token usage.
It also does not show that cached output was shared. DeepSeek’s cache concerns input-prefix computation. The model still generated each output, and each response had to satisfy the current request’s JSON contract.
A minimal reproducible cache probe
The following Python shape reproduces the important comparison. Use only synthetic content, make the prefix long enough to observe useful token counters, and expect caching to remain best effort.
import os
import time
import requests
URL = "https://api.deepseek.com/chat/completions"
HEADERS = {
"Authorization": f"Bearer {os.environ['DEEPSEEK_API_KEY']}",
"Content-Type": "application/json",
}
synthetic_prefix = "\n".join(
f"Record {i:03d}: generated test data; no personal information."
for i in range(1, 801)
)
def run(user_id: str, request_code: str):
body = {
"model": "deepseek-v4-flash",
"messages": [
{
"role": "system",
"content": "Return JSON with request_code copied exactly."
},
{
"role": "user",
"content": f"{synthetic_prefix}\nrequest_code={request_code}"
},
],
"user_id": user_id,
"response_format": {"type": "json_object"},
"thinking": {"type": "disabled"},
"temperature": 0,
"max_tokens": 50,
}
response = requests.post(URL, headers=HEADERS, json=body, timeout=120)
response.raise_for_status()
data = response.json()
usage = data["usage"]
print(
user_id,
request_code,
"hit=", usage["prompt_cache_hit_tokens"],
"miss=", usage["prompt_cache_miss_tokens"],
)
run("tenant_a", "A-WARM-1")
run("tenant_a", "A-WARM-2")
time.sleep(6)
run("tenant_a", "SHARED-PROBE")
run("tenant_b", "SHARED-PROBE")
run("tenant_b", "B-WARM-2")
time.sleep(6)
run("tenant_b", "B-PROBE")
Repeat the sequence with new unique prefixes. Do not treat one cold miss as conclusive, because the provider documents caching as best effort.
What happened when user_id was omitted?
The omitted-ID control created two fresh prefix groups. Within each group, the harness labeled the warm-up requests “Logical A” and the later probe “Logical B,” but no user_id field was sent for either label.
In both groups:
- the first request reported 0 cache-hit tokens and 6,036 misses;
- the second request reported 6,016 hits and 20 misses;
- the later “Logical B” probe reported 6,016 hits and 21 misses.
Those two later probe ratios were 99.65%. The labels existed only in the test application, so DeepSeek received no identity boundary with which to separate them. The result is consistent with omitted requests using a shared fallback cache lane in this run. DeepSeek does not document that observation as a named “default identity partition.”
This does not mean Logical B received Logical A’s text or model output. It means the API reported reused prefix computation. Still, in this run a multi-tenant application that omitted the documented isolation field used a shared fallback cache lane instead of explicit per-user lanes.
user_id is not conversation memory
The official Multi-round Conversation guide states that Chat Completions is stateless. An application must resend the relevant messages on every turn.
We ran a response-contract control in four cycles. Each cycle first sent a unique synthetic marker under Tenant A. A second request used the same user_id but omitted the earlier message and explicitly instructed the model to return NOT_PRESENT when the marker was absent. A third request performed the corresponding control under Tenant B.
All 12 calls passed:
- 4/4 marker-setting requests copied the current marker;
- 4/4 same-user requests without history returned
NOT_PRESENT; - 4/4 different-user requests without history returned
NOT_PRESENT.
Because the recall prompts explicitly stated that history was absent and prescribed NOT_PRESENT, these results verify the requested output contract only. They are not independent evidence about hidden server memory. The stateless conclusion comes from DeepSeek’s protocol documentation: applications must resend the messages the model needs, and user_id is not documented as a history handle.
Use user_id for the documented backend isolation functions. Use your own conversation store, truncation, or summary strategy for chat continuity. The tested DeepSeek multi-turn conversations guide explains those history choices.
What the scheduling smoke test did and did not show

The scheduling suite sent two batches of 16 short calls with a client concurrency of eight. One batch assigned all 16 requests to Tenant A. The other alternated eight Tenant A and eight Tenant B requests.
All 32 returned HTTP 200 on the first attempt, returned the correct tenant and request code, and reported zero cache-hit tokens. The single-tenant batch recorded a true sample median response-header availability of 301 ms and a p95 of 403 ms. The split-tenant batch recorded a true sample median of 294 ms and a p95 of 307 ms; within that split batch, Tenant A’s median was 296 ms and Tenant B’s was 292.5 ms.
The lower split-tenant p95 is an observation, not evidence that splitting IDs improves speed. The two batches were small, ran at different moments, were not randomized into a performance experiment, and were nowhere near the official limits. The timer stopped when the HTTP response object became available, before the body had been fully read; it therefore measures response-header availability rather than time to first streamed token or complete-body latency. Network and service conditions were part of the measurement.
The smoke test supports only two modest statements:
- concurrent responses remained associated with the correct synthetic request and tenant;
- low-load calls with one or two IDs completed normally in this run.
It cannot establish scheduling fairness, noisy-neighbor protection, per-ID throttling, or behavior at 500 or 2,500 concurrent requests. Stressing a paid production endpoint to those levels would be unnecessary for this guide.
Choosing a safe user_id strategy

The identifier should be stable enough to represent the intended isolation lane but opaque enough not to expose private data.
| Strategy | Benefit | Tradeoff |
|---|---|---|
| One ID per end user | Strong user-level cache and safety separation | Shared prefixes warm separately for each person |
| One ID per tenant or workspace | Cache reuse inside the tenant with separation between tenants | Individual users share one provider-side identity lane |
| Tenant plus user pseudonym | Fine-grained distinction and easy internal mapping | More cache partitions and more application bookkeeping |
| Random ID per request | Maximum partition churn | Defeats meaningful cache reuse and stable scheduling identity |
| One global ID or omitted field | Maximum shared-prefix opportunity | No explicit user-level cache partition |
DeepSeek documents users on your business side, not a universal rule for whether the value must represent a person, tenant, workspace, or internal service. Choose granularity deliberately. If content-safety identity needs to follow an individual, a tenant-only ID may be too broad. If strict tenant cache separation is the primary goal, a stable tenant-derived component is essential.
Never accept user_id directly from an untrusted browser field or request header and pass it through unchecked. Derive it from the authenticated server-side context. The OWASP Multi-Tenant Security Cheat Sheet recommends binding tenant context to the authenticated session, validating it through application layers, isolating cache namespaces, and implementing tenant-aware quotas and logging.
Generate a pseudonymous allowed-string ID

An HMAC lets the application create a stable identifier without sending its raw database IDs or email addresses to DeepSeek.
import hashlib
import hmac
import os
def make_deepseek_user_id(tenant_id: str, internal_user_id: str) -> str:
secret = os.environ["USER_ID_HMAC_SECRET"].encode("utf-8")
canonical = f"{tenant_id}:{internal_user_id}".encode("utf-8")
digest = hmac.new(secret, canonical, hashlib.sha256).hexdigest()
return f"u_{digest}"
The result contains only u_ plus lowercase hexadecimal characters, so it stays inside the documented character set and is far below 512 characters. Keep the HMAC secret server-side. Store the mapping or be prepared for the operational effect of rotating that secret: a new value creates a new cache and scheduling lane.
user_id is only one layer of multi-tenant safety

A valid user_id does not authorize a request. A malicious person must not be able to change an ID and gain access to another customer’s prompts, vector results, files, tool credentials, or database rows.
Enforce tenant boundaries before the model call:
- derive tenant context from a verified login or service identity;
- apply tenant filters or row-level security to every data query;
- use separate vector-store namespaces or mandatory tenant filters;
- tenant-prefix application cache keys and object-storage paths;
- scope tool credentials to the least privilege required;
- keep per-tenant queues, quotas, and concurrency budgets;
- redact sensitive data before it enters a prompt;
- log hashes, counts, and request IDs instead of full private prompts where possible;
- test cross-tenant access as an authorization regression case.
Also keep the API key on the server. DeepSeek’s Open Platform Terms say developers must not expose an API key in browser or other client-side code and must implement organizational and technical security measures. The DeepSeek API key guide covers secure creation, storage, and rotation.
For workloads involving personal, regulated, or confidential information, review the provider’s terms and your own legal obligations before sending data. The site’s DeepSeek data-residency guide covers the hosted-service privacy context separately. Cache isolation does not change where the provider processes data.
What to log in production
The response does not document an echoed user_id, so keep your own correlation record. Avoid logging the raw ID if a hash or internal surrogate is sufficient.
Useful metrics include:
- internal tenant and user surrogate;
- DeepSeek model;
- application request ID;
- active in-flight count by tenant and model;
- queue depth and queue wait time;
- HTTP status and retry count;
- response-header availability at p50, p95, and p99, plus separately measured full-body or streaming timings where relevant;
- prompt, cache-hit, cache-miss, completion, and reasoning tokens;
- calculated cost by tenant;
- model
system_fingerprint; - timeout, 429, 500, and 503 rates;
- prompt-template version;
- cache-hit ratio by stable prefix and identity lane.
Alert on sudden cache-hit collapse after a prompt-template deployment. Dynamic timestamps, random request IDs, and frequently changing instructions at the beginning of a prompt can prevent prefix reuse. Place changing data after stable instructions and references when the task permits.
For error handling, distinguish validation failures from transient failures. Do not retry a bad user_id indefinitely. Use the DeepSeek error-codes guide for 400, 401, 402, 422, 429, 500, and 503 behavior.
Production checklist
- Use the current official endpoint and model name.
- Send
user_idat the documented location for the chosen protocol. - Keep the value stable for the intended user or tenant lane.
- Restrict generation to letters, digits, hyphens, and underscores.
- Keep the ID at 512 characters or fewer.
- Never include names, emails, phone numbers, prompt text, or other private data.
- Derive the ID on the server from authenticated context.
- Do not trust a tenant identifier supplied directly by the client.
- Keep authorization and tenant data filters outside the model.
- Resend conversation history because
user_idis not memory. - Monitor cache-hit and cache-miss tokens separately.
- Implement application-side per-tenant queues and quotas.
- Treat all API keys under one DeepSeek account as sharing account concurrency.
- Handle 429, 500, and 503 with bounded retry and jitter.
- Do not rely on the three undocumented characters accepted in this one run.
- Recheck documentation, pricing, and behavior before major production changes.
Benchmark limitations
This benchmark is descriptive, not a certification or security proof. It used one model, one endpoint, one account, one network location, two synthetic IDs, three cache groups, and one test date. It could not clear or inspect DeepSeek’s internal cache. Cache-hit token counters were the only provider-reported cache signal.
The cache system is best effort. The consistency of three groups is useful evidence, but a larger repeated study would provide tighter confidence. A cache miss by itself is ambiguous; the paired warm-same-user and cold-different-user design is what made the observed pattern meaningful.
The scheduling smoke test deliberately stayed far below published concurrency limits. It did not test 429 thresholds, capacity expansion, fairness, or service-level guarantees. The stateless response-control prompts explicitly stated that earlier history was absent and prescribed the answer, so they were not an independent memory test. Recorded timing is response-header availability and includes network and provider conditions; it is not streaming TTFT or full-body completion time. Prices, validation behavior, limits, model behavior, and system fingerprints can change.
FAQ
What is DeepSeek user_id isolation?
DeepSeek user_id isolation is the use of DeepSeek’s custom user_id request field for the three functions the provider documents: content-safety isolation, KV-cache isolation, and scheduling isolation among users served through the same API account.
Where do I put user_id in a DeepSeek request?
Put it at the top level of a raw OpenAI-format Chat Completions JSON body. With the Python OpenAI SDK, use extra_body={"user_id": "..."}. With DeepSeek’s Anthropic-compatible endpoint, use metadata={"user_id": "..."}.
What characters are allowed in DeepSeek user_id?
The documented character set is letters, digits, hyphens, and underscores, with a maximum length of 512. Although the July 25, 2026 test accepted a space, dot, and exclamation mark, production code should follow the documented set.
Does user_id isolate the DeepSeek context cache?
DeepSeek says it does. In this benchmark, all three groups showed a warmed Tenant A prefix, zero cache-hit tokens on Tenant B’s first identical-prefix request, and a high Tenant B hit ratio after Tenant B warmed its own lane. That supports the documentation for this run but is not a universal security proof.
What happens if I omit user_id?
In this benchmark, requests without the field behaved consistently with a shared fallback cache lane. In both omitted-ID controls, a later request with a different application-side logical-user label reused the warmed prefix because no identity distinction was sent to DeepSeek. DeepSeek does not name this behavior a default identity partition in the cited documentation.
Does user_id make DeepSeek remember a conversation?
No. DeepSeek documents Chat Completions as stateless, so the application must resend the messages the model needs. The benchmark’s same-ID controls returned the prescribed NOT_PRESENT value, but those constrained prompts were not an independent memory test.
Does every user_id receive a separate DeepSeek rate limit?
Not for regular accounts. DeepSeek says all IDs are combined for the account concurrency calculation. For increased-quota accounts, it documents both a total account cap and per-ID concurrency limits.
Can I use an email address as user_id?
No. DeepSeek explicitly says not to include private user information. Generate a stable pseudonymous value, such as an HMAC-derived hexadecimal identifier, and keep the mapping inside your server.
Does user_id replace tenant authorization?
No. It does not protect your database, vector store, tools, files, sessions, application caches, or object storage. Enforce authenticated tenant context and authorization independently at every data boundary.
How can I verify user_id cache isolation?
Use synthetic long prefixes. Warm a unique prefix under Tenant A, record prompt_cache_hit_tokens, then send the identical request body under a previously unused Tenant B ID. Repeat across several unique groups, allow time for cache construction, and report both hits and misses without treating one run as definitive.
Official sources checked
- DeepSeek Rate Limit & Isolation
- DeepSeek Create Chat Completion reference
- DeepSeek Context Caching
- DeepSeek Multi-round Conversation
- DeepSeek Anthropic API compatibility
- DeepSeek Models & Pricing
- DeepSeek Open Platform Terms of Service
- OWASP Multi-Tenant Security Cheat Sheet
Last tested and source-checked: July 25, 2026.
