Chapter 01 — JSON — the language APIs speak4 min read

Reading real API responses

Your first real API response

OK, you know the basics: objects {}, lists [], key-value pairs. Now let's see what JSON actually looks like in the wild. The kind of stuff you'll run into when you look at an API response for the first time.

Fair warning: it's going to be bigger than the examples from lesson 1. Don't panic. You already have everything you need.


A Stripe payment

Your app just charged a customer. Stripe sends back this:

{
  "id": "pi_3MqKj2LkdIwHu7ix0123abcd",
  "object": "payment_intent",
  "amount": 2000,
  "currency": "usd",
  "status": "succeeded",
  "customer": "cus_Na6dX7aXxi11N4",
  "created": 1680000000,
  "metadata": {
    "order_id": "order_9876",
    "product": "Annual Pro Plan"
  }
}

A lot going on here. Let's slow down and pick it apart.

The IDs look weird. "pi_3MqKj2...", "cus_Na6dX...": these are Stripe's way of tagging things. The prefix tells you what you're looking at: pi_ = payment intent, cus_ = customer. You'll see this pattern everywhere. Slack uses U for users, C for channels, etc. Once you know the prefix, you can instantly tell what kind of object you're dealing with.

The amount is 2000, not 20.00. That's not a bug. Stripe stores amounts in cents to avoid decimal issues. 2000 = $20.00. This is a very common pattern in payment APIs. As a PM, this is exactly the kind of thing you want to know before writing a spec. Otherwise your UI might display "$2,000" instead of "$20.00".

metadata is a freeform object. Your engineering team can put whatever they want in there. It's like a sticky note attached to the payment. Super useful for linking a payment back to your own data (like order_id).

status is a string, not a boolean. It's not just "paid" or "not paid". It can be "succeeded", "pending", "failed", "canceled", and more. When you see a status field in an API, always ask: what are all the possible values? That's a spec question you can answer by reading the docs.


A SaaS user profile

Now let's look at something you've probably dealt with a hundred times. A user profile:

{
  "id": "usr_8a3b2c1d",
  "name": "Alice Martin",
  "email": "alice@acme.com",
  "plan": "pro",
  "is_active": true,
  "monthly_spend": 49.99,
  "permissions": ["read", "write", "export"],
  "company": {
    "name": "Acme Corp",
    "industry": "SaaS",
    "employee_count": 150
  },
  "referral_code": null,
  "created_at": "2024-09-15T09:30:00Z"
}

Let's zoom in on a few things.

true and false: no quotes. "is_active": true is a boolean. It means yes or no, on or off. You'll see these for feature flags, subscription status, email verification... If you see quotes around it ("true"), that's actually a string. A subtle but important difference.

null means "nothing". "referral_code": null. This user wasn't referred by anyone. The field exists, but it has no value. It's not "" (empty text), it's not 0, it's truly nothing. When you see null, think: "this field is optional and hasn't been filled in."

permissions is a list. ["read", "write", "export"]: square brackets mean it's an array. Alice has three permissions. Another user might have ["read"] only. Arrays are how APIs represent "one or more of something."

company is a nested object. We saw this in lesson 1. Alice's company info is grouped inside its own {}. To find the industry: company.industry"SaaS".

That weird date. "2024-09-15T09:30:00Z" is an ISO 8601 date. The T separates date from time, and Z means UTC. You'll see this format in almost every API. You don't need to memorize it. Just know that when you see something like 2024-09-15T..., it's a date.


Patterns you'll see everywhere

After reading a few API responses, you start noticing the same patterns over and over:

Prefixed IDs. usr_, pi_, cus_, sub_, evt_... The prefix tells you the type of object without having to look at anything else.

snake_case keys. is_active, monthly_spend, employee_count. This is the most common naming style in APIs. Some use camelCase (isActive), but snake_case dominates.

ISO dates. "2024-09-15T09:30:00Z". The universal date format. Always a string, always in this shape.

Amounts in cents. 2000 instead of 20.00. Common in payment APIs (Stripe, Paddle, etc.) to avoid floating point issues.

Null for optional fields. If a field can be empty, it's usually null rather than missing entirely.

Metadata objects. A freeform {} where teams can attach custom data. Very common in Stripe, Segment, and other developer-facing tools.


Mini exercise: navigate a big response

Here's an order from an e-commerce API. Take a breath, then answer the questions below.

{
  "order": {
    "id": "ord_5f82a",
    "status": "shipped",
    "customer": {
      "name": "Bob Chen",
      "email": "bob@startup.io"
    },
    "items": [
      { "name": "Wireless Mouse", "quantity": 1, "price": 2999 },
      { "name": "USB-C Hub", "quantity": 2, "price": 4500 }
    ],
    "shipping": {
      "method": "express",
      "tracking_number": "1Z999AA10123456784"
    },
    "coupon_code": null
  }
}
  1. What's the customer's email?
  2. How many items are in the order?
  3. What's the price of the USB-C Hub (in dollars)?
  4. Was a coupon used?
  5. What's the shipping method?

Answers:

  1. order.customer.email"bob@startup.io"
  2. order.items has 2 entries in the array
  3. order.items[1].price4500$45.00 (remember: cents!)
  4. order.coupon_codenull → no coupon
  5. order.shipping.method"express"

If this felt doable, you're in great shape. You just navigated a nested API response with objects, arrays, nulls, and amounts in cents. That's a real-world skill.