Developers
The catalog,
as JSON.
Read the catalog from your own app. Same data as the site, same rule: a value is present only when the breeder published it.
https://straintrackr.com/api/v1Access
Authentication
Anonymous requests are allowed at 60 requests per minute per IP. That is enough to try every endpoint before deciding you want a key.
A key raises the limit. Send it as X-Api-Key: … or Authorization: Bearer …. Keys are stored hashed; the plaintext exists only in the email that delivered it.
Request a key by emailing jberardi@gmail.com with what you are building.
curl -H "X-Api-Key: YOUR_KEY" "https://straintrackr.com/api/v1/strains?breeder=fast-buds"Reference
Endpoints
All GET. Paged lists default to 24 per page.
| Endpoint | Returns | Params |
|---|---|---|
GET /strains | Search and filter the catalog. Paged. | q, breeder, type, seedKind, growthType, thc=low|med|high, cbd=low|med|high, letter, hasImage, hasLineage, hasThc, sort=name|thc|flowering|newest|breeder|views, page, perPage (max 100) |
GET /strains/{slug} | One strain: specs, per-field sources, parents, crosses and similar strains. | — |
GET /breeders | Every breeder with at least one listed strain. | — |
GET /breeders/{slug} | One breeder: profile, catalog stats and a page of their strains. | Same list params as /strains |
GET /stats | Catalog totals and coverage percentages. | — |
GET /export/strains.json | The full catalog in one file. ETag + 304, cached hourly. | — |
curl "https://straintrackr.com/api/v1/strains?q=gelato&thc=high&sort=thc&perPage=10"curl "https://straintrackr.com/api/v1/strains/gelato-41"curl "https://straintrackr.com/api/v1/breeders"curl "https://straintrackr.com/api/v1/breeders/royal-queen-seeds?sort=flowering"curl "https://straintrackr.com/api/v1/stats"curl -H 'If-None-Match: "v1-…"' "https://straintrackr.com/api/v1/export/strains.json"Shape
A strain, as returned
Abridged. Keys the breeder did not publish are simply not there.
{
"id": "royal-queen-seeds--gelato",
"name": "Gelato",
"breeder": "Royal Queen Seeds",
"breederSlug": "royal-queen-seeds",
"slug": "gelato",
"pageUrl": "https://straintrackr.com/strain/gelato",
"type": "hybrid",
"growthType": "photoperiod",
"seedType": "feminized",
"genetics": "Sunset Sherbet x Thin Mint Girl Scout Cookies",
"thcMin": 20, "thcMax": 24,
"floweringMinDays": 56, "floweringMaxDays": 63,
"heightMinCm": 80, "heightMaxCm": 120,
"yieldMinIndoor": 450, "yieldMaxIndoor": 500,
"imageUrl": "https://…/gelato.jpg",
"source": "https://www.royalqueenseeds.com/…/gelato",
"sources": [
{ "field": "thc", "sourceUrl": "https://…", "sourceTier": "breeder", "extractor": "bespoke", "fetchedAt": "…" }
],
"parents": [
{ "position": 1, "parentNameRaw": "Sunset Sherbet", "parent": { "slug": "…", "name": "…", "breeder": "…" }, "grandparents": [] }
],
"crossesTotal": 3
}Semantics
Field semantics
| Field | Meaning |
|---|---|
Absent field | The breeder did not publish it. Never null-as-zero, never estimated. Check for the key, not its value. |
Numeric ranges | Min/max pairs: thcMin/thcMax, floweringMinDays/floweringMaxDays, heightMinCm/heightMaxCm, and so on. A single figure sets both. |
thcMin, thcMax, cbdMin, cbdMax | Percent, as the breeder states it. A claim, not a lab result. |
floweringMinDays, seedToHarvestMinDays | Days. Weeks on the breeder page are converted. |
heightMinCm, heightMinOutdoorCm | Centimetres. |
yieldMinIndoor | Grams per square metre. |
yieldMinOutdoor, yieldMinPerPlant | Grams per plant. |
genetics | The cross exactly as the breeder wrote it. Parsed parents are in `parents` where they could be split. |
source | The breeder page every value on the row was read from. |
Sibling app
Using it in GrowTrackr
GrowTrackr bundles the export as its offline strain library, so the app works without a network and the catalog still carries every source URL. Refresh it with:
npm run strains:pullThe script sends the last ETag; an unchanged catalog answers 304 and nothing is downloaded.
Contract
Versioning, CORS and limits
| Version | Every response carries X-API-Version: 1. Additive changes (new fields, new params) never bump it; only a change to an existing response shape does, and that ships as a new path. |
|---|---|
| CORS | Enabled for every origin, so a browser app can call the API directly. Preflight is answered on OPTIONS. |
| Rate limits | X-RateLimit-Remaining on every response. Over the limit you get 429 with Retry-After: 60. |
| Errors | JSON { "error": "…", "code": "not_found" | "rate_limited" | "invalid_key" | "internal" }. |
| Caching | The export is cached for an hour and supports If-None-Match. Other endpoints are live. |