GET
    /v1/results
    Pro

    Results

    Finishing positions, margins and official starting prices for completed races. Takes the same parameters as Race Form, so any race you fetched form for can be looked up here with the identical query string — fetch a single race, or a whole meeting's card in one call.

    Result Freshness

    On Free and Pro, results are ingested by a pipeline run every 2–3 hours, not streamed in real time. Expect a lag between a race finishing and its result appearing here.

    On Premium and Enterprise, results arrive as races settle rather than waiting for that run, and each early position says how settled it is via resultStage — interim or correct_weight. Scratchings, track condition and weather are live on these tiers too, so a race card reflects a scratching before the batch pipeline has seen it.

    Use the race-level resultStatus field to tell a race that hasn't run yet from one that has run but isn't ingested yet — don't poll in a tight loop waiting for a result to change. A race that exists but has no result yet returns 200 with resultStatus: "pending", not a 404.

    Market Coverage

    Results cover every meeting Race Form covers, across gallops, harness and greyhounds. How a result is sourced varies by market, and so does what it carries:

    • Where the batch pipeline holds the race, positions arrive with margin and, where published, the official startingPrice.
    • Elsewhere the result comes from bookmaker results feeds, which report placings but not margins, so those results carry no margin. Premium sees them as races settle; Pro receives the confirmed (correct_weight) result once a race is two hours past its start with no batch result.

    Coverage deepens as sources are added. Treat margin and startingPrice as optional on every runner.

    Parameters

    datestring
    required
    Race date in YYYY-MM-DD format. Range: 30 days in the past to 7 days in the future.
    trackstring
    required
    Track slug identifier. Supports flexible venue name input.
    raceinteger
    required
    Race number (1-20). Omit on the /meeting variant to return every race at the meeting.
    race_codestring
    optional
    Default: gallops
    Race type: gallops, harness, or greyhounds.
    countrystring
    optional
    Default: au
    Two-letter country code, as for Race Form (see Market Coverage above).
    timezonestring
    optional
    Default: Australia/Sydney
    IANA timezone for date interpretation (e.g. Pacific/Auckland, Asia/Hong_Kong). The date you provide is interpreted in this timezone, so overseas meetings match your local calendar date. Defaults to Australian Eastern time. This scopes which races you get — it does not shift the times in the response. startTime is always UTC regardless of what you pass here; use the timezone response field to render a local post time.

    Two Endpoints

    GET
    /v1/results
    Pro

    A single race. Requires date, track and race. Returns a RaceResult object.

    GET
    /v1/results/meeting
    Pro

    Every race at a meeting, in race-number order — one call instead of looping over race numbers. Omit race. Races still awaiting results appear with resultStatus: "pending" rather than being omitted, so the card is always complete.

    Code Examples

    javascript
    // Single race
    const response = await fetch(
      'https://api.formfav.com/v1/results?' +
      new URLSearchParams({
        date: '2026-07-25',
        track: 'caulfield',
        race: '1'
      }),
      {
        headers: { 'X-API-Key': 'your_api_key_here' }
      }
    );
    
    const race = await response.json();
    
    if (race.resultStatus === 'final') {
      race.placings
        .filter(p => p.position != null)
        .forEach(p => console.log(`${p.position}. ${p.name} (${p.margin})`));
    }

    Result Status

    The race-level resultStatus field tells you how complete a race's result is:

    StatusMeaning
    finalEvery non-scratched runner has a result
    interimResults are partially in — some runners resulted, some not yet
    pendingNo results yet — the race may not have been run, or hasn't been ingested
    abandonedThe race was abandoned — no result will ever exist

    Result Stages

    On Premium and Enterprise, a finishing position can reach you from the real-time feed before the batch pipeline has the race at all. When it does, that runner carries resultStage saying how settled the position is. It is a per-runner field — placings[].resultStage, not a race-level one.

    StageMeaning
    interimProvisional. The finish has been called but stewards have not confirmed it, so the placings can still change.
    correct_weightStewards have confirmed the result. Payouts are settled and the placings will not change.

    Two consequences of it being a fill-in rather than an override:

    • •A runner the batch pipeline has already resulted never carries a stage, and neither does a scratching. Where the two sources both have a position the batch one wins — it comes from the controlling body rather than a bookmaker feed.
    • •A position filled in this way carries no margin. The feed reports the finishing order, not margins; the margin arrives later with the batch result.

    Abridged, showing a race the feed has called but the batch pipeline has not yet ingested. The placed runners carry a stage and no margin; the scratching carries neither:

    200
    Response
    json
    {
      "raceNumber": 4,
      "resultStatus": "final",
      "placings": [
        {
          "number": 6,
          "name": "Torrent Bay",
          "position": 1,
          "scratched": false,
          "resultStage": "correct_weight"
        },
        {
          "number": 2,
          "name": "Second Reading",
          "position": 2,
          "scratched": false,
          "resultStage": "correct_weight"
        },
        {
          "number": 9,
          "name": "Withdrawn Early",
          "scratched": true
        }
      ]
    }

    Although the field sits on each runner, the stage is decided once per race — a feed calls a whole race interim or correct-weight, not individual placings. So every live-filled runner in a race carries the same value, and it is safe to read one runner's stage as the race's. Do not build logic that expects the two to differ within a race.

    Where several feeds report the same race, a stewards-confirmed result is preferred over a provisional one, then the most recent. A feed that disagrees with the others is excluded outright rather than ranked below them, on the grounds that a known-wrong result is worse than none.

    Pro keys never see interim. Their positions come from the batch pipeline or, where it has no result two hours after the start, from the confirmed live result — which carries resultStage correct_weight until a batch position replaces it.

    Corrected and withdrawn results

    A result can change after it first appears: stewards amend the order, a margin arrives after the placings, or an abandonment clears results that were already recorded. Three race-level fields let you tell which copy of a race is the latest and whether its placings were ever altered.

    FieldMeaning
    resultUpdatedAtWhen the result last changed, in UTC. Compare it with the value you stored: a later time means refetch. This is when we recorded the change — the stewards' own publication time is not available to us.
    resultVersionHow many separate writes have touched the result, starting at 1. It orders two copies of the same race. A result normally lands in more than one write (placings, then margins), so a value above 1 does not mean it was corrected.
    resultRevisedtrue once a placing was changed to a different one, or withdrawn, after it had been recorded. false means we hold a change record and nothing was ever altered.

    A race whose result was withdrawn (for example by an abandonment) still reports the time of the withdrawal, with resultRevised: true and no positions. Corrections show on the next request; a settled result is cached for a few minutes only once it has been unchanged for ten.

    Reading a placing

    Two fields tell you everything about a runner: position and scratched. There is no outcome field — see the note below if you were using one.

    What you seeWhat it means
    position presentRan and finished in that position. margin is present too.
    scratched: trueWithdrawn before the race. Never has a position.
    NeitherNo result: either it did not finish, or the result has not landed yet. Use resultStatus to tell them apart.

    Response Fields

    datestring
    Race date
    trackstring
    Track name
    raceNumberinteger
    Race number
    raceNamestring | omitted
    Race name
    distancestring | omitted
    Race distance, e.g. "1200m"
    conditionstring | omitted
    Track condition, e.g. "Good 4"
    weatherstring | omitted
    Weather
    raceClassstring | omitted
    Race class / conditions
    abandonedboolean
    True if the race was abandoned
    startTimestring | omitted
    Scheduled start time in UTC (ISO 8601, e.g. "2026-07-25T02:30:00Z"). Use timezone to convert to local venue time.
    timezonestring | omitted
    Venue timezone (e.g. "Australia/Sydney")
    prizeMoneystring | omitted
    Race prize money
    numberOfRunnersinteger
    Total entries in placings, including scratchings — not the number that contested the race. Count runners whose scratched is false for that.
    resultStatusstring
    final · interim · pending · abandoned (see above). Race-level — distinct from the per-runner resultStage.
    resultUpdatedAtstring | omitted
    When the result last changed (UTC, ISO 8601). Our recording time, not the stewards’. Omitted when we hold no change record — see Corrected and withdrawn results.
    resultVersioninteger | omitted
    Number of separate writes that touched the result, from 1. Higher is later; above 1 does not by itself mean corrected. Omitted under the same conditions as resultUpdatedAt, and always on a result served from the real-time feed.
    resultRevisedboolean | omitted
    true when a placing was changed or withdrawn after being recorded.
    placingsarray
    Runners, ordered by finishing position, then non-finishers, then unresulted, then scratchings
    placings[].numberinteger
    Runner number
    placings[].namestring
    Runner name
    placings[].jockeystring | omitted
    Jockey / driver name
    placings[].trainerstring | omitted
    Trainer name
    placings[].weightnumber | omitted
    Allocated weight (kg)
    placings[].claimnumber | omitted
    Apprentice claim / allowance (kg), thoroughbreds only. Omitted for the majority of runners, who carry no claim.
    placings[].barrierinteger | omitted
    Barrier draw / box number. For harness, the place within the runner's row (startRow) or at its handicap mark (handicapMetres).
    placings[].startRowstring | omitted
    Harness only: the row the runner starts from, front or second. Together with barrier it is the start — second with barrier 1 is Sr1. Omitted for back-markers in a standing start and outside harness.
    placings[].unrulyboolean | omitted
    Harness only: true when the runner is drawn in an unruly place in its row or at its mark (FrU3, 20m U1).
    placings[].handicapMetresinteger | omitted
    Harness standing starts: metres behind the front mark (e.g. 20); barrier is then the place at that mark. Omitted on mobile starts and for front-mark runners.
    placings[].positioninteger | omitted
    Finishing position, 1 = winner. Present only for runners that completed the race
    placings[].marginstring | omitted
    Cumulative margin behind the winner. Greyhounds carry a metres suffix ("4.25m"); gallops and harness are lengths ("1.7"). Winner reads "0"/"0.0"
    placings[].scratchedboolean
    Whether the runner was scratched
    placings[].startingPricenumber | omitted
    Official starting price as decimal odds — 4.50 means $4.50 returned on a $1 win stake, stake included. Coverage is partial — see the note below.
    placings[].resultStagestring | omitted
    interim or correct_weight. Premium and Enterprise only, and present only while the real-time feed is ahead of the batch pipeline — see Result Stages. Absence means the position is settled, not unconfirmed.
    placings[].pricesarray | omitted
    Live bookmaker prices for this runner, identical in shape to the Live Prices endpoint’s runners[].prices. Premium and Enterprise only — absent on other tiers, and absent for a runner with no quote worth serving. Not the same thing as startingPrice above.
    dividendsarray | omitted
    Declared tote dividends for the race, exotics included. Premium and Enterprise only. Race-level, not per runner, because an exotic pays on a combination of runners. Omitted until the pool has paid, and for races the tote does not cover — see Tote dividends.
    dividends[].poolstring
    Win · Place · Quinella · Exacta · Duet · Trifecta · FirstFour
    dividends[].combinationinteger[]
    Winning runner numbers. In finishing order for Exacta, Trifecta and FirstFour, where the order is the bet. A dead heat produces one entry per paying combination.
    dividends[].dividendnumber
    Return on a $1 unit, stake included
    dividends[].operatorstring
    Tote operator whose pool paid it, e.g. TAB
    dividends[].jurisdictionstring
    Which of the operator’s pools paid it, e.g. VIC. Pools pay differently for the same result, so a dividend is only meaningful with its pool.

    The /results/meeting variant wraps a list of these race objects: date, track, slug, country, raceType, abandoned, and a races array.

    Tote dividends

    On Premium and Enterprise, each race carries dividends: every declared single-race tote pool, including the exotics — Quinella, Exacta, Duet, Trifecta and FirstFour — alongside Win and Place. They arrive once the operator declares them, typically within minutes of correct weight, and never change after that.

    A dividend belongs to a pool

    Tote pools are separate and pay differently for the same result — one exacta on the same race paid $7.20, $17.90 and $17.20 across three state pools. Every dividend therefore carries operator and jurisdiction. Today these are TAB’s VIC pool; read the label rather than assuming it.

    Multi-race pools (Quaddie, Running Double, Treble and so on) are not included: their combinations name runners in other races.

    Coverage follows the tote: dividends appear where it runs a pool on the race, and races it does not sell carry no dividends key. Like startingPrice, a dividend is decimal and includes the stake.

    Starting price

    startingPrice is the official starting price as decimal odds, taken from the racing body's own published result — not from a wagering operator. It is settled after the race and never moves.

    It is not a live or pre-race price. There is no price movement, no per-bookmaker breakdown, and no market data before a race is run. If you need those, this endpoint is not the right source.

    Coverage is partial — plan for absence

    The key is omitted entirely for any runner we do not hold a price for, and that is common today. Coverage is still filling and varies by market, so treat the key as optional everywhere.

    Two runners in the same race can legitimately differ in whether they carry one. Do not treat an absent price as a scratching, a zero, or an error — and do not compute a field-wide average from a race unless every runner in it has a price.

    Prices are decimal odds including stake, so a $1 win bet on a runner at 4.50 returns $4.50 in total, not $5.50. Winners are typically far shorter than the field average, which is a useful sanity check if you are ingesting these at scale.

    Example Response

    200
    Response
    json
    {
      "date": "2026-07-25",
      "track": "Caulfield",
      "raceNumber": 1,
      "raceName": "Le Pine Funerals Handicap",
      "distance": "1200m",
      "condition": "Good 4",
      "weather": "Fine",
      "raceClass": "BM70",
      "abandoned": false,
      "startTime": "2026-07-25T02:30:00Z",
      "timezone": "Australia/Melbourne",
      "prizeMoney": "130000",
      "numberOfRunners": 10,
      "resultStatus": "final",
      "placings": [
        {
          "number": 3,
          "name": "Custom",
          "jockey": "J. McDonald",
          "trainer": "C. Waller",
          "weight": 58.5,
          "barrier": 4,
          "position": 1,
          "margin": "0.0",
          "scratched": false,
          "startingPrice": 3.4
        },
        {
          "number": 7,
          "name": "Sun Setting",
          "jockey": "M. Dee",
          "trainer": "G. Waterhouse",
          "weight": 56.0,
          "barrier": 2,
          "position": 2,
          "margin": "3.0",
          "scratched": false
        },
        {
          "number": 9,
          "name": "Late Withdrawal",
          "trainer": "P. Moody",
          "barrier": 8,
          "scratched": true
        }
      ]
    }

    Related