Blitz Content Lab · 18 blocks

Content is composed, not written

12 blocks shared between the blog and the courses, 7 lesson blocks reserved to lessons. Every card gives the sentence that chooses it, the real rendering and the code to copy. The renderings below are the components themselves, not images.

SHARED · content/blocksLESSONS · learn/blocksBLOG AND LEARN ROUTES: DORMANT IN SEPTEMBER, BACK IN OCTOBER
01Correct without JavaScript

An interactive block shows its full content at build time. The script adds immediate feedback, never information.

02One import line

A lesson imports from learn/blocks, an article from content/blocks. An author never looks for a path.

03The id is a storage key

Renaming the id of a Quiz or a Checkpoint erases the learner’s answer. It is named after what it asks.

04The compiler holds the rules

Stats refuses a figure without a source, Quiz refuses zero or two correct answers, Payload refuses a note outside the code.

01
Lesson blocks

Seven blocks that make a lesson

Reserved to courses: they know where the learner is. The arc of a lesson places them in this order, and only Objective and Recap are mandatory, because they give the lesson a start and an end.

7 BLOCKS
  1. Objective
  2. prose + shared blocks
  3. Payload
  4. TryIt
  5. Troubleshoot
  6. Quiz · Checkpoint
  7. Recap

Objective

Opens the lesson: what the learner will be able to DO, how long it takes, what to have at hand.

Nota list of topics covered. An outcome starts with an action verb and can be checked.

Live
By the end3 min
  • Add a BlitzAPI call to any Clay table with the HTTP API enrichment
  • Set the rate limit that keeps a bulk run from failing halfway

You needA Clay account · Your BlitzAPI key

MDX
<Objective
  outcomes={[
    'Add a BlitzAPI call to any Clay table',
    'Set the rate limit that keeps a bulk run alive',
  ]}
  needs={['A Clay account', 'Your BlitzAPI key']}
  time="3 min"
/>

Payload

The code the learner will copy, with the lines to adapt annotated one by one.

Nota plain code block when nothing has to change. Three notes on fifteen lines is the right order of magnitude.

Live
POST https://api.blitz-api.ai/v2/enrichment/personjson

          
          {
          
        
          
            "person_linkedin_url": "https://www.linkedin.com/in/antoine-blitz-5581b7373"
          1
        
          
          }
          
        
  1. The only field of the request body: the LinkedIn profile URL. Send the x-api-key header with it.
MDX
<Payload
  label="POST https://api.blitz-api.ai/v2/enrichment/person"
  lang="json"
  code={`{
  "person_linkedin_url": "https://www.linkedin.com/in/antoine-blitz-5581b7373"
}`}
  notes={[
    { line: 2, text: 'The only field of the request body: the LinkedIn profile URL.' },
  ]}
/>

TryIt

Interrupts the reading to demand a gesture, then gives the criterion that says whether it worked.

Notforgetting the expected slot. An exercise without a success criterion invites silent failure.

Live
Your turn~2 min

Fire one row and read the response

Point the enrichment at /v2/enrichment/person and run it on a single row before touching the rest.
You got it when
The cell returns a JSON object with found: true and a person object, and the run counter shows one result consumed.
Show the solution
An empty cell almost always means the enrichment never ran, usually because the LinkedIn URL column was empty on that row.
MDX
<TryIt task="Fire one row and read the response" minutes={2}>
  The instruction, in markdown.

  <span slot="expected">What should appear on screen.</span>
  <span slot="solution">The way out, folded.</span>
</TryIt>

Quiz

saves state

Checks ONE thing right after it was explained. Every option carries its why, the wrong ones included.

Notan absurd distractor. A plausible wrong answer is what teaches the most.

Live
Check yourselfYour first 200 rows enrich fine, then the rest come back 429. What happened?
Show the answer key
  1. You ran out of creditsRunning out of credits does not return 429. It stops the run with a 402 Payment Required.
  2. Clay fired requests faster than your plan allowsThat is what 429 means. The first rows go through because the burst has not yet exceeded the window.
MDX
<Quiz
  id="rate-limit"
  question="Your first 200 rows go through, then 429. What happened?"
  options={[
    { text: 'You ran out of credits', why: 'Running out of credits does not return 429.' },
    { text: 'Too many requests per second', correct: true, why: 'That is what 429 means.' },
  ]}
/>

Checkpoint

saves state

The gate before moving on: checkboxes that remember the learner.

Notconfusing it with Checklist, which is a list to READ, static and without memory.

Live
Checkpoint

Before you build the payload

MDX
<Checkpoint
  id="enrichment-ready"
  title="Before you build the payload"
  items={[
    'The method is POST and the URL points at a BlitzAPI endpoint',
    'One test row returned JSON, not an error',
  ]}
/>

Troubleshoot

The exact text the learner sees on screen, its cause, and the gesture that fixes it.

Notwriting the diagnosis in the symptom column. The learner searches for « 429 », not for « rate problem ».

Live

When it does not work

  • 429 Too Many Requests

    CauseNo client-side rate limit on this enrichment, so Clay fires faster than your plan allows.

    FixSet Max Requests to your plan rate, then re-run only the failed rows.

  • 401 Unauthorized

    CauseThe key is missing, misspelled, or pasted into the wrong header name.

    FixCheck that the header is exactly x-api-key, lowercase, with no trailing space.

MDX
<Troubleshoot
  items={[
    {
      symptom: '429 Too Many Requests',
      cause: 'No client-side rate limit on this enrichment.',
      fix: 'Set Max Requests, then re-run the failed rows.',
    },
  ]}
/>

Recap

Closes the lesson: what the learner did, in the past tense, then the reason to continue.

Notdescribing what the lesson covered. « You set the limit » can be checked, « the limit was presented » cannot.

Live

What you just did

  • You added a BlitzAPI call to a Clay table with the HTTP API enrichment
  • You can tell a 429 apart from an auth error and from an empty input

NextThe plumbing holds. Next you fill it: a real payload that turns a filter into your first page of accounts.

MDX
<Recap
  points={[
    'You added a BlitzAPI call to a Clay table',
    'You can tell a 429 apart from an auth error',
  ]}
  next="The plumbing holds. Next you fill it."
/>
02
Shared core

Twelve blocks shared by blog and lessons

They know nothing about the module that calls them: same components, same rendering in an article and in a lesson. Adding a block here makes it available to both. Twelve names for eleven cards: Steps and Step never come apart.

12 BLOCKS · 11 CARDS

Definition

The plain-language block. A technical text loses its reader on a WORD, not on an idea.

Notusing, inside the definition, the jargon the block is meant to explain.

Live
Definition
Catch-all
also called accept-all domain
A domain that says yes to every address you test, which means a valid answer proves nothing about the address you actually care about.

PreciselyA domain whose mail server accepts any local part at SMTP time, so per-address verification returns no signal.

MDX
<Definition term="Catch-all" aka="accept-all domain" precise="A domain whose mail server accepts any local part at SMTP time.">
  A domain that says yes to every address you test, so a valid answer proves nothing.
</Definition>

Callout

Four tones: note, tip, warning, pitfall. The pitfall tells what breaks in production.

Live
MDX
<Callout tone="pitfall" label="Each column has its own rate limit">
  The setting lives on the enrichment, not on the table.
</Callout>

Steps / Step

A procedure to execute, when the order matters and the reader makes the gestures at the same time.

Nota disguised bullet list. If the order does not matter, a markdown list is enough.

Live

Four fields, once per enrichment

  1. Method: POST. Every BlitzAPI endpoint is a POST.
  2. URL: the endpoint you want. The only field that changes.
MDX
<Steps label="Four fields, once per enrichment">
  <Step>**Method:** `POST`. Every BlitzAPI endpoint is a POST.</Step>
  <Step>**URL:** the endpoint you want. The only field that changes.</Step>
</Steps>

Compare

Two columns: what everyone does, what to do instead. The verdict carries the conclusion.

Notnaming a prop like a slot. Titles go through leftTitle and rightTitle, never left.

Live

Before

One exact-title query

One title, one shot. It works on the accounts that use your vocabulary, and returns nothing on the rest.

After

A prioritized cascade

Tiers, in order of preference. Every account gets an answer, and you know which tier produced it.

The cascade stops at the first tier that finds someone, so it costs what one query costs on the accounts where the title is exact.

MDX
<Compare leftTitle="One exact-title query" rightTitle="A prioritized cascade" verdict="…">
  <div slot="left">…</div>
  <div slot="right">…</div>
</Compare>

Checklist

The list to re-read BEFORE launching, with two polarities: to do, to avoid.

Notusing it as a checkpoint. Nothing is checkable here, and nothing is remembered.

Live

Before a bulk run

  • Rate limit set on every BlitzAPI column
  • A single test row returned real JSON
  • Running with no filter on empty inputs
MDX
<Checklist
  title="Before a bulk run"
  items={[
    { text: 'Rate limit set on every column' },
    { text: 'Running with no filter on empty inputs', polarity: 'avoid' },
  ]}
/>

Endpoint

The card of an endpoint: method, path, the parameters that matter, the response fields to read.

Notcopying the full reference. Only the fields the text talks about are listed, and every name comes from the API reference.

Live
POST/v2/company/tam-by-peopleAPI reference

Returns the distinct companies whose current employees match the people and firmographic filters, each with its matched-people count.

Send

people.job_title.includerequired
Title keywords; wrap a value in [brackets] for an exact match
people.min_per_company
Floor on the matched-people count, 1 to 25, 0 = unset
max_results
Up to 50 companies per page, 10 by default

Read

results[].matched_people
Current employees matching the filter at this company
cursor
Pass it back into the next request until it comes back null
MDX
<Endpoint
  method="POST"
  path="/v2/company/tam-by-people"
  summary="Returns the distinct companies whose employees match the filters."
  params={[
    { name: 'people.job_title.include', note: 'Title keywords; [brackets] for an exact match', required: true },
    { name: 'people.min_per_company', note: 'Floor on matched people, 1 to 25' },
    { name: 'max_results', note: 'Up to 50 companies per page, 10 by default' },
  ]}
  returns={[
    { name: 'results[].matched_people', note: 'Current employees matching the filter at this company' },
    { name: 'cursor', note: 'Pass it back until it comes back null' },
  ]}
  href="https://docs.blitz-api.ai/api-reference/company-search/tam-by-people"
/>

Stats

Figures with their source. The source is mandatory and checked at build time.

Nota hedged figure or one taken from an article. Without a verifiable source, it does not get in.

Live
550M+profiles in the baseBlitzAPI database, 2026
65M+companiesBlitzAPI database, 2026
MDX
<Stats
  items={[
    { value: '550M+', label: 'profiles', source: 'BlitzAPI database, 2026' },
  ]}
/>

Figure

A visual mounted on the Visual Generator: a scene, an asset or a signature. Caption mandatory.

Notan image. The site is image-free outside identity assets.

Live
The cascade stops at the first tier that finds someone.
MDX
<Figure signature="cascade-focus" caption="The cascade stops at the first tier that finds someone." />

Calculator

Two sliders, one formula, the READER’S figure. The result is already right at build time.

Nota free formula. The vocabulary of operations is closed: multiply, multiply-percent, percent-of.

Live

Your discovery rate

Share of your TAM you have contact data for

Share of those where you reach the buyer

24%Accounts you actually work

The two rates compound. Fixing the smaller one moves the result more than fixing the larger one.

MDX
<Calculator
  title="Your discovery rate"
  inputs={[
    { key: 'reach', label: 'Accounts you can reach', value: 40, suffix: '%' },
    { key: 'match', label: 'Right decision maker found', value: 60, suffix: '%' },
  ]}
  op="multiply-percent"
  resultLabel="Accounts you actually work"
  resultSuffix="%"
/>

Diagnostic

A few statements to tick, one verdict. Turns an abstract model into information about oneself.

Notmore than six statements. Beyond that, nobody keeps count.

Live

Self-check

Where does your targeting sit?

Tick what is true for your team today.

Where the answers land you

  1. 2+Stage 1: static listsThe list ages faster than you work it. Start from the account universe, not from the people.
  2. 1+Stage 2: refreshed listsYou rebuild on a cadence. The next step is rebuilding on a signal instead.
  3. 0+Stage 3: dynamic TAMThe universe is a query. Keep it that way.
MDX
<Diagnostic
  title="Where does your targeting sit?"
  questions={['Your account list is a CSV someone exported once', 'You enrich people before agreeing on accounts']}
  outcomes={[
    { from: 2, label: 'Stage 1', text: '…' },
    { from: 0, label: 'Stage 3', text: '…' },
  ]}
/>
03
Reading layout · Blog article

A 740px column, and the blocks inside it

The blog routes are dormant in the September build and come back in October; the collection, the components and the three articles are in the code. The article template is a reading column: header, takeaways, head figure, prose with the shared blocks, FAQ, then three cards.

3 ARTICLES · DORMANT
  1. 01Header

    category label, date, reading time, level (meta.ts), title, description

  2. 02KeyTakeaways

    takeaways[] from the frontmatter, 3 to 5, also emitted as structured data

  3. 03Figure

    the head visual: motion/specs/blog/<slug>.json, or a signature

  4. 04Prose column

    740px, about 67 characters per line (prose.css): 18/28 body, section titles in Inter Display 28/36

  5. 05Shared blocks

    Definition, Callout, Steps, Compare, Checklist, Endpoint, Stats, NextStep, Calculator, Diagnostic, inline in the MDX

  6. 06FAQ

    faq[] from the frontmatter, FAQPage JSON-LD

  7. 07Keep reading

    three BlogCard, latest posts, categories from CATEGORY_LABELS

KEYTAKEAWAYS · blog/KeyTakeaways.astro

PROSE · styles/prose.css

The metric hides in the denominator

Your reply rate is measured on the accounts you found. Discovery Rate measures the ones you did not, which is where most of the market lives. The column is 740 pixels wide, about 67 characters per line, body 18 on 28.

  • Section titles are Inter Display 28/36, the alt heading of the system.
  • Lists keep the same measure; code stays mono.
MDX · frontmatter contract
---
title: "Discovery Rate: The B2B GTM Metric Nobody Tracks"
description: "140 to 160 characters"
category: data-strategy          # playbooks | product | data-strategy
publishedAt: 2026-08-08
readingMinutes: 8
level: beginner                  # beginner | intermediate | advanced
signature: scan                  # head visual, or scene: true with specs/blog/<slug>.json
takeaways:                       # 3 to 5, enforced by the schema
  - "…"
faq:
  - { q: "…", a: "…" }
tags: ["Discovery Rate", "TAM"]
---
import { Definition, Callout, Stats } from '../../components/content/blocks';
04
Reading layout · Course and lesson

An LMS without a login, built from the same column

Catalog, course landing, lesson page. The lesson page is the article column plus the seven lesson blocks and two pieces of chrome; progress lives in localStorage. Routes are dormant in September; the components and content are in the code.

2 COURSES · 12 LESSONS · DORMANT
  1. 01LessonChrome

    reading bar, remaining time, keyboard arrows, resume where you left

  2. 02LessonSidebar

    course plan, current lesson highlighted, ✓ on finished lessons, course progress bar

  3. 03Objective

    the entry contract: outcomes, needs, time

  4. 04Prose + shared blocks

    the same column as an article, the same 12 blocks

  5. 05Payload → TryIt → Troubleshoot

    the gesture: copy, run, fix

  6. 06Quiz · Checkpoint

    the stop: one question, one gate; state saved without a login (ProgressScript, localStorage)

  7. 07Recap

    the exit: what was done, the thread to the next lesson

COURSECOVER · learn/CourseCover.astro · generative, image-free

CRM Enrichment in Code
Dynamic TAM in Clay

LESSON PAGE · the pieces not rendered here

  • CourseCard builds its own /learn/courses/<slug> link and CurriculumList its lesson links: rendering them here would put dormant routes on the page, which the audit refuses.
  • LessonChrome and LessonSidebar read the headings and the progress of a real lesson; they have no standalone state.
  • LeadMagnet posts to the LEADS endpoint of config/site.ts, still empty: the form would have nowhere to go.

The full process, written to be delegated to an AI, is in docs/04-contenu/ecrire-une-lecon.md and creer-un-cours.md.

MDX · a lesson, in order
import { Objective, Payload, TryIt, Troubleshoot, Quiz, Checkpoint, Recap, Callout } from '../../components/learn/blocks';

<Objective outcomes={['…']} needs={['…']} time="3 min" />

Prose, with the shared blocks where they help.

<Payload label="POST https://api.blitz-api.ai/v2/…" lang="json" code={`…`} notes={[{ line: 2, text: '…' }]} />
<TryIt task="…" minutes={2}>…<span slot="expected">…</span></TryIt>
<Troubleshoot items={[{ symptom: '429 Too Many Requests', cause: '…', fix: '…' }]} />
<Quiz id="rate-limit" question="…" options={[…]} />
<Checkpoint id="enrichment-ready" title="…" items={['…']} />

<Recap points={['…']} next="…" />