API documentation.

The FerroYard Public API serves a yard's published listings to any website or system — photos, specs, pricing and seller info, ready to render on a public inventory page.

Overview

  • Base URL: https://ferroyard.com — all endpoints under /api/v1/, JSON in and out.
  • Only listings that are ACTIVE and marked published to public site are exposed. Drafts, internal records and hidden photos never appear.
  • The API is tenant-scoped by token: your token only ever returns your workspace's listings. There is no tenant parameter to get wrong.
  • Read-only — tokens cannot create or modify anything.

Authentication

Generate a token in Settings → Workflow → Integrations(you'll need a FerroYard account with settings access). The full token — fy_live_… — is shown once; FerroYard stores only a hash. Send it on every request:

Authorization: Bearer fy_live_YOUR_TOKEN

Call the API from your server, not from visitors' browsers — a token in client-side JavaScript is public. Typical pattern:

// Next.js example — keep the token on YOUR server, never in browser code
export default async function InventoryPage() {
  const res = await fetch('https://ferroyard.com/api/v1/inventory?limit=24', {
    headers: { Authorization: `Bearer ${process.env.FERROYARD_API_TOKEN}` },
    next: { revalidate: 300 }, // cache 5 minutes
  });
  const { data } = await res.json();
  return <Listings items={data} />;
}

In this pattern the token lives in a server environment variable and the fetch runs during server rendering — visitors only ever receive the finished HTML; the token never reaches their browser. Avoid NEXT_PUBLIC_ variables or browser-side fetches for the token: anything shipped to the browser can be read by anyone.

If a token does leak, the exposure is limited by design: it is read-only and only returns listings already publicly displayed on your website. Revoke it instantly from the same settings page (revoked tokens are rejected on the very next request); usage is metered per day.

List listings

GET /api/v1/inventory

ParameterTypeDescription
pageintegerPage number, default 1
limitintegerItems per page, default 20, max 100
statusstringDefault ACTIVE. Pass SOLD for published sold units (a "recently sold" section)
categorystringCategory slug, e.g. excavators
searchstringMatches title, make, model and stock number
curl "https://ferroyard.com/api/v1/inventory?page=1&limit=20&category=excavators" \
  -H "Authorization: Bearer fy_live_YOUR_TOKEN"

Response — summaries with the primary public photo:

{
  "success": true,
  "data": [
    {
      "id": "cmq…",
      "stockNumber": "FY-0001",
      "slug": "2019-caterpillar-336-excavator-ab-fy-0001",
      "title": "2019 Caterpillar 336 Excavator",
      "listPrice": 215000,
      "currency": "CAD",
      "status": "ACTIVE",
      "year": 2019,
      "manufacturer": "Caterpillar",
      "model": "336",
      "specifications": { "hours": "4,100", "undercarriage": "80%" },
      "category": { "id": "cmq…", "name": "Excavators", "slug": "excavators" },
      "location": { "name": "Edmonton Yard", "province": "AB" },
      "description": "…",
      "primaryPhoto": { "url": "https://…", "thumbnailUrl": "https://…", "altText": null },
      "featured": false,
      "seller": { "name": "Foothills Mining Corp", "location": "Edmonton, AB" },
      "createdAt": "2026-06-11T…", "updatedAt": "2026-06-12T…"
    }
  ],
  "pagination": { "page": 1, "limit": 20, "total": 42, "totalPages": 3, "hasNext": true, "hasPrev": false }
}

Get one listing

GET /api/v1/inventory/{idOrSlug} — accepts the listing id, stockNumber or slug interchangeably, so your listing-page URLs can use whichever you prefer.

# by id, stock number, or slug — all three work
curl "https://ferroyard.com/api/v1/inventory/FY-0001" \
  -H "Authorization: Bearer fy_live_YOUR_TOKEN"

The detail adds to the summary shape: photos (all public photos, primary first, with full-size and thumbnail URLs), photoCount, listingText (the written ad copy), specificationTemplate(the category's field layout, handy for rendering spec tables), videoUrl and dateListedOnline/dateSold.

Errors

StatusMeaning
401Missing, invalid, revoked or expired token
403Origin not allowed for this token, or the token lacks the inventory:read scope
404Listing not found — or exists but isn't published
500Server error — safe to retry

Every error body has the same shape: { "success": false, "error": "…" }

CORS & security

  • Server-to-server calls (recommended) need no CORS setup and always work — even when an origin allow-list is configured.
  • For browser calls, a token can be restricted to specific origins in Settings → Workflow → Integrations (bare domains; www. is matched automatically; wildcards like *.example.com supported) — browser requests from other origins get 403. Without a restriction list, all origins are accepted.
  • Tokens are stored hashed (SHA-256) and can be revoked at any time.

OpenAPI spec

A machine-readable OpenAPI 3.0 description lives at /api/v1/openapi — point your code generator or API client (Postman, Insomnia, Bruno) at it.