Y2 Elite workspaces are rolling out for teams
Y2Y2Docs

Integration Recipes

Build grounded agents, GIS imports, warehouse syncs, and durable intelligence pipelines

Use these recipes when you already have a Y2 API key and need a production-shaped integration. They use the canonical schemas in the OpenAPI document. Keep Y2_API_KEY and webhook secrets in server-side environment variables.

GoalStart with
Ground an agent answerReport metadata and Markdown
Import data into a map or spatial analysis toolGeoJSON FeatureCollection
Synchronize an analytics warehousePaginated Intel v2 JSON
Keep another system currentChange feed and webhooks

Persist public IDs such as rpt_... and prf_.... Legacy Convex document IDs are not a stable integration contract.

Ground an agent with a report and its sources

Fetch the report's structured source metadata and canonical Markdown representation in parallel. Retry only rate-limit and server failures, and retain requestId when logging a Problem Details response.

const authorization = `Bearer ${process.env.Y2_API_KEY}`;
const reportId = "rpt_0123456789abcdef01234567";
const reportUrl = `https://api.y2.dev/api/v1/reports/${reportId}`;

async function requireSuccess(response: Response) {
  if (response.ok) return response;

  const problem = await response.json();
  const error = new Error(
    `${problem.code}: ${problem.detail} (request ${problem.requestId})`,
  );
  Object.assign(error, {
    retryable: problem.status === 429 || problem.status >= 500,
    problem,
  });
  throw error;
}

const [metadataResponse, markdownResponse] = await Promise.all([
  fetch(`${reportUrl}?include=sources&view=agent`, {
    headers: { Authorization: authorization },
  }),
  fetch(reportUrl, {
    headers: { Authorization: authorization, Accept: "text/markdown" },
  }),
]);

const [metadata, markdown] = await Promise.all([
  requireSuccess(metadataResponse).then((response) => response.json()),
  requireSuccess(markdownResponse).then((response) => response.text()),
]);

answerWithCitations(
  markdown,
  metadata.data.sources.map((source: { url: string }) => source.url),
);

The repository checks its generated TypeScript definitions with:

bun run generate:api-types
bun run check:api-types

External TypeScript projects can generate their own types from https://y2.dev/api/openapi.yaml with an OpenAPI 3.1-compatible generator.

Import regional intelligence into a GIS

Request GeoJSON from the regional OSINT endpoint. Coordinates use WGS 84 longitude-first order: [longitude, latitude].

import os
import requests

response = requests.get(
    "https://api.y2.dev/api/v1/osint/regional",
    headers={
        "Authorization": f"Bearer {os.environ['Y2_API_KEY']}",
        "Accept": "application/geo+json",
    },
    params={
        "bbox": "-122.5,32.5,-96.8,49.2",
        "datetime": "2026-07-01T00:00:00Z/2026-07-21T23:59:59Z",
        "limit": 200,
    },
    timeout=30,
)
response.raise_for_status()

feature_collection = response.json()
assert feature_collection["type"] == "FeatureCollection"
assert all(
    feature["type"] == "Feature"
    for feature in feature_collection["features"]
)

next_page = next(
    (
        link["href"]
        for link in feature_collection.get("links", [])
        if link.get("rel") == "next"
    ),
    None,
)

The bounding box filter excludes records without matching coordinates. Open the returned FeatureCollection directly in QGIS or ArcGIS, or load it with GeoPandas. Follow the item in links whose rel is next to retrieve another page.

Sync financial intelligence into a warehouse

The semantic /api/v2/finint collection returns JSON with pagination in meta.page and links.next. Merge on the canonical public id, and treat the returned next link as an opaque, filter-bound continuation.

import os
from urllib.parse import urljoin

import requests

origin = "https://api.y2.dev"
url = f"{origin}/api/v2/finint?limit=500"
headers = {"Authorization": f"Bearer {os.environ['Y2_API_KEY']}"}

while url:
    response = requests.get(url, headers=headers, timeout=30)
    response.raise_for_status()
    page = response.json()

    warehouse.merge("y2_finint_facts", page["data"], key="id")

    next_link = page["links"]["next"]
    url = urljoin(origin, next_link) if next_link else None

/api/v2/finint does not offer NDJSON. If a row-oriented stream is required, use /api/v1/osint/finint?format=ndjson; that endpoint returns at most 100 rows per request and exposes continuation in X-Y2-Next-Cursor.

Model semantic fields as facts and dimensions. Do not make deprecated epoch aliases or raw provider payloads part of the warehouse contract.

Build a durable change pipeline

Use webhooks for low-latency report notifications and /api/v2/changes to detect missed or updated resources. A bearer API key receives global intelligence changes plus changes belonging to its own user or workspace; it does not receive another tenant's records.

async function ingestChanges(savedWatermark?: string) {
  const first = new URL("https://api.y2.dev/api/v2/changes");
  first.searchParams.set("limit", "500");
  if (savedWatermark) first.searchParams.set("watermark", savedWatermark);

  let next: URL | null = first;
  while (next) {
    const response = await requireSuccess(
      await fetch(next, {
        headers: { Authorization: `Bearer ${process.env.Y2_API_KEY}` },
      }),
    );
    const page = await response.json();

    // Implement this as one transaction: apply every resource change or
    // tombstone, then persist the page watermark as the final write.
    await applyPageAtomically(page.data, page.meta.watermark);

    next = page.links.next
      ? new URL(page.links.next, "https://api.y2.dev")
      : null;
  }
}

The watermark is an exclusive checkpoint. Reusing the last committed value after a failure is safe; saving it before resource writes commit can lose changes. A tombstone instructs the consumer to remove or retire the named resource.

For webhook requests, verify HMAC-SHA256 over the exact raw request bytes before parsing JSON. Deduplicate on the CloudEvent id (also sent as Idempotency-Key and X-Y2-Event-Id) before performing side effects, and acknowledge a duplicate with a 2xx response.

Y2 does not run an automatic retry loop for failed webhook deliveries. Use the change feed as the repair path; five consecutive delivery failures disable the webhook configuration.