ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-04Intermediate

Building a Veo 3 Video Generation App with Antigravity — From API to Launch

A hands-on guide to building a video generation iOS app using the Veo 3 API with Antigravity. Covers async polling architecture, SwiftUI implementation, and monetization design.

Veo 33video generationGoogle AI4Antigravity338app development8API6indie dev18

The moment that pushed me from "interesting demo" to "I want to build something with this" was watching an 8-second Veo 3 clip. The motion was smooth enough, and the lighting realistic enough, that it felt like product-grade quality rather than a novelty.

This article is about building, not about what Veo 3 is. If you want an introduction to Veo 3 itself, there are good resources elsewhere. Here, the focus is: how do you actually implement a video generation app using the Veo 3 API through Antigravity?

The Architecture You Need to Understand First

Video generation APIs don't work like text APIs. You don't send a request and get a video back in the response. The flow is asynchronous:

User → prompt input → API request
                         ↓
                    job ID returned (immediate)
                         ↓
                    polling loop (5–30 seconds)
                         ↓
                    video URL retrieved → display

If you try to handle this synchronously, your UI will block and users will think the app is frozen. Understanding this before you write a single line of code saves significant debugging time.

When you explain this architecture to Antigravity upfront, it generates well-structured async code that handles the polling loop correctly.

Core API Implementation

The Veo 3 API is part of the Gemini API family and accessed through Google AI Studio. Here's a practical prompt to start your Antigravity session:

"I'm building an iOS app that lets users generate short videos using the Veo 3 API.
The app should use SwiftUI with an async polling architecture.
Flow: user inputs prompt → app starts generation job → polls for completion → displays video.
Start with the API client layer."

The API client Antigravity generates will look something like this:

import Foundation
 
struct Veo3APIClient {
    private let apiKey = "YOUR_API_KEY"
    private let baseURL = "https://generativelanguage.googleapis.com/v1beta"
    
    func startVideoGeneration(prompt: String) async throws -> String {
        let url = URL(string: "\(baseURL)/models/veo-003:generateVideo?key=\(apiKey)")!
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let body: [String: Any] = [
            "contents": [["parts": [["text": prompt]]]],
            "generationConfig": [
                "durationSeconds": 8,
                "aspectRatio": "16:9",
                "resolution": "720p"
            ]
        ]
        
        request.httpBody = try JSONSerialization.data(withJSONObject: body)
        let (data, _) = try await URLSession.shared.data(for: request)
        let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
        
        return json["name"] as! String  // job ID for polling
    }
    
    func checkJobStatus(jobName: String) async throws -> VideoJobStatus {
        let url = URL(string: "\(baseURL)/\(jobName)?key=\(apiKey)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
        
        let done = json["done"] as? Bool ?? false
        if done {
            let response = json["response"] as? [String: Any]
            let candidates = response?["candidates"] as? [[String: Any]]
            let videoUri = candidates?.first?["video"] as? [String: Any]
            let uri = videoUri?["uri"] as? String
            return .completed(videoURL: uri)
        }
        
        return .processing
    }
}
 
enum VideoJobStatus {
    case processing
    case completed(videoURL: String?)
    case failed
}

Polling ViewModel

Tell Antigravity: "Add a ViewModel that polls the job status every 5 seconds for up to 60 seconds, updating the UI with progress." The result:

@MainActor
class VideoGenerationViewModel: ObservableObject {
    @Published var status: GenerationStatus = .idle
    @Published var videoURL: URL?
    @Published var errorMessage: String?
    
    private let client = Veo3APIClient()
    
    func generateVideo(prompt: String) async {
        status = .starting
        
        do {
            let jobName = try await client.startVideoGeneration(prompt: prompt)
            status = .processing(progress: 0)
            
            for attempt in 1...12 {  // 12 attempts × 5 seconds = 60 second max
                try await Task.sleep(nanoseconds: 5_000_000_000)
                
                let result = try await client.checkJobStatus(jobName: jobName)
                
                switch result {
                case .processing:
                    status = .processing(progress: Double(attempt) / 12.0)
                    
                case .completed(let urlString):
                    if let urlString, let url = URL(string: urlString) {
                        videoURL = url
                        status = .completed
                    } else {
                        status = .failed
                        errorMessage = "Failed to retrieve video URL"
                    }
                    return
                    
                case .failed:
                    status = .failed
                    errorMessage = "Video generation failed"
                    return
                }
            }
            
            status = .failed
            errorMessage = "Generation timed out. Please try again."
            
        } catch {
            status = .failed
            errorMessage = error.localizedDescription
        }
    }
}
 
enum GenerationStatus: Equatable {
    case idle
    case starting
    case processing(progress: Double)
    case completed
    case failed
}

UI Design — The Waiting Experience Matters

With video generation, users wait 10–30 seconds. How you handle that wait determines whether users trust the app or abandon it. Tell Antigravity specifically:

"Design the loading state to show:
- A progress bar based on polling attempts
- Animated text: 'Veo 3 is creating your video...'
- A cancel button
Reveal the video player with an animation on completion."

The progress bar gives users a sense of movement even when the API is working in the background. Without it, the experience feels broken.

Monetization Considerations

Veo 3 API is priced per second of video generated (check current Google AI Studio pricing — rates change). If you use your own API key for a user-facing app, costs scale linearly with usage.

Two practical approaches:

User-provided API key: Users enter their own Google AI Studio key. Lower friction to launch, but reduces your addressable market to users who already have API access.

Credit-based in-app purchases: Sell video generation credits through StoreKit. Ask Antigravity to wire up StoreKit 2 with a credit consumption system — it handles this pattern well.

Antigravity prompt: "Implement StoreKit 2 for selling video generation credits.
100 credits = $0.99. Each 8-second video costs 10 credits.
Track remaining credits in UserDefaults and show balance in the UI."

Before You Submit to the App Store

Verify your app handles the Veo 3 content policy correctly. Generated videos must comply with both the API terms of service and App Store guidelines — particularly around deepfakes and misleading content. Add a usage disclaimer to your app and consider adding a content reporting mechanism.

Also add the appropriate NSUsageDescription keys if your app accesses the camera or microphone alongside the video feature.

The technical implementation takes a day. Getting the monetization and policy details right takes longer, but that's where apps succeed or fail long-term. Antigravity helps with both — just ask specifically.

Share

Thank You for Reading

Antigravity 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

App Dev2026-05-06
Running a Solo AI Studio with Antigravity × Google AI — Automating Every Stage of App Development as One Developer
How to combine Antigravity 2.0 and Antigravity CLI with Stitch and Veo 3 into a pipeline that takes an app from design through implementation, testing, asset generation, and App Store submission, driven by one developer. Covers where to delegate to agents and the operational pitfalls.
App Dev2026-05-15
Delegating iOS App Maintenance to Antigravity — An Honest 6-Week Report
A real-world account of using Antigravity for maintenance work on Beautiful HD Wallpapers (50M+ downloads). What worked, what didn't, and what the honest conclusion is for solo developers.
App Dev2026-04-18
Building and Releasing an Art Portfolio App for iOS and Android with Antigravity — The Full Story
How I used Antigravity to build a portfolio app for my own artwork and release it simultaneously on iOS and Android — from concept through App Store and Google Play approval.
📚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 →