Run the Timelines query over many locations and deliver the result as a file to your own bucket, once or on a schedule.
Bulk Timelines runs the Timelines query for many locations at once. The result is delivered as one file to your own bucket. You can run an order once (on demand) or on a cron schedule.
Orders and runsAn order is the stored configuration: the locations, fields, time window, format, destination and an optional schedule. Its ID is
orderId. You can pause, resume or rename an order.A run is one execution of an order. Its ID is
runId. A run cannot be changed. An on-demand order starts its run immediately. A scheduled order starts a run at each tick of its schedule.
Before you start
- Your account must have the data-export entitlement.
- An active destination credential must be registered for your account and destination bucket. If there is none, the request returns
422 missing_credential.
Create an order
curl -X POST "https://api.tomorrow.io/v4/data/orders/timelines?apikey=$TOMORROW_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept-Encoding: deflate, gzip, br" \
-d '{
"name": "warehouses-next-5-days",
"locations": [
[32.0853, 34.7818],
"40.7128, -74.0060",
{ "type": "Polygon", "coordinates": [[[-0.2,51.4],[0.1,51.4],[0.1,51.6],[-0.2,51.6],[-0.2,51.4]]] }
],
"fields": ["temperature", "windSpeed", "precipitationIntensity"],
"timesteps": ["1h"],
"start": "now",
"end": "nowPlus5d",
"units": "metric",
"timezone": "UTC",
"format": "csv",
"destination": { "type": "gcs", "bucket": "my-weather-exports", "prefix": "tomorrow" }
}'Response 201 Created for an on-demand order:
{
"orderId": "ord_8c1f2a",
"name": "warehouses-next-5-days",
"runId": "3f1c2b9e-7a4d-4e8b-9c21-5d6e7f8a9b0c",
"dataset": "timelines",
"size": null
}Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
locations | array | One of locations or feedId | The locations. Each item is a [lat, lon] pair, a "lat, lon" string, a GeoJSON geometry (Point, Polygon, MultiPolygon and other types), or the ID of a saved location. Refer to formats. |
feedId | string | One of locations or feedId | The ID of a stored assets feed. The feed is resolved when the run starts. |
fields | array of strings | Yes | The fields to export. Use the same names as in the Timelines API. The maximum is 100 fields. |
format | string | Yes | The format of the delivered file: csv, json (one array of location objects) or jsonl (one location object per line). |
destination | object | Yes | The bucket that receives the file. Refer to Destination. |
timesteps | array of strings | No | For example ["1h"] or ["1d"]. Refer to Timestep Availability. |
start | string | No | The start of the time window: an ISO 8601 time, now, nowPlusXm/h/d or nowMinusXm/h/d. |
end | string | No | The end of the time window. It accepts the same formats as start. |
timezone | string | No | An IANA timezone name, for example America/New_York. |
dailyStartHour | integer | No | 0–23. The hour at which a daily timestep starts. |
units | string | No | metric or imperial. |
name | string | No | A label for the order, with a maximum of 128 characters. It must be unique in your account. If not, the request returns 409 name_conflict. |
active | boolean | No | The default is true. If false, the order is stored but does not run until you activate it. Refer to Pause, resume or rename an order. |
trigger | object | No | Omit it for an on-demand order (one time request). For a scheduled order, refer to Scheduled orders. |
Destination
{ "type": "gcs", "bucket": "my-bucket", "prefix": "optional/prefix" }| Parameter | Description |
|---|---|
type | gcs, s3 or azure. |
bucket | The bucket name only. Do not include gs:// or a path. |
prefix | Optional. It is added to the start of each object key. |
Scheduled orders
To make a recurring export, add a trigger with a 5-field cron expression. The request does not start a run. The response has nextRunTime and no runId.
Request for example:
{
"feedId": "my-sites-feed",
"fields": ["temperature", "humidity"],
"timesteps": ["1h"],
"start": "now",
"end": "nowPlus2d",
"format": "jsonl",
"destination": { "type": "s3", "bucket": "my-weather-exports" },
"trigger": { "type": "schedule", "schedule": "0 */6 * * *" }
}Response:
{
"orderId": "ord_9d2e3b",
"dataset": "timelines",
"nextRunTime": "2026-10-12T18:00:00Z",
"size": null
}
Relative time windowsOn a scheduled order, relative values such as
nowandnowPlus2dare resolved at each run. In the example above, each run exports a new 2-day forecast.
Delivered files
Each run writes its files to a folder for that run. A new run never overwrites the files of an earlier run.
<bucket>/<prefix>/<accountId>/<orderId>/<runId>/timelines.<csv|json|jsonl>
<bucket>/<prefix>/<accountId>/<orderId>/<runId>/failures.json
<bucket>/<prefix>/<accountId>/<orderId>/<runId>/credits.json
<bucket>/<prefix>/<accountId>/<orderId>/<runId>/error.json
| File | When it is written | Contents |
|---|---|---|
timelines.<format> | When the run delivers data | One row for each location, timestep and interval. Point locations have lat and lon columns. Other geometries have a location column. Locations from a feed use the feed asset's key as id. |
failures.json | Only when some locations failed | The locations that are not in the file, and the reason for each. |
credits.json | Best effort. A missing file does not mean that the delivery failed. | credits_used, billed_calls, complete, balance_remaining, locations_total, locations_ok, locations_failed. |
error.json | Only when the whole run was refused | order_id, run_id, code, message, time. |
Example timelines.csv:
id,lat,lon,timestep,startTime,temperature,windSpeed
tel-aviv,32.0853,34.7818,1h,2026-01-01T00:00:00Z,23.21,6.74
tel-aviv,32.0853,34.7818,1h,2026-01-01T01:00:00Z,23.46,7.07
Example failures.json:
{
"order_id": "ord_8c1f2a",
"run_id": "3f1c2b9e-7a4d-4e8b-9c21-5d6e7f8a9b0c",
"locations_total": 1000,
"locations_failed": 1,
"failures": [
{ "location_id": "site-17", "reason": "location_not_found", "status": 400, "message": "..." }
]
}The values of reason in failures.json are location_not_found, invalid_request, not_entitled, rate_limited, upstream_unavailable and unknown.
The values of code in error.json are permission_denied, feed_not_found, too_many_locations, invalid_order and invalid_query. invalid_query means that every location failed with the same query error, for example an unknown field or an incorrect timezone.
Track a run
GET https://api.tomorrow.io/v4/data/orders/timelines/{orderId}/runs/{runId}?apikey=<YOUR_API_KEY>
Poll this endpoint until the status is completed, failed, cancelled or skipped.
{
"runId": "3f1c2b9e-7a4d-4e8b-9c21-5d6e7f8a9b0c",
"status": "completed",
"fileCount": 1,
"filesDelivered": 1
}| Status | Meaning |
|---|---|
queued | The run was accepted but has not started. |
running | The run is in progress. |
completed | The file was delivered. If outcome is partial, some locations are not in the file. failures.json lists them. |
failed | The run did not deliver what was ordered. |
cancelling | A cancel request was accepted and is in progress. |
cancelled | You cancelled the run. |
Manage orders and runs
All paths start with https://api.tomorrow.io/v4 and need the apikey query parameter.
| Request | Description |
|---|---|
GET /data/orders?dataset=timelines | Lists your bulk timelines orders. Use limit and offset to get more pages. |
GET /data/orders/timelines/{orderId} | Gets an order, its schedule and its most recent run. |
PATCH /data/orders/timelines/{orderId} | Pauses, resumes or renames an order. Refer to Pause, resume or rename an order. |
DELETE /data/orders/timelines/{orderId} | Deletes the order and its schedule. Runs that are in progress are not cancelled. Cancel them first. |
GET /data/orders/timelines/{orderId}/runs | Lists the runs of an order, newest first. You can filter with status, and you can repeat this parameter. |
POST /data/orders/timelines/{orderId}/runs | Starts a new run now. The request has no body. Relative start and end values are resolved when the run starts. Returns 202 with the new runId. |
DELETE /data/orders/timelines/{orderId}/runs | Cancels all runs of the order that are not finished. The order continues to run on its schedule. To stop it, pause the order. |
DELETE /data/orders/timelines/{orderId}/runs/{runId} | Cancels one run. Cancellation is asynchronous: the status changes to cancelling first. Data that is already in delivery can still arrive. |
Cancel runs before you delete an orderDeleting an order does not cancel its runs. After an order is deleted, you can cancel a run only with
DELETE /data/orders/timelines/{orderId}/runs/{runId}.
Pause, resume or rename an order
curl -X PATCH "https://api.tomorrow.io/v4/data/orders/timelines/{orderId}?apikey=$TOMORROW_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "active": false }'{"active": false}pauses the order. Future runs do not start. A run that is in progress continues.{"active": true}resumes the order. If the order was created with"active": false, this starts it for the first time.{"name": "new-name"}renames the order.
Limits
| Limit | Error |
|---|---|
The maximum number of locations in one order. The maximum is given in the limit field of the error. An order with more locations is refused, not shortened. | 422 too_many_locations |
| A maximum of 100 fields in one order. | 422 too_many_fields |
| A maximum of 10 active orders in one account. This includes active scheduled orders and on-demand orders whose run is not finished. | 422 order_limit_exceeded |
Errors
All error responses have the same structure:
{
"code": "too_many_locations",
"field": "locations",
"limit": 50000,
"message": "..."
}Use code in your logic, because its values do not change. field is the request field that caused the error. limit is the limit that was exceeded, or the list of valid values.
| Status | Meaning |
|---|---|
400 | The request is not valid. Possible causes: an unknown or missing field, both locations and feedId, an incorrect cron expression, or an unsupported destination type. |
401 | The API key is missing or not valid. |
403 | The account does not have the export entitlement. |
404 | The order or run does not exist, or another account owns it. |
409 | name_conflict (the name is already used), run_pending (the order was created inactive and is not activated) or order_expired. |
422 | The request is valid but cannot be done. Codes: missing_credential, location_not_found, feed_not_found, too_many_locations, too_many_fields, order_limit_exceeded. |
502 / 503 | A temporary error in a dependency. Send the request again. |