betaStrainTrackr

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.

Base URLhttps://straintrackr.com/api/v1

Access

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.

EndpointReturnsParams
GET /strainsSearch 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 /breedersEvery 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 /statsCatalog totals and coverage percentages.
GET /export/strains.jsonThe full catalog in one file. ETag + 304, cached hourly.
GET /strains
curl "https://straintrackr.com/api/v1/strains?q=gelato&thc=high&sort=thc&perPage=10"
GET /strains/{slug}
curl "https://straintrackr.com/api/v1/strains/gelato-41"
GET /breeders
curl "https://straintrackr.com/api/v1/breeders"
GET /breeders/{slug}
curl "https://straintrackr.com/api/v1/breeders/royal-queen-seeds?sort=flowering"
GET /stats
curl "https://straintrackr.com/api/v1/stats"
GET /export/strains.json
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

FieldMeaning
Absent fieldThe breeder did not publish it. Never null-as-zero, never estimated. Check for the key, not its value.
Numeric rangesMin/max pairs: thcMin/thcMax, floweringMinDays/floweringMaxDays, heightMinCm/heightMaxCm, and so on. A single figure sets both.
thcMin, thcMax, cbdMin, cbdMaxPercent, as the breeder states it. A claim, not a lab result.
floweringMinDays, seedToHarvestMinDaysDays. Weeks on the breeder page are converted.
heightMinCm, heightMinOutdoorCmCentimetres.
yieldMinIndoorGrams per square metre.
yieldMinOutdoor, yieldMinPerPlantGrams per plant.
geneticsThe cross exactly as the breeder wrote it. Parsed parents are in `parents` where they could be split.
sourceThe 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:pull

The script sends the last ETag; an unchanged catalog answers 304 and nothing is downloaded.

Contract

Versioning, CORS and limits

VersionEvery 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.
CORSEnabled for every origin, so a browser app can call the API directly. Preflight is answered on OPTIONS.
Rate limitsX-RateLimit-Remaining on every response. Over the limit you get 429 with Retry-After: 60.
ErrorsJSON { "error": "…", "code": "not_found" | "rate_limited" | "invalid_key" | "internal" }.
CachingThe export is cached for an hour and supports If-None-Match. Other endpoints are live.