You can't put an API key in an iOS app binary. Anyone can extract it. Here's the architecture that keeps your key safe without slowing down your app.
Daniel Ehrlich · May 2026 · 8 min read
The single most important security decision when building an AI-powered app is where your API key lives. Get it wrong and someone will find your key, run up a massive bill, and Anthropic will send you an invoice you didn't expect.
Never do this: let apiKey = "sk-ant-api03-..." in your Swift code.
iOS app binaries are publicly downloadable from the App Store. Tools like IPA extractors can pull strings from any downloaded app in minutes. Your key will be found.
The Architecture: Proxy Pattern
The fix is a thin backend server that sits between your iOS app and the Anthropic API. Your app never knows the API key — it just sends messages to your server, and the server handles the Anthropic call.
iPhone app
↓ POST /api/chat (with your app secret in the header)
Your Railway server
↓ POST /v1/messages (with ANTHROPIC_API_KEY from env vars)
Anthropic API
↓ response
Your Railway server
↓ JSON response
iPhone app
Two separate secrets, two separate channels:
ANTHROPIC_API_KEY — lives only in Railway environment variables. Never in code, never in your repo.
APP_SECRET_KEY — a second secret you create. Lives in Railway AND hardcoded in Constants.swift. Used to verify that only your iOS app can hit your Railway endpoint.
Why the APP_SECRET_KEY? Without it, anyone who finds your Railway URL can hit your /api/chat endpoint and run up your Anthropic bill. The APP_SECRET_KEY is lightweight authentication — not bulletproof, but it stops casual abuse. For production apps with significant traffic, add rate limiting by IP.
// Constants.swift — hardcode these (not secrets, just URLs and a non-secret key)
enum Constants {
static let backendURL = "https://your-app.up.railway.app/api/chat"
static let appSecretKey = "the-same-value-from-railway-APP_SECRET_KEY"
}
Wait — the APP_SECRET_KEY is in Constants.swift? Yes. It's still extractable from the binary, but that's OK. It only authorizes access to your Railway endpoint — not Anthropic's API. If someone finds it and hits your Railway URL, your Railway server limits the damage. Add rate limiting if you want stronger protection. The ANTHROPIC_API_KEY is the one that must never leave the server.
You should get a Claude response back. If you get 401, check the APP_SECRET_KEY matches. If you get 500, check the ANTHROPIC_API_KEY is set correctly in Railway's Variables tab.
Production Checklist
ANTHROPIC_API_KEY only in Railway environment variables
APP_SECRET_KEY in Railway variables AND Constants.swift
Secrets.swift (if you have one) is in .gitignore
Server validates APP_SECRET_KEY on every request
Server returns appropriate HTTP status codes (401 for bad key, 500 for proxy errors)
Set a spending limit in Anthropic Console (console.anthropic.com → Limits)
Consider adding rate limiting by IP for high-traffic apps
Set a spending limit immediately. Go to console.anthropic.com → Settings → Limits and set a monthly hard limit. Even if someone finds a way to hit your proxy, they can only run up so much. This is your last line of defense.