slowlp
← Blog
Lesson 2026.06.11 · 10 min read

I Read the Terms of Service After Building the Whole Thing

Building a stock portfolio app — KIS API regulation pitfalls and a pivot

Lesson

I Read the Terms of Service After Building the Whole Thing

Building a stock portfolio app — KIS API regulation pitfalls and a pivot

When I was picking the slowloop brand name, one of the candidates was For Loop. for(;;) { ship(); } — any developer would crack a smile at that, but to a general user it’s just two English words. People using a stock management app wouldn’t feel anything hearing “made by for loop” — I wrote something to that effect once. That stock management app is exactly what this story is about.


What I originally wanted to build was simple. An app for people who are interested in stocks but find individual stock analysis overwhelming — something where you could set up a portfolio, define your target allocations, and have the app calculate an order plan. It would connect to the Korea Investment & Securities (KIS) API: you create a portfolio, set your weights, and the app figures out what to buy and sell.

I built it alongside an AI agent. FastAPI on the backend, Expo for mobile, Clerk for auth, PostgreSQL for the database. I branched off features in parallel and merged them into dev with an integration script — a worktree pipeline. I also experimented with splitting things into a manager agent, worker agents, and a reviewer agent. Through all that, I confirmed firsthand that “context handoff and feed management between agents is the critical piece.”

I built a lot. A portfolio creation wizard, live order execution through KIS, order processing via Celery workers, a notification system, P&L calculations. It was about 70% done.


There was plenty of stumbling around with the KIS API along the way. I first hit the rate limit error (EGW00201) while writing a batch sell for 12 positions at once. The error 40100000 — “not a paper trading business day” — only shows up in paper trading mode, and I spent a while scratching my head because my test environment was misconfigured. I also ran into a situation where D+0 balance and D+2 settlement balance were getting mixed up and the numbers just didn’t add up. And once I ran pytest with AUTH_EMAIL_ALLOWLIST still set in the test environment, which caused 403s across every endpoint.

I deployed it to my home server. I set up a GitHub Actions self-hosted runner on the home server so that pushing to the dev-release branch would automatically trigger Docker Compose. That side had its share of headaches too.

At first I tried using the korat.iptime.org domain, but Let’s Encrypt certificate issuance kept failing. I started by suspecting port forwarding and dug through firewall settings. Two days later I found the cause — the CAA record on iptime.org had 0 issue ";" baked in. It was a blanket block on certificate issuance from all public CAs, and since I can’t control the parent domain’s DNS, there was nothing I could do. One line — dig +short CAA iptime.org — ended two days of suffering. After that I switched to DuckDNS.

Because the self-hosted runner was also the deployment server, I ran into port conflicts too. When CI spun up a test DB container, the live dev stack was already holding port 5432. This was when I first used the !reset [] syntax in Docker Compose’s ports override — the way to completely strip out port mappings defined in the base file. Just using an empty array [] doesn’t actually clear them.

I also caused a cascading failure by accidentally running docker compose up from a stale folder. That old checkout had no project name pin (name:), so the directory name became the project name, which collided with the live stack’s container names. That, combined with missing environment variables, took the site down. After that I deleted the manual folder entirely.


Then, when development was almost wrapped up, I read the KIS API terms of service again.

To have a fintech service make API calls on behalf of other users’ accounts, you need either a Financial Services Commission license or a corporate entity with a financial advisory partnership. In other words, an individual can’t attach to someone else’s account and execute orders. A public launch was effectively off the table.

At first I was pretty deflated. I’d built all the features. But thinking about it for a moment, the path wasn’t completely blocked. Using it on my own account is fine. I can run it on a personal server for personal use only. What I built didn’t go to waste — it became my own investing tool.

That’s how I use it now. I create a portfolio, set my target weights, review the order plan the app calculates, and then execute.


If there’s one thing I’m taking away from this experience — check external API and regulatory constraints before you start designing. Pivoting at 70% completion is a lot harder than designing around those constraints from the start. I got lucky and could convert it to personal use, but if I’d known the restrictions upfront, the whole design might have looked different.

I also picked up the habit of running dig +short CAA <domain> before using a new domain. One line that means I never have to sit through that two-day ordeal again.


← Prev: I figured out what slowloop actually was while planning the brand site Next →: What I went through building a stock portfolio app with AI agents See also: Three deployment headaches from hosting a stock app on a home server · 5 mistakes solo developers keep repeating

Related
All posts →
COMMENTS