Jettova for Developers

The Meet API

Where should a scattered group meet? Send everyone's home cities and get back destinations ranked by the whole group's total flight cost, travel time, or carbon, with per-person estimates for every option. It's the same fairness engine behind Jettova's group trips, as one endpoint.

Try it below with no key. When you're ready to build, sign in and mint a free key from the dashboard (500 calls/month, no card). The OpenAPI spec is live for Postman imports and codegen, and we read every note from people building on it.

Try it live, no key needed

Where should this group meet? Add everyone's home city and the real engine ranks the fairest destinations by the whole group's flying.

BostonBerlinTokyo

Demo caps: 8 origins, top 5, 10 requests/minute. A free key unlocks 25 origins, filters, visas, per-member booking links, and the full surface below.

Endpoint

POST /api/v1/meet with your key in the Authorization header. CORS is open, so it works from a server or a browser.

curl -X POST https://www.jettova.com/api/v1/meet \
  -H "Authorization: Bearer jk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "origins": ["Boston", "Berlin", "Tokyo"],
    "sort_by": "cost",
    "limit": 5,
    "major_hubs_only": true,
    "dates": { "start": "2026-09-10", "end": "2026-09-14" },
    "adults": 1
  }'

Request fields

  • origins (required): 2 to 25 city names. Free text is fine; we resolve cities and major airports ("Boston", "berlin", "Mexico City").
  • sort_by: cost (default), time, or co2.
  • limit: results to return, 1 to 25 (default 10).
  • major_hubs_only: restrict to ~50 major business hubs, built for corporate offsite use cases (default false).
  • dates (optional): {"start", "end"} in YYYY-MM-DD. When provided, every ranked city returns prefilled booking links: hotels for the group, plus a flight search per member from their own airport for those exact dates. Your users go from "where should we meet" to booking in one step.
  • adults (optional): traveler count for the hotel link, 1 to 8 (default 2).
  • candidates (optional): score ONLY these cities. Built for decision support: your users already have a shortlist ("Austin, Denver, or Chicago?") and want the fairness numbers for exactly those. Unknown cities come back in meta.unmatched_candidates.
  • exclude (optional): drop these cities from the ranking. The inverse of candidates ("anywhere but where we met last year").
  • vibes (optional): restrict to destinations matching tags like beach, nightlife, adventure, from our curated destination data.
  • countries (optional): restrict results to specific countries.
  • passports (optional): ISO2 codes or country names for the group's passports (e.g. ["US", "IN"]). Keeps only destinations every passport can enter without pre-trip paperwork (visa-free, on-arrival, or eTA), and every result carries per-passport visa statuses (incl. e-visa) so groups can loosen the bar themselves. Backed by the Passport Index dataset, refreshed from source — not hand-maintained. Not legal advice; always confirm with official sources before booking.
  • english_friendly (optional): keep only destinations whose country has native or high general English comfort. An opt-in preference, never a default weight — and every result carries english_comfort (native / high / moderate / low, directional) so your group can weigh language themselves.
  • max_member_flight_hours (optional): drop any city where even one member's estimated flight exceeds this. The "nobody suffers a 16-hour trek" constraint.
  • max_avg_cost_usd (optional): drop cities above this estimated cost per person.

sort_by also accepts best — an opinionated blend (45% group cost, 30% fairness, 25% city quality from our curated tiers and real trip ratings) where every result carries a score_breakdown showing its work, tunable via weights ({"cost": 0.6, "fairness": 0.3, "quality": 0.1}) — and fairness, which minimizes the worst single member's journey instead of the group total. When you pass dates, each result includes expected_climate (warm, cold, or mixed) for that month, so "somewhere warm in January" is one request, and meta.date_info reports the number of nights and whether the window covers a weekend. No dates yet? Pass month ("January") alone to get expected_climate without booking links.

The Itinerary API

Once a destination is chosen, POST /api/v1/itinerary builds a full day-by-day plan for it: real named venues and restaurants, one free activity per trip, per-day and total cost estimates, and prefilled booking links when you pass dates. It's the same generation engine behind Jettova trips.

curl -X POST https://www.jettova.com/api/v1/itinerary \
  -H "Authorization: Bearer jk_live_YOUR_KEY" \
  -H "Idempotency-Key: your-unique-id-123" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "Lisbon",
    "days": 3,
    "vibes": ["foodie", "history"],
    "pace": "balanced",
    "dates": { "start": "2026-09-10", "end": "2026-09-13" }
  }'
  • destination (required) and days (required, 1 to 14).
  • vibes, pace (relaxed, balanced, packed), and budget_per_day_usd (all optional).
  • dates and adults: unlock prefilled hotel and activity booking links, same as the Meet API.
  • Idempotency: send an Idempotency-Key header. Because generation is billed per call, a retry with the same key returns the original itinerary without regenerating or double-charging your quota.

Refine an itinerary

Generation is a starting point; travelers edit. POST /api/v1/itinerary/refine takes an itinerary you generated with us plus a natural-language instruction ("make day 2 cheaper", "more seafood", "slow the pace down") and returns the complete updated itinerary with re-summed costs and a modified_days list. The destination can't be changed by an instruction; off-scope asks return a structured refusal with the itinerary unchanged. Send city, days (the array from a generate response), and instruction.

Multi-city trips

The Itinerary API also plans routes: pass legs instead of a single destination — {"legs": [{"city": "Lisbon", "nights": 3}, {"city": "Porto", "nights": 2}]} (2 to 4 legs) — and each leg comes back with its own days, an arrival_transport estimate (train, flight, bus, with duration and price), and per-leg hotel links on sequential date windows when you pass dates.

The Group-Trip API

The whole group decision in one call. POST /api/v1/group-trip takes members with home cities AND the dates each can travel — {"members": [{"origin": "Boston", "name": "Sam", "available": ["2026-09-10", ...]}]} — and returns the fairest cities for that exact group, the best travel window (who makes it, who misses it, with alternatives), and booking links per member from their own airport for those dates. Supports the Meet API's filters (major_hubs_only, vibes, countries, exclude, sort_by) plus trip_length_days. No scheduling tool connects availability to travel decisions; this is that connection as an endpoint.

The Video-to-Trip API (private preview)

The most Jettova thing we make: POST /api/v1/video-trip takes a TikTok or YouTube travel video URL your user pasted, reads the frames and caption, and returns the extracted trip intent (destination, vibe, activities, a confidence level) plus a full generated itinerary, with booking links when you pass dates. Analysis takes 5 to 15 seconds; send an Idempotency-Key so retries never double-bill.

  • url (required): a TikTok or YouTube link, including Shorts. YouTube analysis uses only YouTube's public embed metadata and thumbnails; TikTok support is best-effort and may degrade without notice.
  • days, dates, adults (optional): same semantics as the Itinerary API. Omit days to use the trip length the video suggests.
  • official_sources_only (optional): restrict analysis to platform-documented endpoints. For TikTok this analyzes the official embed cover and caption instead of sampled video frames — confidence is typically lower, but every byte comes from TikTok's own documented API. YouTube analysis is official-sources by default. Built for partners whose compliance teams require it.
  • Calls must be user-directed: your end user submits the specific URL. Nothing from the video is stored, and bulk or automated harvesting isn't permitted.

There's a photo counterpart too: POST /api/v1/photo-trip takes a base64 image your user uploaded (a screenshot, a saved travel photo) and runs the same extraction: destination, vibe, activities, confidence, and a generated itinerary. Since the user supplies their own image bytes, nothing is fetched from any platform at all.

Video-to-Trip and Photo-to-Trip are in private preview at premium per-call pricing. Tell us what you're building to get access.

Health check

GET /api/v1/health is unauthenticated and reports which products are live. Point your uptime monitor at it.

The destination catalog

GET /api/v1/destinations lists every city the API can rank: name, country, vibe tags, and whether it's a major business hub. Use it to build destination pickers and validate candidates before you submit. Optional query filters: country, vibe, major_hubs_only=true. Cacheable for an hour.

Every response also echoes your optional request_id (up to 64 chars) in meta, and carries X-Quota-Used / X-Quota-Limit headers.

Resolve locations first

GET /api/v1/locations?q=NYC resolves free text (city names, aliases, IATA codes) to the canonical city the other endpoints accept, plus its IATA and whether it's valid as a candidates entry. It runs the exact resolver /meet uses, so anything it returns is guaranteed to resolve downstream. Wire it to your picker and 422s disappear.

Compare scenarios in one call

POST /api/v1/meet/batch ranks 1–5 scenarios in a single request (each takes the same filters as /meet): the current team vs. the team plus two new hires, hubs-only vs. anywhere, under $600 vs. uncapped. The whole batch bills one metered call. Results are lean (costs, hours, CO2, fairness); run the winning scenario through /meet for booking links, climate, and visas.

Split expenses

POST /api/v1/expenses/split is the settle-up engine as an endpoint: expenses in (who paid, who shares), per-member net balances and the minimum set of who-pays-whom transactions out. Members are plain display names, so you own identity. Multi-currency inputs settle per currency; nothing is FX-converted.

Visa access

GET /api/v1/visa?passports=US,IN&destination=Tokyo answers the question that kills a "fair" destination in the real world: can this group actually enter? Per-passport entry status plus one group_ok boolean (true when every passport gets in without pre-trip paperwork). Passport Index data; directional, not legal advice.

Packing lists

POST /api/v1/packing-list returns a climate-and-activity-aware packing list as structured data: destination, days, month, and activities in; categorized items (with quantities scaled to trip length) and the expected climate band out. Deterministic, instant, and exactly the block a booking confirmation email or checklist app wants.

Booking links

POST /api/v1/booking-links turns a decision into a booking: destination and dates in, prefilled partner deep links out (hotels, activities, and flights when you pass an origin). Every link carries your key's attribution, so bookings made by your users are visible under your key.

AI-backed endpoints

/itinerary, /itinerary/refine, /video-trip, and /photo-trip run real model inference per call, so they're enabled per agreement rather than on self-serve keys (you'll get a 403 ai_not_enabled otherwise). Everything else on this page, the fairness engine included, works on a free key. Tell us what you're building and we'll switch them on.

Response

{
  "results": [
    {
      "rank": 1,
      "city": "Lisbon",
      "country": "Portugal",
      "iata": "LIS",
      "total_est_cost_usd": 2140,
      "avg_est_cost_usd": 713,
      "total_flight_hours": 24.9,
      "max_member_flight_hours": 14.2,
      "total_co2_kg": 3462,
      "fairness": { "cost_spread_usd": 410, "hours_spread": 6.1 },
      "per_member": [
        {
          "origin": "Boston",
          "distance_km": 5185,
          "mode": "flight",
          "est_round_trip_cost_usd": 512,
          "est_flight_hours": 8.5,
          "flights_url": "https://..."
        }
      ],
      "booking": {
        "activities_url": "https://...",
        "hotels_url": "https://..."
      }
    }
  ],
  "meta": {
    "sort_by": "cost",
    "major_hubs_only": true,
    "currency": "USD",
    "note": "All figures are directional estimates for ranking, not bookable quotes."
  },
  "usage": { "month": "2026-07", "used": 12, "limit": 500 }
}

Every figure is a directional estimate for ranking, not a bookable quote. Unresolvable origins return a 422 listing exactly which inputs failed.

Errors & retries

Every error has the shape { error: { code, message } }. The codes you should branch on:

  • 400 (invalid_json, invalid_origins, invalid_filter, …) — a problem with your request. Never billed, never worth retrying unchanged.
  • 401 (missing_key, invalid_key) — check the Authorization: Bearer jk_live_... header.
  • 422 (unresolved_origins) — the inputs that failed are listed in error.unresolved. Resolve them first with GET /locations.
  • 429 with rate_limited — a burst limit. The response carries a Retry-After header (seconds); honor it and retry. With quota_exceeded — your monthly quota; the body includes usage, and the meter resets at the start of the next UTC month.
  • 502 (generation_failed, …) — an AI-backed call failed. Retry with the SAME Idempotency-Key: a success you missed replays free, a genuine retry bills once.
  • 503 (unavailable) — transient on our side. Back off exponentially (start at 2s) and retry.

Idempotency: on billable endpoints (/itinerary, /itinerary/refine, /video-trip, /photo-trip), send an Idempotency-Key header (any unique string; a UUID per logical request is ideal). Repeats replay the cached response without billing.

The SDK

The official TypeScript client is zero-dependency and works in Node 18+, Deno, Bun, and the browser. Typed responses, typed errors (with retryAfter and unresolved surfaced), and idempotency built in.

npm install jettova

import { Jettova } from "jettova";

const jettova = new Jettova(process.env.JETTOVA_API_KEY);

// Rank fair meeting cities for a scattered group
const { results } = await jettova.meet({
  origins: ["Boston", "Berlin", "Tokyo"],
  sort_by: "fairness",
  limit: 5,
});
console.log(results[0].city); // the fairest place to meet

// Resolve free text first and 422s disappear
const { results: matches } = await jettova.locations("NYC");

// Who owes whom, minimum transactions
const split = await jettova.splitExpenses([
  { payer: "Alice", amount: 320, split_among: ["Alice", "Bob", "Cam"] },
  { payer: "Bob", amount: 75 },
]);

Prefer raw HTTP? Everything on this page works with plain fetch or curl, and the OpenAPI spec generates clients for any other language.

For AI agents: the MCP server

Agents are good at talking about travel and bad at computing it. The jettova-mcp server gives any MCP client (Claude Desktop, Claude Code, agent frameworks) the tools no model can fake: meet, visa_check, split_expenses, packing_list, booking_links, and resolve_locations.

// Claude Code
claude mcp add jettova -e JETTOVA_API_KEY=jk_live_... -- npx -y jettova-mcp

// Claude Desktop (claude_desktop_config.json)
{
  "mcpServers": {
    "jettova": {
      "command": "npx",
      "args": ["-y", "jettova-mcp"],
      "env": { "JETTOVA_API_KEY": "jk_live_..." }
    }
  }
}

Without a key, the meet tool still works through the keyless demo, so an agent can try the engine before you configure anything. There's also a machine-readable overview at /llms.txt.

Recipes

A "where should we meet" Slack command in ~15 lines: take the channel's cities, answer with the three fairest options.

// Slack slash command: /meet Boston, Denver, London
app.command("/meet", async ({ command, ack, respond }) => {
  await ack();
  const origins = command.text.split(",").map((s) => s.trim());
  const { results } = await jettova.meet({ origins, sort_by: "fairness", limit: 3 });
  await respond(
    results
      .map((r) => `${r.rank}. *${r.city}* ~$${r.avg_est_cost_usd}/person, longest leg ${Math.round(r.max_member_flight_hours)}h`)
      .join("\n")
  );
});

Settle a trip's expenses from Python: receipts in, minimum who-pays-whom out.

import requests

r = requests.post(
    "https://www.jettova.com/api/v1/expenses/split",
    headers={"Authorization": "Bearer jk_live_YOUR_KEY"},
    json={
        "expenses": [
            {"payer": "Alice", "amount": 320, "split_among": ["Alice", "Bob", "Cam"]},
            {"payer": "Bob", "amount": 75},
        ]
    },
)
for t in r.json()["transactions"]:
    print(f"{t['from']} pays {t['to']} ${t['amount']}")

Building something else on the engine, a booking tool, an HR offsite planner, a travel newsletter with live rankings? Tell us and we'll help you wire it.

Stability & versioning

  • v1 is stable. Changes to /api/v1 are additive only: new endpoints, new optional fields, new response properties. Existing fields don't change meaning or disappear.
  • Breaking changes get a new namespace. If we ever need one, it ships as /api/v2 and v1 keeps working for at least 6 months after, with the migration documented here.
  • Status: point your monitor at GET /api/v1/health (unauthenticated). Every change lands in the changelog below.

Where the numbers come from

Honesty about data beats false precision, so here's exactly what you're getting:

  • Cost and time figures are directional estimates from a deterministic distance-based fare and duration model over our curated destination pool, tuned against observed fares. They're built for ranking: reliable for "which city is fairest for this group", not for quoting a bookable price. The same inputs always return the same outputs.
  • Live prices come from partners. The booking links carry your users into partner sites with real, current prices. We never present an estimate as a quote.
  • Visa data is the Passport Index dataset, refreshed periodically. Directional, not legal advice.
  • The destination pool is curated (200+ cities with vibe tags and hub flags), which is why rankings stay sane instead of surfacing an airport nobody wants to fly into. GET /destinations is the exact pool.

Changelog

  • 2026-07-28Self-serve opened: keyless playground + demo endpoint, free keys from the dashboard. New: /locations, /meet/batch, /expenses/split, /visa, /packing-list, /booking-links. Official SDK (npm install jettova) and MCP server (npx jettova-mcp). Burst 429s now carry Retry-After; the error schema documents unresolved and quota usage. AI-backed endpoints moved to per-agreement access (ai_not_enabled).
  • 2026-07-20Early access launch: /meet, /destinations, /group-trip, /itinerary, /itinerary/refine, video and photo trip extraction (private preview), OpenAPI spec, key metering and idempotency.

Pricing

All tiers are capped at 60 requests per minute, and quota state rides along in every response's usage object. This is the planned pricing for launch; early-access billing will be by simple monthly invoice, arranged through contact.

TierPriceRequests / monthIncludes
Free$0500Full API. "Powered by Jettova" attribution required in your UI.
Starter$29/mo5,000No attribution requirement.
Growth$99/mo25,000Priority support.
ScaleCustomCustomSLA, contract terms, custom quotas, and revenue-share options on booking links.

Booking links in responses carry Jettova's partner attribution by default. Want your own affiliate IDs in them instead? That's a Scale conversation.

More of the engine is coming

The Meet API is the first piece of Jettova's group travel technology available to developers. Full itinerary generation, video-to-trip, and embeddable group planning rooms are on the roadmap. If one of those would change your product, tell us which — early partners shape the order we ship them.