ct smith

docs goblin

Building a REST API for fun and no profit

October 23, 2025

I'm obsessed with API definitions. I love REST APIs. I don't know if this has any connection to my utter obsession with the J Crew and LL Bean catalogs in the 90s, but I get the sense that they're connected in some way I can't articulate yet.

I've documented plenty of APIs but I've never actually built one before. I decided to build a very simple static JSON content API for docsgoblin.com (this site). I've never done this before so I started from the place I usually start from: metadata.

deciding on metadata and broad shapes

I knew I was going to do just a static JSON, read-only API. There's not a ton of content on this site yet, and it's all easily represented as JSON. The next step was deciding what exactly I wanted my API to return. I sketched out the response shape for the different content types I have.

article

For my blog posts, I wanted basic stuff like the title, dates, tags, and the HTML content for reuse elsewhere (maybe a future project, IDK).

{
  "id": "string",
  "type": "article",
  "title": "string",
  "summary": "string",
  "url": "string",
  "published_date": "YYYY-MM-DD",
  "updated_date": "YYYY-MM-DD",
  "tags": ["string"],
  "reading_time_minutes": number,
  "word_count": number,
  "content_html": "string (single article only)",
  "author": {
      "name": "string",
      "url": "string"
    } (single article only)
  }

page

I made pages simpler than the articles (blog posts) because they're simpler overall. There's less important noise to capture there.

{
  "id": "string",
  "type": "page",
  "title": "string",
  "summary": "string",
  "url": "string",
  "updated_date": "YYYY-MM-DD",
  "tags": ["string"],
  "content_html": "string (single page only)"
  }

reading item

I went full ham on the reading items because I have a silly belief that if I put a lot of effort into this, then I'll definitely be more of a reader. Ha.

{
  "id": "string",
  "type": "reading_item",
  "title": "string",
  "author": "string | null",
  "url": "string | null",
  "item_type": "book" | "article" | "paper",
  "genre": "fiction" | "non-fiction" | null,
  "status": "reading" | "finished" | "want-to-read",
  "date_added": "YYYY-MM-DD",
  "date_finished": "YYYY-MM-DD | null",
  "rating": 1-5 | null,
  "notes": "string | null",
  "tags": ["string"]
  }

Okay, now that I know what I want from the APIs, I can fully design them.

just write a spec

I really don't know how to describe the work I did on the next part. Because I deal with API specifications professionally, the next natural step was just to write an OpenAPI spec doc. I'm not sure I can really describe my process here because this is just how my brain works -- once I know what data needs to be exposed, the rest of it just naturally comes to me. I really should sit down and think through this, but it's like a near-instantaneous sub-language groking process for me and that's all I can say for now.

Anyway, CT's mystery API machine brain did its thing and I ended up with this definition.

choosing python even though my zoomer coworker will make fun of me

Okay so now I know I need to turn a few directories worth of different kinds of HTML content into JSON representations. First, I made sure all my pages and articles had the right meta tags. Then I created reading-list.yml to abstract my book list, only because I didn't want to complicate the HTML structure of books page. Finally, I wrote a python script to crawl through my source files, do some light math for reading times on the blog pages, and spit out JSON files that become the API endpoints. It uses beautifulsoup4 for parse-y things. If scripting isn't your thing, then Claude or ChatGPT comes in handy here.

I wanted a quick proof-of-concept before I sunk too much time into scripting, so I fed Claude my draft OAS file, an example of each content type, some python-flavored half-assed pseudocode, and said I needed a python script that would do the job of making the HTML into an API. I thought perhaps I'd get something workable but suboptimal, but it worked PERFECTLY on the first try and dumped all my JSON into a new api/ directory.

Satisfied that Claude understood the assignment, I then added the Swagger UI to the site and hooked everything up, so now I have a documented API.

lessons learned

Like with all of my projects, Claude-assisted or not, I learned a lot.

  1. Relative links continue to hamper/humble me. The only trouble I had during this project was repeatedly not understanding how to link Swagger UI to my OAS file.
  2. I didn't understand at first that a static API meant I couldn't do query param magic for filtering. This was disappointing to realize, but maybe a future project for me to learn how to handle this.
  3. The urge to over-engineer is real.
  4. I learned that the .json extension needed to be in the API path, and that's fine (I discovered this while wrestling with point #1 above).

Okay, that was a fun experiment. What next?


catalog addendum: I got possessed by the idea of articulating why I think my catalog hoarding as a kid is connected with my current love of specs. See the catalog connection for the working theory.