SchemaCheck/ DocsDashboard

SDK Examples

JavaScript / TypeScript

Works in Node.js 18+, Bun, Deno, and modern browsers. Uses the native fetch API — no dependencies required.

Installation

bash
# No dependencies required — uses the native fetch API
# Node 18+, Bun, Deno, or any modern browser

TypeScript types

Copy these into a types.ts file in your project for full type safety.

typescript
// TypeScript types for the SchemaCheck API
export interface SchemaCheckResult {
  success: boolean;
  url?: string;
  schemas_found: number;
  schemas: Schema[];
  summary: Summary;
  meta: Meta;
}

export interface Schema {
  type: string;
  valid: boolean;
  rich_result_eligible: boolean;
  deprecated: boolean;
  deprecation_note: string | null;
  errors: Issue[];
  warnings: Issue[];
  properties_found: string[];
  properties_missing_required: string[];
  properties_missing_recommended: string[];
  rich_result: RichResult;
}

export interface Issue {
  severity: "error" | "warning";
  property: string;
  message: string;
  fix: string;
  google_docs_url: string;
}

export interface RichResult {
  eligible: boolean;
  reason: string;
  google_docs_url: string;
}

export interface Summary {
  total_schemas: number;
  valid_schemas: number;
  invalid_schemas: number;
  total_errors: number;
  total_warnings: number;
  rich_result_eligible: number;
  score: number;
}

export interface Meta {
  api_version: string;
  validated_at: string;
  cached: boolean;
  credits_used: number;
  credits_remaining: number;
  response_time_ms: number;
}

export interface SchemaCheckError {
  success: false;
  error: {
    code: string;
    message: string;
    docs_url: string;
    upgrade_url?: string;
    retry_after_seconds?: number;
  };
}

Validate a URL

Fetch and validate all JSON-LD on any public URL. Results are cached for 1 hour — the second call for the same URL is free.

typescript
import type { SchemaCheckResult, SchemaCheckError } from "./types";

const API_KEY = process.env.SCHEMACHECK_API_KEY!;
const BASE_URL = "https://schemacheck.dev/api/v1";

// Validate a URL — pass access_key as query param (no headers needed for GET)
export async function validateUrl(url: string): Promise<SchemaCheckResult> {
  const endpoint = `${BASE_URL}/validate?url=${encodeURIComponent(url)}&access_key=${API_KEY}`;
  const res = await fetch(endpoint);
  const data: SchemaCheckResult | SchemaCheckError = await res.json();

  if (!res.ok || !data.success) {
    const err = (data as SchemaCheckError).error;
    throw new Error(`[${err.code}] ${err.message}`);
  }

  return data as SchemaCheckResult;
}

// Example usage
const result = await validateUrl("https://stripe.com");

console.log(`Score: ${result.summary.score}/100`);
console.log(`Schemas found: ${result.schemas_found}`);

for (const schema of result.schemas) {
  console.log(`\n[${schema.type}] valid=${schema.valid} richResult=${schema.rich_result_eligible}`);

  for (const error of schema.errors) {
    console.log(`  ✗ ${error.property}: ${error.message}`);
    console.log(`    Fix: ${error.fix}`);
  }

  for (const warning of schema.warnings) {
    console.log(`  ⚠ ${warning.property}: ${warning.message}`);
  }
}

Validate raw JSON-LD

POST the JSON-LD object directly — perfect for CI pipelines or validating markup before publishing. Raw JSON-LD is never cached.

typescript
import type { SchemaCheckResult } from "./types";

const API_KEY = process.env.SCHEMACHECK_API_KEY!;

// Validate raw JSON-LD — useful during development before publishing
export async function validateJsonLd(jsonld: object): Promise<SchemaCheckResult> {
  const res = await fetch("https://schemacheck.dev/api/v1/validate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": API_KEY,
    },
    body: JSON.stringify({ jsonld }),
  });

  const data = await res.json();

  if (!res.ok || !data.success) {
    throw new Error(`[${data.error.code}] ${data.error.message}`);
  }

  return data;
}

// Example: validate Article schema before publishing a blog post
const result = await validateJsonLd({
  "@context": "https://schema.org",
  "@type": "Article",
  headline: "How to validate Schema.org structured data",
  author: { "@type": "Person", name: "Jane Doe" },
  datePublished: "2026-03-18",
  image: "https://example.com/photo.jpg",
});

if (!result.schemas[0].valid) {
  console.error("Schema has errors — fix before publishing:");
  result.schemas[0].errors.forEach((e) => console.error(`  ${e.property}: ${e.fix}`));
  process.exit(1);
}

console.log("Schema is valid and rich-result eligible:", result.schemas[0].rich_result_eligible);

Batch validation

Validate many URLs in parallel with a configurable concurrency limit so you don't hit rate limits.

typescript
import type { SchemaCheckResult } from "./types";

const API_KEY = process.env.SCHEMACHECK_API_KEY!;

async function validateUrl(url: string): Promise<SchemaCheckResult> {
  const res = await fetch(
    `https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(url)}&access_key=${API_KEY}`
  );
  const data = await res.json();
  if (!res.ok) throw new Error(`[${data.error?.code}] ${data.error?.message}`);
  return data;
}

// Validate multiple URLs with concurrency control
async function validateBatch(
  urls: string[],
  concurrency = 5
): Promise<Map<string, SchemaCheckResult | Error>> {
  const results = new Map<string, SchemaCheckResult | Error>();

  for (let i = 0; i < urls.length; i += concurrency) {
    const batch = urls.slice(i, i + concurrency);
    const settled = await Promise.allSettled(batch.map((url) => validateUrl(url)));

    for (let j = 0; j < batch.length; j++) {
      const s = settled[j];
      results.set(batch[j], s.status === "fulfilled" ? s.value : s.reason);
    }
  }

  return results;
}

// Example
const urls = [
  "https://stripe.com",
  "https://github.com",
  "https://shopify.com",
  "https://airbnb.com",
];

const results = await validateBatch(urls);

for (const [url, result] of results) {
  if (result instanceof Error) {
    console.log(`${url} → ERROR: ${result.message}`);
  } else {
    console.log(`${url} → score ${result.summary.score}/100`);
  }
}

Retry with backoff

Production-ready wrapper with exponential backoff for server errors and automatic Retry-After handling for rate limits.

typescript
async function validateWithRetry(
  url: string,
  apiKey: string,
  maxRetries = 3
): Promise<SchemaCheckResult> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const res = await fetch(
      `https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(url)}&access_key=${apiKey}`
    );
    const data = await res.json();

    if (res.ok) return data;

    const code = data.error?.code;

    // Never retry client errors (except rate limits)
    if (res.status >= 400 && res.status < 500 && code !== "rate_limit_exceeded") {
      throw new Error(`[${code}] ${data.error.message}`);
    }

    if (code === "rate_limit_exceeded") {
      const retryAfter = Number(res.headers.get("Retry-After") ?? 60);
      console.warn(`Rate limited. Waiting ${retryAfter}s…`);
      await new Promise((r) => setTimeout(r, retryAfter * 1000));
      continue;
    }

    // Exponential backoff for 5xx: 1s, 2s, 4s
    if (attempt < maxRetries) {
      const delay = 2 ** (attempt - 1) * 1000;
      console.warn(`Attempt ${attempt} failed. Retrying in ${delay}ms…`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  throw new Error("Max retries exceeded");
}

Next.js server route

Keep your API key server-side by proxying requests through a Next.js route handler.

typescript
// app/api/schema-check/route.ts — server-side proxy for Next.js
import { NextRequest, NextResponse } from "next/server";

export async function GET(req: NextRequest) {
  const url = req.nextUrl.searchParams.get("url");
  if (!url) return NextResponse.json({ error: "url is required" }, { status: 400 });

  const res = await fetch(
    `https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(url)}&access_key=${process.env.SCHEMACHECK_API_KEY}`
  );

  const data = await res.json();
  return NextResponse.json(data, { status: res.status });
}

Next steps