May 14, 2026 · 9 min read · Use cases

Generating 10,000 destination pages from one API.

Programmatic SEO for travel works when the underlying data is real. It fails when it's GPT slop. Here's the pattern: pull cities by score, fetch typed context per city, render at build time, and skip the LLM-generated paragraphs.

The build pipeline

The mental model is two API calls per page, executed at build time. No runtime LLM, no hallucination surface, no per-request cost.

// Step 1. Get the list of cities to render
GET /v1/cities?iso_alpha2=IN&min_score=50&limit=10000
  → [{ id: "in-jodhpur", name: "Jodhpur", master_score: 84, slug: "jodhpur" }, ...]

// Step 2. For each city, fetch the full context
GET /v1/cities/in-jodhpur/context
  → { city, heritage[], circuits[], airports[], sentiment, narrative }

The narrative field is the part most teams overlook. The API returns long-form destination prose grounded against the structured fields, license-tagged for redistribution. You don't need to write or generate filler text to fill the page.

Static site generator integration

The pattern below is Next.js, but the shape is the same in Astro, Hugo, Eleventy, or your own pipeline.

// app/destinations/[slug]/page.tsx
export async function generateStaticParams() {
  const r = await tmai("/v1/cities?iso_alpha2=IN&min_score=50&limit=10000");
  return r.map(c => ({ slug: c.slug }));
}

export default async function Page({ params }) {
  const ctx = await tmai(`/v1/cities/${params.slug}/context`);
  return (
    <article>
      <h1>{ctx.city.name}, {ctx.city.state}</h1>
      <p>{ctx.narrative.lede}</p>

      <h2>Heritage</h2>
      <ul>{ctx.heritage.map(h => <li key={h.id}>{h.name}</li>)}</ul>

      <h2>Getting there</h2>
      <p>Nearest airport: {ctx.airports[0]?.name} ({ctx.airports[0]?.iata}).</p>

      {ctx.circuits.length > 0 && (
        <p>On the {ctx.circuits[0].name}, stop {ctx.circuits[0].sequence}.</p>
      )}
    </article>
  );
}

The schema you write against

The shape of /v1/cities/{id}/context is stable across countries. Build your template once and it works for every city in coverage:

Why not just generate the prose with an LLM?

Three reasons. First, Google has been measurably down-weighting pure LLM-generated filler since the 2024 helpful-content updates. Pages that look like every other AI travel page rank like every other AI travel page. Second, the hallucination rate on second-tier Indian cities is high enough that 10–30% of your facts will be wrong on the day you publish. Third, the marginal cost of LLM tokens × 10,000 pages is non-trivial; pulling pre-written, license-tagged narrative is one HTTP call.

Indexability and freshness

Set up a weekly rebuild that re-pulls /v1/cities/{id}/context for the top 2,000 cities by master_score, and a monthly rebuild for the long tail. Sentiment, airport changes, and new heritage inscriptions flow through automatically. Your sitemap stays accurate; pages that lose freshness stop ranking.

Schema markup, for free

Because every field is structured, your TouristAttraction and Place JSON-LD writes itself. Pull lat/lon out of city, names out of heritage[], and you get rich-result-eligible markup without a manual review step.

Worked example: 9,000+ Indian city pages

Filter min_score=50 on India and you get roughly 4,200 pages worth shipping. Drop to min_score=30 and you're closer to 9,000. Ship them as a typed sub-section of your domain, cross-link by circuit and by state, and let the long tail accumulate. Three months in, head terms still go to Wikipedia, but the long tail — "things to do in Jodhpur near Mehrangarh," "ASI monuments in Hampi" — starts converting.

Free tier covers 1,000 calls/month, enough to prototype a full state. Growth at $249/mo is comfortable for a 10,000-page build with weekly refreshes.

Sign up — free See India coverage →