# I shipped my first iOS app in 30 days for $300. Here's the build log.

> Source: <https://dev.to/shillajr/i-shipped-my-first-ios-app-in-30-days-for-300-heres-the-build-log-1h5c>
> Published: 2026-06-13 18:46:34+00:00

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.

Most of them die in my camera roll.

So 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:

It 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.

**Chista** is a native iOS app + Python backend.

`PHPhotoLibraryChangeObserver`

, scoped to `PHAssetMediaSubtype.photoScreenshot`

(so it literally can't see your other photos).`CategorizationResult`

JSON: 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.

| Layer | Tool | Why |
|---|---|---|
| iOS app | Swift 5.10, SwiftUI, StoreKit 2 | iOS 17+, modern surface |
| Backend | FastAPI on Railway | One-file ergonomics, fast cold starts |
| Database + Auth | Supabase | Postgres + JWT auth out of the box |
| AI | OpenAI GPT-4o (Pro), gpt-4o-mini (Free) | Tier-routed at categorization time |
| Push | APNs via aioapns | Direct, no Firebase middleman |
| Subscriptions | StoreKit 2 + `app-store-server-library`
|
Server-side JWS verification |
| Affiliate routing | Custom matrix in Supabase tables | Amazon Associates wired, more pending |
| Hosting (web) | Cloudflare Pages | Free, fast, never goes down |

No frameworks I wouldn't reach for again.

| Line item | Cost |
|---|---|
| Apple Developer Account | $99/yr |
| Domain (chista.app) | $10/yr |
| OpenAI API credits |
$100 (one-off prepaid) |
| Cloudflare (web + DNS) | free |
| Railway (backend) |
$5/mo trial credit, then hobby tier |
| Supabase | free tier |
| Everything else | mostly free tiers |

**Total to ship v1: about $300.**

Apple App Review's reviewer kept hitting `INVALID_CERTIFICATE`

when validating in-app purchase JWS payloads on our backend. We chased it through four library versions:

```
app-store-server-library==1.8.0  → INVALID_CERTIFICATE
app-store-server-library==1.9.0  → INVALID_CERTIFICATE
app-store-server-library==2.0.0  → INVALID_CERTIFICATE
app-store-server-library==3.1.2  → INVALID_CERTIFICATE
```

I enabled online cert checks. I bundled `app_apple_id`

. I made the verifier environment switch independently from APNs. Nothing worked.

The actual problem: the library has a constructor like this:

```
SignedDataVerifier(
    root_certificates=[],   # ← THIS
    enable_online_checks=True,
    environment=Environment.SANDBOX,
    bundle_id="com.chista.app",
    app_apple_id=6771318695,
)
```

I assumed `root_certificates=[]`

meant *"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.

The 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:

```
cert_dir = Path(__file__).parent.parent / "data" / "apple_certs"
root_certs = [
    (cert_dir / "AppleRootCA-G3.cer").read_bytes(),
    (cert_dir / "AppleRootCA-G2.cer").read_bytes(),
]

SignedDataVerifier(
    root_certificates=root_certs,
    enable_online_checks=True,
    environment=env,
    bundle_id=bundle_id,
    app_apple_id=app_apple_id,
)
```

One line of additional config, ~2KB committed to the repo, problem solved.

If 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.

I 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.

What that actually changed:

That last point is where I think the whole industry is heading.

A decade ago, the hard part was writing software.

Today, the hard part is deciding what should exist.

The 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.

Software 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.*

That's a great problem to have.

`AMAZON_ASSOCIATES_US=...`

style env vars for affiliate keys. Six countries in, I moved them all to a `affiliate_keys`

Postgres 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.

I'd love to hear what's in your camera roll graveyard.

*This post mirrors a version on chista.app. The canonical link is set so dev.to credits the source domain for SEO.*
