Build a Secure DeepSeek Mobile App: Chat, Vision, iOS, and Android

Build secure iOS and Android DeepSeek features through a backend, with Vision uploads, tenant-bound file_id, streaming, rate limits and key safety.

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

LayerResponsibilitiesMust not do
iOS / Android appCollect 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 gatewayAuthenticate, 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 registryMap 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 APIRun 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 modeUpstream modelInputAccount concurrencyUse
fastdeepseek-v4-flashText2,500Default low-latency chat, extraction and routine app features.
advanceddeepseek-v4-proText500Harder text reasoning when evaluation justifies latency and cost.
visiondeepseek-v4-flash-vision-expText + images2,500Separately 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.

  1. Authorize before upload. Verify the session, tenant, Vision entitlement, object scope and budget.
  2. Apply a lower app limit. Reject too many or oversized assets before buffering them. Provider maxima are not recommended mobile defaults.
  3. Sniff decoded bytes. Accept only verified JPEG, PNG, GIF or WebP. Do not trust the filename or declared MIME from iOS/Android.
  4. Inspect dimensions and animation. Reject decompression bombs, unexpected animation and dimensions above your product policy.
  5. Remove unnecessary metadata. Strip EXIF/GPS when it is not required, while preserving orientation and image meaning.
  6. Construct the provider payload server-side. Images for Chat Completions must be in a user message and must use the Vision model.
  7. Bind reusable uploads. Store the provider file_id behind your own opaque asset ID and tenant authorization.
  8. Delete or expire media. Apply a documented lifecycle to your upload, provider file, cache, logs and backups.

Current provider ceilings

RuleCurrent documented ceilingMobile-backend action
Inline request body48 MiBSet a lower route limit and stream uploads instead of buffering unbounded JSON.
URL or Base64 image32 MiB eachDecode and validate before forwarding; Base64 increases transport memory.
Image by file_id64 MiBAuthorize the tenant-bound registry entry before reuse.
External URL8,192 characters; 60-second downloadAllow approved public HTTPS only; enforce SSRF controls and lower timeouts.
Images per request600Mobile products should normally allow far fewer.
Total image bytes64 MiB without file_id; 200 MiB including file_idCalculate the whole request before dispatch.
Maximum side8,192 px; 4,096 px when 15+ imagesNormalize 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

ConditionBackend actionMobile action
App authentication failsReturn application 401.Refresh once or ask the user to sign in.
Invalid text/image/file ownershipReturn stable 400/403/415; do not call DeepSeek.Correct input; do not loop.
Provider 400/422Log sanitized model/modality/request ID; fix payload.Show a safe message; do not retry unchanged.
Provider 401/403Alert operators; rotate/fix server credential or permission.Do not retry as transient.
Provider 402Resolve balance/billing.Do not retry as transient.
Provider 429Queue/reduce concurrency; bounded jittered backoff.Honor safe retry state; no rapid taps.
Provider 500/503 or network failureBounded backoff only when safe to repeat.Offer deliberate retry; avoid duplicate results.
Timeout/cancelAbort 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_id and 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_id is 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

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.

Privacy and cookie settings