GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-06-02Intermediate

Why Firebase AI Logic Returns 403 When Calling Gemini from iOS — And How to Fix It

Firebase AI Logic (formerly Vertex AI in Firebase) often returns 403 PERMISSION_DENIED when calling Gemini from an iOS app. Here is how to isolate the three real causes — App Check enforcement, disabled APIs, and missing Blaze billing — based on hands-on device testing.

gemini-api277firebase6ios12troubleshooting82app-check

As an indie developer who has shipped iOS apps since 2014, I once lost a full day to a frustrating symptom: Firebase AI Logic worked fine in the simulator, but the moment I pushed the build to TestFlight, every single Gemini call failed with a 403.

The error message is short — usually just PERMISSION_DENIED or The request is missing a valid API key — which makes it hard to tell whether the problem is in your code or your configuration. This article walks through the three causes I actually hit, and the order in which to check them.

First, read the full 403 response

When you call Gemini through Firebase AI Logic (the FirebaseAI module in Swift), a failure comes back like this:

import FirebaseAI
 
let model = FirebaseAI.firebaseAI(backend: .googleAI())
    .generativeModel(modelName: "gemini-2.5-flash")
 
do {
    let response = try await model.generateContent("Hello")
    print(response.text ?? "")
} catch {
    // Don't swallow this — print the error as-is
    print("Gemini call failed:", error)
}

Printing error directly in the catch block reveals details alongside httpStatusCode = 403, such as status: PERMISSION_DENIED or App Check token is invalid. Reading that full message is the starting point for diagnosis. If you only notice that response.text is nil and move on, you'll take the long way around.

Behind a 403 there are broadly three causes. Checking them top to bottom is the fastest path.

Cause 1: App Check is enforced but no token is being issued (most common)

This was the culprit in my case. Once you flip App Check to "Enforce" in the Firebase console, any request without a valid App Check token is rejected with a 403. The reason it worked in the simulator but failed on device and TestFlight: the simulator was passing a debug-provider token, while on a real device I had never initialized the App Attest provider.

The fix is to set the App Check provider before FirebaseApp.configure(). Get the order wrong and the provider gets swapped in after Firebase initializes, so your first few requests fly out with no token.

import FirebaseCore
import FirebaseAppCheck
 
class AppCheckSetup: NSObject, AppCheckProviderFactory {
    func createProvider(with app: FirebaseApp) -> AppCheckProvider? {
        // App Attest on iOS 14+, DeviceCheck on older devices
        if #available(iOS 14.0, *) {
            return AppAttestProvider(app: app)
        }
        return DeviceCheckProvider(app: app)
    }
}
 
// At launch, call this BEFORE configure
AppCheck.setAppCheckProviderFactory(AppCheckSetup())
FirebaseApp.configure()

To test in the simulator during development, use the debug provider and register the debug token (printed to the console) under "App Check → your app → Manage debug tokens" in the Firebase console.

#if DEBUG
AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory())
#else
AppCheck.setAppCheckProviderFactory(AppCheckSetup())
#endif
FirebaseApp.configure()

One caveat: because App Attest verifies device integrity server-side, the first verification can take a few seconds, even on a TestFlight build. If your app calls Gemini immediately on launch, the token may not be issued yet and you'll get a 403. Waiting for a user action before the first call, or adding a light retry, makes it stable.

Cause 2: The required Google APIs are not enabled

If you run the initial setup from "AI Logic" in the Firebase console via the wizard, the required APIs are enabled automatically. But if you reuse another Firebase project, or just swap in a GoogleService-Info.plist from elsewhere, the APIs can stay disabled and you'll get a 403.

The required API depends on the backend you use:

  • Gemini Developer API backend (.googleAI()): firebasevertexai.googleapis.com and the Generative Language API
  • Vertex AI backend (.vertexAI()): aiplatform.googleapis.com

Check under "APIs & Services → Enabled APIs" in Google Cloud Console that these appear for the target project. To check or enable from the command line:

# Check whether enabled
gcloud services list --enabled --project=YOUR_PROJECT_ID | grep -E "aiplatform|firebasevertexai"
 
# Enable if missing (Vertex AI backend example)
gcloud services enable aiplatform.googleapis.com --project=YOUR_PROJECT_ID

In my experience this trap is easiest to fall into when bolting AI features onto an existing app. If the project never went through the new-setup wizard, suspect this first.

Cause 3: The Blaze (pay-as-you-go) plan is not set up

If you use the Vertex AI backend while the project is still on the free Spark plan, you'll hit a 403 or a billing-related error because billing is required. Upgrading to the Blaze plan from "Usage and billing" in the Firebase console resolves it.

The Gemini Developer API backend runs on Spark within the free tier, but for a production app where you want to avoid rate limits, you'll likely move to Blaze anyway. If cost is a concern, set a Google Cloud budget alert for peace of mind.

The isolation order, summarized

When you hit a 403, checking in this order gets you to the cause fastest:

  1. Print the full error in catch and look for App Check token is invalid → if present, it's Cause 1
  2. If not, confirm the target API is enabled in Google Cloud Console → if disabled, Cause 2
  3. If the API is enabled too, check that the billing plan is Blaze → if Spark, Cause 3
  4. If all of these are satisfied and 403 persists, verify that GoogleService-Info.plist matches the calling project (mixing in a plist from another environment makes the console settings and the actual destination diverge)

Causes 1 and 4 in particular can't be spotted by staring at console settings alone. Cross-check the full on-device error log against the PROJECT_ID in the plist your build actually ships.

Preventing a recurrence

My release checklist for AI-enabled apps now includes one line: "Before enforcing App Check, confirm on a real device (TestFlight) that an App Attest token is actually issued." Enforcement is the last switch you flip — turning it on early in development makes isolating any cause far harder.

During development, keep App Check unenforced (monitoring only), watch token issuance in the console, and only switch to enforcement for production. Since adopting that order, I've stopped getting blindsided by 403s.

I hope this saves someone else the day I lost.

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-05-05
Never Embed Your Gemini API Key in a Mobile App: Complete Multi-Layer Security Architecture with Firebase App Check
A production-grade guide to securing Gemini API access in mobile apps. Covers Firebase App Check, Cloud Functions proxy, rate limiting, and anomaly detection — with complete iOS and Android code examples.
API / SDK2026-07-01
When a Prompt That Worked in AI Studio Quietly Breaks Over the API — Field Notes on Measuring the Difference
A prompt that behaves perfectly in AI Studio returns an empty string or a 404 the moment you call the Gemini API from your own code. Instead of eyeballing the two, here is a small harness that records the config diff plus finish_reason, token usage, and the model name the server actually resolved — so you can isolate the cause by layer.
API / SDK2026-06-23
Integrating Gemini 3.2 Pro Function Calling into iOS/Android Apps: Production Design Patterns
A practical guide to integrating Gemini 3.2 Pro Function Calling into iOS and Android apps. Includes working SwiftUI, Kotlin, and Python code, plus production patterns proven in a real indie wallpaper app — cost, latency, staged rollout, and regression testing.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →