When you start building with LLMs, the first wall you hit is not the model — it is the key. OpenAI wants an overseas phone number and card. Anthropic wants an overseas card. Gemini is almost instant but throttles the free tier. DeepSeek is domestic-friendly but congests at peak hours. Four providers, four onboarding flows, four rate-limit policies, four dashboards.
This post walks through how I apply for each, the pitfalls that actually burned me, why I collapsed them into one client, and the cost and quota reality nobody warns you about.
The framing matters: most "how to use LLMs" tutorials start at the API call. But the call is the easy part. The friction is everything around it — getting approved, staying under a rate ladder you did not know existed, and not having your launch fail because a card got declined at 2 a.m. Get the key layer right and the model layer becomes a config value. Get it wrong and you spend your first week fighting dashboards instead of shipping.
The four application flows
OpenAI
- Apply at platform.openai.com → API keys.
- Needs an overseas phone number and a working card on file.
- New accounts sit on a Usage Tier ladder. Your initial per-minute quota is tiny and only unlocks as you spend real money over time.
- The pitfall: connecting from some regions triggers risk review, and a frozen account mid-launch is painful. Do your first top-up on a stable network and keep the spend warm. Anthropic (Claude)
- Apply at console.anthropic.com → API Keys.
- Needs an overseas card.
- Also has a Rate Limit Tier, and bulk or high-volume usage is held to stricter usage policies than the docs imply.
- The pitfall: a high card decline rate gets your account throttled even after it was working. Validate with a small charge before you wire it into production. Gemini (Google AI Studio)
- Apply at aistudio.google.com/apikey with a Google account. Nearly instant, which is why people love it for prototypes.
- Free tier has a quota; production needs a paid project.
- The pitfall: the free tier QPS is low and the latency creeps up under load. Do not ship production traffic on it. DeepSeek
- Apply at platform.deepseek.com. Domestic card, WeChat, or Alipay all work, which makes it the easiest for many builders.
- Lowest barrier and cheapest pricing per token by a wide margin.
- The pitfall: the official service congests during peak hours in some regions, so your client needs timeouts and retries or you will see sporadic failures. Why a unified client All four mostly speak the OpenAI-compatible chat and embeddings shape. That means I can collapse them into one client and switch providers by changing a single model string instead of importing four SDKs. import os from openai import OpenAI
One client, four providers behind the same request shape.
BASE_URL = os.getenv("UNIFIED_BASE_URL", "https://easy88ai.com/v1")
API_KEY = os.getenv("UNIFIED_API_KEY")
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
def chat(model: str, prompt: str, timeout: int = 60) -> str:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
timeout=timeout,
)
return resp.choices[0].message.content
print(chat("gpt-4o", "hello"))
print(chat("claude-3-5-sonnet", "hello"))
print(chat("gemini-2.0-flash", "hello"))
print(chat("deepseek-chat", "hello"))
The BASE_URL above points at easy88ai, a unified gateway I use as my base_url. It exposes OpenAI, Claude, Gemini and DeepSeek through one OpenAI-compatible endpoint, so I only maintain one key and one SDK instead of four. (I am building easy88ai, which is what I use as base_url above — more on that at the end.)
Native SDK vs unified
Before I settled on the unified client, I ran each provider through its own SDK. The native path looks like this for just two of them:
OpenAI native
from openai import OpenAI
oa = OpenAI(api_key=os.getenv("OPENAI_KEY"))
oa.chat.completions.create(model="gpt-4o", messages=[{"role": "user", "content": "hi"}])
Anthropic native
import anthropic
ac = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_KEY"))
ac.messages.create(model="claude-3-5-sonnet", max_tokens=256, messages=[{"role": "user", "content": "hi"}])
The problem is not the two lines — it is the drift. Each SDK has its own message format, its own error types, its own timeout knob. Multiply that by four and your error handling becomes four special cases. The unified client trades a tiny bit of provider-specific control for one code path, and for most application code that is the right trade.
The cost and quota reality
- Tiers beat raw price. A provider that is cheap per token but throttles you at launch costs more in incident time than a slightly pricier one with headroom.
- Free tiers are prototypes only. Gemini free and OpenAI trial credits are great for a demo, useless for a launch.
- DeepSeek economics. It is the cheapest by far, so it is my default for high-volume, latency-tolerant tasks. I keep a pricier provider as the fallback for quality-critical paths.
- One bill, one dashboard. Routing through a single egress means one invoice to reconcile instead of four. That alone paid for the migration. Pitfalls I wish I had tracked from day one
- Tier ladders. OpenAI and Claude start you on a low quota. Pre-spend before launch or your first traffic spike fails silently.
- Payment risk. Overseas card declines throttle your account. Test with a small amount first.
- Region/QPS. Gemini's free tier is not production-grade. Move to a paid project.
- Timeouts. DeepSeek congests at peak. Set read timeouts above 60s and add retries.
- Key sprawl. One key per provider per environment turns into a dozen keys you cannot track. Centralize. Key storage rules
- Never hardcode keys in a repo. Use environment variables or a secrets manager.
- Use separate keys per environment (dev / prod) so you can revoke one without breaking the other.
- Set usage alerts so a code bug cannot drain your quota in minutes.
- Rotate on a schedule; treat a leaked key as already compromised. Per-task routing Once everything sits behind one client, routing stops being a migration and becomes a config change. My default policy:
- Cheap, high-volume, latency-tolerant → DeepSeek. It wins on price and is fine for classification, drafting, and bulk summarization.
- Quality-critical, user-facing → GPT-4o or Claude Sonnet. When the output is what the customer reads, I pay for the stronger model.
- Long-context or multimodal → Gemini Flash. Its context window and price make it my default for document-heavy work.
- Fallback for any of the above → the next provider on the list, swapped by changing one model string. The key insight: I do not pick a model per project, I pick a model per call based on the task class. The unified client makes that a one-line branch instead of a four-SDK refactor, and it keeps my application code free of provider-specific conditionals that rot over time. Status codes I actually handle A single error path across four providers saves more time than any model choice. The codes I branch on: Code Meaning My action 401 Bad or revoked key Alert, stop retrying, rotate key 429 Rate limited / quota Exponential backoff, then failover to backup model 500/502/503 Provider-side Retry once, then failover 408 Read timeout Retry with longer timeout (DeepSeek peak) Wrapping this in the unified client means I write the branch once. With native SDKs I would have written it four times and gotten three of them wrong. A minimal production wrapper The unified client is only safe to ship once it has retries and failover. Here is the wrapper I actually run, stripped to the parts that matter: import time import os from openai import OpenAI
BASE_URL = os.getenv("UNIFIED_BASE_URL", "https://easy88ai.com/v1")
API_KEY = os.getenv("UNIFIED_API_KEY")
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
PRIMARY = ["gpt-4o", "claude-3-5-sonnet", "deepseek-chat"]
def chat(prompt: str, models=None, max_retries: int = 3) -> str:
models = models or PRIMARY
last_err = None
for model in models:
for attempt in range(max_retries):
try:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
timeout=60,
)
return resp.choices[0].message.content
except Exception as e:
last_err = e
if "429" in str(e) or "5" in str(e)[:1]:
time.sleep(2 ** attempt) # backoff before next model
break # failover to next model
time.sleep(2 ** attempt)
raise last_err
Two design choices here: a models list gives me ordered failover for free, and a 429 or 5xx on one provider drops straight to the next model instead of burning retries on a dead endpoint. That single behavior has saved more launches than any model tuning.
Why I landed on a gateway
I am building easy88ai, a unified API gateway that routes OpenAI, Claude, Gemini and DeepSeek through one OpenAI-compatible interface. The reason I reach for it as my base_url is boring: I did not want to babysit four onboarding flows, four SDKs, and four dashboards when what I actually need is "call a model and get text back." If you are in the same boat — want to prototype across providers without fighting payment and risk review on each — it is at easy88ai.com.
Once you treat each provider as a dialect behind one client, "which model should I use" becomes a routing table instead of a migration project. Start with one provider you can apply for today, wrap it, then add the others as model strings. The wrapper is the cheapest insurance you will write all year, and the day one of your providers goes down at 2 a.m., you will be glad the failover was a config change and not a rewrite.
Top comments (0)