TRAINTRACKER API

Submit train sightings programmatically from railcams and other automated sources.

Getting Started

To use the TrainTracker API, you need an API key. Contact us at api@traintrackerapp.com to request access.

Include your API key in every request using the X-API-Key header. Your key will start with tt_.

Authentication

# Include your API key in the header
X-API-Key: tt_your_key_here

Submit a Sighting

POST/api/v1/sightings

Request Body

{
  "locomotive_entries": [
    {
      "number": "4014",
      "railroad": "BNSF"
    },
    {
      "number": "7226",
      "railroad": "UP"
    }
  ],
  "railroad": "BNSF",
  "train_symbol": "Q-CHILA1",
  "direction": "E",
  "description": "Spotted on railcam",
  "photo_url": "https://example.com/photo.jpg",
  "latitude": 41.05,
  "longitude": -95.97,
  "location_name": "Galesburg, IL",
  "sighted_at": "2026-06-11T12:00:00Z",
  "axle_count": 450,
  "milepost": 152.3
}

Fields

FieldTypeRequiredDescription
locomotive_entriesarrayRequiredOrdered list of locomotives, leading engine first. Each entry: { number, railroad }. If railroad is omitted on an entry, it defaults to the top-level railroad.
railroadstringOptionalDefault railroad short name (e.g. BNSF, UP, CSX). Used for entries without a railroad specified. Falls back to API key default.
latitudenumberRequiredLatitude of the camera/sighting location.
longitudenumberRequiredLongitude of the camera/sighting location.
train_symbolstringTrain symbol (e.g. Q-CHILA1). Max 50 characters. Auto-normalized.
directionstringDirection of travel: N, NE, E, SE, S, SW, W, NW
descriptionstringDescription text. Max 500 characters.
photo_urlstringURL to a photo of the sighting.
youtube_urlstringYouTube video URL (e.g. livestream clip). Max 500 characters.
location_namestringHuman-readable location name (e.g. "Belen, NM"). Recommended.
sighted_atstringISO 8601 timestamp. Defaults to current time. Cannot be in the future.
axle_countintegerTotal axle count of the train. Must be between 1 and 9999.
milepostnumberMilepost marker at the sighting location (e.g. 152.3).
parent_sighting_idstringUUID of a previous sighting to link as a re-spot.
tag_idsarrayArray of tag UUIDs to apply to the sighting.

Response

{
  "id": "uuid-of-new-sighting",
  "status": "created"
}

Locomotive Entries

The locomotive_entries array is how you submit locomotive numbers. List them in order, with the leading engine first.

Entry Fields

FieldTypeRequiredDescription
numberstringRequiredThe locomotive number (e.g. "4014").
railroadstringOptionalRailroad short name for this locomotive (e.g. "UP"). If omitted, defaults to the top-level railroad field.

Single Railroad

When all locomotives belong to the same railroad, set railroad at the top level. You don't need to repeat it on each entry.

{
  "railroad": "BNSF",
  "locomotive_entries": [
    {
      "number": "4014"
    },
    {
      "number": "7226"
    },
    {
      "number": "1221"
    }
  ]
}

Mixed Railroads (Foreign Power)

When locomotives from different railroads are in the consist, specify railroad on each entry that differs from the top-level railroad.

{
  "railroad": "BNSF",
  "locomotive_entries": [
    {
      "number": "4014"
    },
    {
      "number": "8102",
      "railroad": "UP"
    },
    {
      "number": "3288",
      "railroad": "CSX"
    }
  ]
}

In this example, 4014 is BNSF (from the top-level railroad), while 8102 is UP and 3288 is CSX.

Example (cURL)

curl -X POST \ https://www.traintrackerapp.com/api/v1/sightings \ -H "Content-Type: application/json" \ -H "X-API-Key: tt_your_key_here" \ -d '{ "locomotive_entries": [ { "number": "4014", "railroad": "BNSF" }, { "number": "7226" } ], "railroad": "BNSF", "direction": "W", "latitude": 34.66, "longitude": -106.78, "location_name": "Belen, NM", "sighted_at": "2026-09-01T15:30:00Z" }'

Upload a Photo

POST/api/v1/upload

Upload a photo and receive a URL to include in your sighting submission. Send as multipart/form-data.

Request

FieldTypeDescription
filefileImage file (jpg, jpeg, png, webp, gif). Max 10MB.

Response

{
  "url": "https://...supabase.co/storage/v1/object/public/train-sighting-photos/..."
}

Example

# Step 1: Upload photo curl -X POST \ https://www.traintrackerapp.com/api/v1/upload \ -H "X-API-Key: tt_your_key_here" \ -F "file=@photo.jpg" # Step 2: Use the returned URL in your sighting curl -X POST \ https://www.traintrackerapp.com/api/v1/sightings \ -H "Content-Type: application/json" \ -H "X-API-Key: tt_your_key_here" \ -d '{ "locomotive_entries": [{ "number": "4014" }], "railroad": "BNSF", "photo_url": "https://...the-url-from-step-1...", "latitude": 34.66, "longitude": -106.78 }'

Response Codes

StatusMeaning
201Sighting created successfully
400Invalid request (missing fields, bad data)
401Invalid or missing API key
429Rate limit exceeded
500Server error

Rate Limits

Default rate limit is 60 requests per hour. This can be adjusted per API key based on your needs. Contact us if you need a higher limit.

Contact

Questions about the API? Contact us at api@traintrackerapp.com