{"slug": "i-shipped-my-first-ios-app-in-30-days-for-300-here-s-the-build-log", "title": "I shipped my first iOS app in 30 days for $300. Here's the build log.", "summary": "A developer built and shipped Chista, an iOS app that auto-imports and classifies screenshots using AI, in 30 days for about $300. The app uses a SwiftUI frontend with a Python backend on FastAPI, Supabase for database and auth, and OpenAI GPT-4o for categorization. The developer documented a critical bug in Apple's in-app purchase verification library where an empty root_certificates list causes all JWS verifications to fail, requiring manual inclusion of Apple's root CA certificates.", "body_md": "I take a lot of screenshots. The article I'll read later. The recipe I'll cook on Sunday. The movie name from someone's Instagram story. A job post, a product, a tender.\n\nMost of them die in my camera roll.\n\nSo I built ** Chista** — an iOS app that auto-imports every screenshot, classifies it with AI (Article, Product, Event, Reference, Media), and surfaces a one-tap action:\n\nIt shipped on the App Store thirty days after I started, for about $300 in total cost. The interesting part wasn't the app. It was what the build revealed.\n\n**Chista** is a native iOS app + Python backend.\n\n`PHPhotoLibraryChangeObserver`\n\n, scoped to `PHAssetMediaSubtype.photoScreenshot`\n\n(so it literally can't see your other photos).`CategorizationResult`\n\nJSON: category, subtype, title, suggested action, extracted data (price, deadline, URL, etc.).That's the whole thing. The \"magic moment\" is just: you screenshot something, switch to Chista a few seconds later, it's already sorted with a contextual action button.\n\n| Layer | Tool | Why |\n|---|---|---|\n| iOS app | Swift 5.10, SwiftUI, StoreKit 2 | iOS 17+, modern surface |\n| Backend | FastAPI on Railway | One-file ergonomics, fast cold starts |\n| Database + Auth | Supabase | Postgres + JWT auth out of the box |\n| AI | OpenAI GPT-4o (Pro), gpt-4o-mini (Free) | Tier-routed at categorization time |\n| Push | APNs via aioapns | Direct, no Firebase middleman |\n| Subscriptions | StoreKit 2 + `app-store-server-library`\n|\nServer-side JWS verification |\n| Affiliate routing | Custom matrix in Supabase tables | Amazon Associates wired, more pending |\n| Hosting (web) | Cloudflare Pages | Free, fast, never goes down |\n\nNo frameworks I wouldn't reach for again.\n\n| Line item | Cost |\n|---|---|\n| Apple Developer Account | $99/yr |\n| Domain (chista.app) | $10/yr |\n| OpenAI API credits |\n$100 (one-off prepaid) |\n| Cloudflare (web + DNS) | free |\n| Railway (backend) |\n$5/mo trial credit, then hobby tier |\n| Supabase | free tier |\n| Everything else | mostly free tiers |\n\n**Total to ship v1: about $300.**\n\nApple App Review's reviewer kept hitting `INVALID_CERTIFICATE`\n\nwhen validating in-app purchase JWS payloads on our backend. We chased it through four library versions:\n\n```\napp-store-server-library==1.8.0  → INVALID_CERTIFICATE\napp-store-server-library==1.9.0  → INVALID_CERTIFICATE\napp-store-server-library==2.0.0  → INVALID_CERTIFICATE\napp-store-server-library==3.1.2  → INVALID_CERTIFICATE\n```\n\nI enabled online cert checks. I bundled `app_apple_id`\n\n. I made the verifier environment switch independently from APNs. Nothing worked.\n\nThe actual problem: the library has a constructor like this:\n\n```\nSignedDataVerifier(\n    root_certificates=[],   # ← THIS\n    enable_online_checks=True,\n    environment=Environment.SANDBOX,\n    bundle_id=\"com.chista.app\",\n    app_apple_id=6771318695,\n)\n```\n\nI assumed `root_certificates=[]`\n\nmeant *\"use Apple's bundled roots.\"* It doesn't. **The library ships with zero root CAs.** An empty list means \"trust nothing.\" Every Apple-signed JWS fails because the cert chain has no trust anchor.\n\nThe fix: download Apple's Root CA G2 and G3 directly from [apple.com/certificateauthority](https://www.apple.com/certificateauthority/), check them into the repo, and pass the bytes:\n\n```\ncert_dir = Path(__file__).parent.parent / \"data\" / \"apple_certs\"\nroot_certs = [\n    (cert_dir / \"AppleRootCA-G3.cer\").read_bytes(),\n    (cert_dir / \"AppleRootCA-G2.cer\").read_bytes(),\n]\n\nSignedDataVerifier(\n    root_certificates=root_certs,\n    enable_online_checks=True,\n    environment=env,\n    bundle_id=bundle_id,\n    app_apple_id=app_apple_id,\n)\n```\n\nOne line of additional config, ~2KB committed to the repo, problem solved.\n\nIf you're integrating Apple's library and hitting this: **the empty default is the bug, not your environment.** The official docs imply bundled roots; the code does not.\n\nI built Chista with heavy AI assistance — drafting Swift views, generating prompt templates, debugging gnarly things like the cert chain above. It would be dishonest to tell this story without saying so.\n\nWhat that actually changed:\n\nThat last point is where I think the whole industry is heading.\n\nA decade ago, the hard part was writing software.\n\nToday, the hard part is deciding what should exist.\n\nThe cost of creation is collapsing. The time from idea to first version is shrinking. The number of people who can build is exploding. None of this is news on dev.to — but watching it happen in real time on my own project felt different than reading about it.\n\nSoftware is starting to behave like content. The bottleneck is moving from *can you build this* to *should this exist, and is it actually better than what's already there.*\n\nThat's a great problem to have.\n\n`AMAZON_ASSOCIATES_US=...`\n\nstyle env vars for affiliate keys. Six countries in, I moved them all to a `affiliate_keys`\n\nPostgres table. Should have done it on day one.Chista is on the [App Store](https://apps.apple.com/app/chista-screenshot-inbox/id6771318695). Free tier covers 30 screenshots/month. Pro is $4.99/mo with a 14-day trial.\n\nI'd love to hear what's in your camera roll graveyard.\n\n*This post mirrors a version on chista.app. The canonical link is set so dev.to credits the source domain for SEO.*", "url": "https://wpnews.pro/news/i-shipped-my-first-ios-app-in-30-days-for-300-here-s-the-build-log", "canonical_source": "https://dev.to/shillajr/i-shipped-my-first-ios-app-in-30-days-for-300-heres-the-build-log-1h5c", "published_at": "2026-06-13 18:46:34+00:00", "updated_at": "2026-06-13 19:14:54.423143+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-products", "developer-tools"], "entities": ["Chista", "Apple", "OpenAI", "Supabase", "FastAPI", "Railway", "Cloudflare", "GPT-4o"], "alternates": {"html": "https://wpnews.pro/news/i-shipped-my-first-ios-app-in-30-days-for-300-here-s-the-build-log", "markdown": "https://wpnews.pro/news/i-shipped-my-first-ios-app-in-30-days-for-300-here-s-the-build-log.md", "text": "https://wpnews.pro/news/i-shipped-my-first-ios-app-in-30-days-for-300-here-s-the-build-log.txt", "jsonld": "https://wpnews.pro/news/i-shipped-my-first-ios-app-in-30-days-for-300-here-s-the-build-log.jsonld"}}