Verified August 23, 2026. The secure architecture for a DeepSeek-powered iOS or Android feature is: mobile app → authenticated backend → server-owned policy and media validation → DeepSeek API. The device sends an application request to a backend you control. That backend authenticates the user, authorizes the tenant and feature, chooses the model, validates text or images, applies quotas, calls DeepSeek and returns only the result the app needs.
Never place a DeepSeek API key in an IPA, APK, JavaScript bundle, source file, mobile environment file, remote configuration or crash report. Any secret delivered to a device can be extracted. Obfuscation, certificate pinning and app attestation can reduce abuse, but none converts a provider key embedded on the device into a secret.
Current model boundary: deepseek-v4-flash and deepseek-v4-pro are text-only. Use experimental deepseek-v4-flash-vision-exp only when approved images must reach DeepSeek. Vision accepts supported images by public URL, Base64/data URL, Files API file_id, or inline file_data. Files is image-only, not PDF/DOCX/ZIP or general document storage.
1. Put the trust boundary on your backend
| Layer | Responsibilities | Must not do |
|---|---|---|
| iOS / Android app | Collect approved text or media, hold the app’s short-lived session, render output, show upload/retry/cancel state. | Store the DeepSeek key; select an upstream model; send arbitrary file_id; trust client-only limits. |
| Your backend/API gateway | Authenticate, authorize, choose model, validate bytes and messages, bind assets to tenants, rate-limit, redact logs, apply budgets and call DeepSeek. | Blindly proxy provider payloads, signed URLs, Base64, errors or client-supplied system prompts. |
| Your media registry | Map an application asset ID to tenant ownership, detected type, dimensions, size, provider file_id, key version and expiry. | Treat a syntactically valid provider ID as proof of ownership. |
| DeepSeek API | Run the selected text or Vision model under the provider contract. | Act as your user database, access-control layer or permanent document store. |
Issue the device a session from your own identity system. On every request, verify the session, feature entitlement, tenant and object ownership. Do not accept a user ID, provider model, system prompt, maximum tokens, image URL or provider file ID merely because the client sent it.
2. Choose among Flash, Pro and Vision on the server
| Server mode | Upstream model | Input | Account concurrency | Use |
|---|---|---|---|---|
fast | deepseek-v4-flash | Text | 2,500 | Default low-latency chat, extraction and routine app features. |
advanced | deepseek-v4-pro | Text | 500 | Harder text reasoning when evaluation justifies latency and cost. |
vision | deepseek-v4-flash-vision-exp | Text + images | 2,500 | Separately approved screenshots, photos, charts and UI inspection. |
These are provider ceilings, not application targets. Limits are calculated at account level across API keys. Use much lower per-user and per-tenant limits, queue work, and watch latency, 429s, cancellations and spend. Do not silently change modality or fall back from Vision to a text model by dropping an image.
const MODEL_POLICY = Object.freeze({
fast: {
model: "deepseek-v4-flash",
modalities: ["text"],
providerConcurrency: 2500,
},
advanced: {
model: "deepseek-v4-pro",
modalities: ["text"],
providerConcurrency: 500,
},
vision: {
model: "deepseek-v4-flash-vision-exp",
modalities: ["text", "image"],
providerConcurrency: 2500,
experimental: true,
},
});
function selectPolicy({ requestedMode, hasImage, visionEntitled }) {
const policy = MODEL_POLICY[requestedMode];
if (!policy) throw new Error("unsupported_mode");
if (hasImage && !policy.modalities.includes("image")) {
throw new Error("text_model_cannot_accept_images");
}
if (policy.experimental && !visionEntitled) {
throw new Error("vision_not_authorized");
}
return policy;
}
Keep the upstream model ID in deployment configuration. The device may send a small application enum such as fast, advanced or vision, but the backend owns the mapping and authorization. Alert if an approved ID disappears from the current model catalog; do not silently route an unknown or retired name.
3. Design a safe mobile image pipeline
Avoid sending large Base64 strings in ordinary JSON from the phone. A better default is an authenticated multipart upload from the app to your backend. The backend validates the original bytes, records ownership, chooses URL/Base64/file_id/file_data, builds the DeepSeek request, and returns an application-owned result.
- Authorize before upload. Verify the session, tenant, Vision entitlement, object scope and budget.
- Apply a lower app limit. Reject too many or oversized assets before buffering them. Provider maxima are not recommended mobile defaults.
- Sniff decoded bytes. Accept only verified JPEG, PNG, GIF or WebP. Do not trust the filename or declared MIME from iOS/Android.
- Inspect dimensions and animation. Reject decompression bombs, unexpected animation and dimensions above your product policy.
- Remove unnecessary metadata. Strip EXIF/GPS when it is not required, while preserving orientation and image meaning.
- Construct the provider payload server-side. Images for Chat Completions must be in a
usermessage and must use the Vision model. - Bind reusable uploads. Store the provider
file_idbehind your own opaque asset ID and tenant authorization. - Delete or expire media. Apply a documented lifecycle to your upload, provider file, cache, logs and backups.
Current provider ceilings
| Rule | Current documented ceiling | Mobile-backend action |
|---|---|---|
| Inline request body | 48 MiB | Set a lower route limit and stream uploads instead of buffering unbounded JSON. |
| URL or Base64 image | 32 MiB each | Decode and validate before forwarding; Base64 increases transport memory. |
Image by file_id | 64 MiB | Authorize the tenant-bound registry entry before reuse. |
| External URL | 8,192 characters; 60-second download | Allow approved public HTTPS only; enforce SSRF controls and lower timeouts. |
| Images per request | 600 | Mobile products should normally allow far fewer. |
| Total image bytes | 64 MiB without file_id; 200 MiB including file_id | Calculate the whole request before dispatch. |
| Maximum side | 8,192 px; 4,096 px when 15+ images | Normalize orientation and reject dimensions above product policy. |
General documents are not supported by Files. Do not pass PDF, DOCX, spreadsheet, ZIP, audio or video as if it were an image. If your app offers document analysis, define a separate backend pipeline that extracts approved text or renders selected pages to validated images, then preserves reading order, privacy and user intent.
Protect URL input from SSRF
If the app accepts a remote image, prefer an application allowlist or fetch it into a controlled media service. Require HTTPS, reject embedded credentials, and block private, loopback, link-local, metadata and reserved destinations. Resolve DNS and validate every redirect; enforce byte, MIME, dimension and time limits. Never put signed image URLs in analytics, crash reports, notification payloads or provider-error messages returned to the device.
Bind every file ID to the tenant
A provider file_id is not an authorization token. At upload time record tenant, creator, provider-key version, detected MIME, byte size, dimensions, hash, creation and expiry. On every reuse or deletion, authorize that registry row. Return your own opaque asset ID to the app; do not expose or accept arbitrary provider IDs.
For one image block, use file_id or file_data, not both. Files API is useful when an approved image will be reused. Upload/reuse does not make the following model inference free; images are converted into billed input tokens.
4. Keep text chat small and defensive
import express from "express";
const app = express();
app.use(express.json({ limit: "64kb" }));
const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY;
if (!DEEPSEEK_API_KEY) throw new Error("DEEPSEEK_API_KEY is required");
app.post("/v1/mobile/chat", requireApplicationUser, async (req, res) => {
const policy = selectPolicy({
requestedMode: req.body?.mode || "fast",
hasImage: false,
visionEntitled: false,
});
const messages = validateTextMessages(req.body?.messages);
if (!messages) return res.status(400).json({ code: "invalid_messages" });
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30_000);
try {
const upstream = await fetch("https://api.deepseek.com/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${DEEPSEEK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: policy.model,
messages: [
{ role: "system", content: "Answer clearly and briefly." },
...messages,
],
thinking: { type: "disabled" },
max_tokens: 800,
stream: false,
}),
signal: controller.signal,
});
if (!upstream.ok) return mapProviderErrorWithoutLeakingBody(upstream, res);
const data = await upstream.json();
const text = data?.choices?.[0]?.message?.content?.trim();
if (!text) return res.status(502).json({ code: "empty_ai_response" });
return res.json({ message: text, mode: req.body?.mode || "fast" });
} catch (error) {
if (error?.name === "AbortError") {
return res.status(504).json({ code: "ai_timeout" });
}
return res.status(503).json({ code: "ai_unavailable" });
} finally {
clearTimeout(timeout);
}
});
The helper functions are intentionally application-specific. They must authenticate a real session, validate only string text messages and allowed roles, enforce server-owned character/context limits, redact diagnostics, and map 400/401/402/403/422 differently from transient 429/5xx failures. Do not replace them with “accept any Bearer token” or a blind proxy.
5. Call your backend from Swift
The iOS app sends its application session to your endpoint, not the DeepSeek key. For text chat, use a small JSON body. For Vision, upload the selected image as multipart binary to an application route such as POST /v1/mobile/vision; do not convert a large image to Base64 in the UI process unless the product has measured the memory cost.
struct MobileChatRequest: Encodable {
let mode: String // "fast" or "advanced"
let messages: [ChatMessage]
}
struct MobileChatClient {
let baseURL: URL
let appAccessToken: () async throws -> String
func send(_ payload: MobileChatRequest) async throws -> ChatReply {
var request = URLRequest(url: baseURL.appending(path: "v1/mobile/chat"))
request.httpMethod = "POST"
request.timeoutInterval = 35
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(try await appAccessToken())",
forHTTPHeaderField: "Authorization")
request.httpBody = try JSONEncoder().encode(payload)
let (data, response) = try await URLSession.shared.data(for: request)
try Task.checkCancellation()
guard let http = response as? HTTPURLResponse else {
throw MobileChatError.invalidResponse
}
return try decodeApplicationResponse(data: data, status: http.statusCode)
}
}
Use your application token from an authenticated session. Prefer short-lived access tokens in memory; keep refresh credentials in Keychain only when required. Cancel the task when the user taps Stop, resubmits, or leaves a screen that no longer needs the result. App Transport Security and certificate validation are still required, but certificate pinning does not make an embedded provider key safe.
6. Call your backend from Kotlin
data class ChatRequest(
val mode: String = "fast",
val messages: List<ChatMessage>,
)
interface MobileAiApi {
@POST("v1/mobile/chat")
suspend fun chat(@Body request: ChatRequest): Response<ChatReply>
@Multipart
@POST("v1/mobile/vision")
suspend fun inspectImage(
@Part image: MultipartBody.Part,
@Part("prompt") prompt: RequestBody,
): Response<ChatReply>
}
Add only your application’s access token in an OkHttp interceptor. Do not enable a body-logging interceptor in production: it can capture messages, multipart image bytes, signed URLs and returned content. Use an HTTPS backend URL, lifecycle-aware coroutines, a clear Stop action and bounded client timeouts. Cancellation must propagate to the backend so upstream generation and billing stop when possible.
7. Stream without losing the security boundary
Streaming improves perceived latency but does not justify a direct mobile-to-DeepSeek connection. Authenticate at your backend, create the provider request with stream: true, parse server-sent events there and emit a small application-owned event format.
- Forward text deltas and a terminal event, not arbitrary upstream bytes or diagnostic fields.
- Handle provider keep-alive comments and the terminal marker explicitly.
- Abort upstream work as soon as the client disconnects.
- Limit duration, output tokens, idle time and simultaneous streams per account/tenant.
- Decide whether thinking/reasoning fields are allowed in the user interface or logs.
- Do not stream raw provider errors, image URLs or file identifiers to the device.
8. Use a bounded retry and error policy
| Condition | Backend action | Mobile action |
|---|---|---|
| App authentication fails | Return application 401. | Refresh once or ask the user to sign in. |
| Invalid text/image/file ownership | Return stable 400/403/415; do not call DeepSeek. | Correct input; do not loop. |
| Provider 400/422 | Log sanitized model/modality/request ID; fix payload. | Show a safe message; do not retry unchanged. |
| Provider 401/403 | Alert operators; rotate/fix server credential or permission. | Do not retry as transient. |
| Provider 402 | Resolve balance/billing. | Do not retry as transient. |
| Provider 429 | Queue/reduce concurrency; bounded jittered backoff. | Honor safe retry state; no rapid taps. |
| Provider 500/503 or network failure | Bounded backoff only when safe to repeat. | Offer deliberate retry; avoid duplicate results. |
| Timeout/cancel | Abort provider request and cleanup temporary media. | Show timeout/cancel state; do not auto-loop. |
Generation is not automatically idempotent. A retry can create a second answer and another charge. Assign an application request ID, deduplicate visible results and stop immediately when the user cancels.
9. Protect data, logs and budget
- Data minimization: transmit only the text, pixels and context the feature needs. Warn users before hosted processing where appropriate.
- Logging: default to request ID, status, model/mode, latency and token counts. Exclude keys, cookies, prompts, completions, Base64, signed URLs, images and provider file IDs.
- Output safety: treat output as untrusted text, escape markup and validate structured data before use.
- Tool safety: require server authorization and human confirmation for consequential actions; never let generated text grant permissions.
- Spend: track cache-hit/miss text tokens, image input tokens, output tokens, retries, cancellations and cost per successful task.
- Isolation: enforce conversation, asset and file ownership on every read/write/delete operation.
- Lifecycle: document retention and deletion for device caches, backend storage, provider files, logs and backups.
10. Test before release
Use mocked upstream responses for most tests; a paid provider request is not required to test authorization, media validation or error mapping. Cover:
- missing/expired app session and cross-tenant asset access;
- unknown mode, image on Flash/Pro, Vision without entitlement and image outside a user message;
- fake extensions, wrong MIME, corrupt images, oversized pixels/bytes/count and decompression bombs;
- URL redirects to private/metadata addresses, DNS rebinding, slow download and oversized response;
- arbitrary/reused/expired
file_idand mismatched provider-key version; - PDF/DOCX/ZIP rejection on the image route;
- 400/401/402/403/422/429/500/503, malformed JSON, empty output and disconnect;
- offline mode, backgrounding, cancellation, duplicate taps, large Dynamic Type and TalkBack/VoiceOver labels;
- log/crash/analytics redaction for secrets, Base64, URLs, images and identifiers.
Launch checklist
- The app calls only your HTTPS backend; no DeepSeek key exists in the binary or remote configuration.
- The server allowlist contains all three exact model IDs and rejects unknown/legacy names.
- Flash/Pro accept text only; Vision is a separately authorized experimental image route.
- Image bytes, MIME, dimensions, count, role, total size and URL destination are validated before provider dispatch.
- Files is used only for supported images; PDF/DOCX/ZIP/general documents are rejected or routed through a separately documented pipeline.
- Provider
file_idis never trusted from the app and is tenant-bound in your registry. - Logs exclude keys, prompts, images, Base64, signed URLs and file IDs.
- Account, tenant and user concurrency/budget controls are lower than provider ceilings.
- Mobile cancellation aborts backend and provider work and cleans up temporary media.
- Runbooks cover key rotation, file deletion, model changes, 429s and Vision rollback.
Frequently asked questions
Can an iOS or Android app call DeepSeek directly?
It can technically send HTTP, but a production app must not embed the provider key. Route requests through an authenticated backend that owns authorization, model choice, validation, rate limits, privacy and spend.
Which DeepSeek model IDs should a mobile backend use?
The current documented IDs are text-only deepseek-v4-flash and deepseek-v4-pro, plus experimental multimodal deepseek-v4-flash-vision-exp. Keep them in a server-side allowlist and never accept an arbitrary provider model from the app.
How should a mobile app send an image to Vision Exp?
Upload approved binary media to your authenticated backend. The backend validates it, authorizes the tenant and chooses a public URL, Base64/data URL, tenant-bound file_id, or supported file_data. The DeepSeek key and provider payload stay server-side.
Can DeepSeek Files accept PDF or DOCX documents?
No. The current Files API is documented for JPEG, PNG, GIF and WebP images. It is not a general document store and does not add PDF, DOCX, spreadsheet or ZIP support.
How should a backend protect DeepSeek file IDs?
Store each provider file_id behind your own opaque asset ID and bind it to tenant, creator, provider-key version, media metadata and expiry. Authorize every reuse/deletion and never trust an ID supplied directly by the mobile client.
What image limits should the backend enforce?
DeepSeek documents a 48 MiB body, 32 MiB URL/Base64 image, 64 MiB file_id image, 600 images, 8,192 px per side or 4,096 px for 15+ images, and 64/200 MiB aggregate rules. Production apps should set lower task-specific limits.
Should the mobile app retry every failed request?
No. Retry only appropriate network failures, 429 and selected 5xx responses with bounded jittered backoff. Do not retry unchanged 400/401/402/403/422 errors, and stop when the user cancels.
Should conversation history be stored on the device or server?
That depends on product and regulatory requirements. Minimize retention, encrypt approved storage, enforce tenant ownership and provide deletion. Do not treat either location as automatically safe.
Is streaming required?
No. It can improve perceived latency, but non-streaming is simpler. In both modes the app must call your backend, and cancellation should stop upstream work.
Is Chat-Deep.ai affiliated with DeepSeek?
No. Chat-Deep.ai is an independent guide and is not affiliated with, endorsed by or operated by DeepSeek.
Official implementation references
- Vision Exp and Files API release
- Vision inputs and limits
- Files API
- Account-level concurrency and isolation
- API error codes
Continue with the DeepSeek API guide, Vision Exp model guide, rate-limit guide and current pricing. Verify the official source again before production because models, limits and prices can change.
