Introduction
The DataCrop API is a REST API that delivers weekly grain market intelligence — price observations, forecast bands up to three weeks out, procurement signals, and alert management — across corn, soybeans, and wheat.
| Tier | Requests / day | Commodities |
|---|---|---|
| Free | 500 | Corn, wheat & soybean |
| Pro | 2,000 | All commodities |
| Max | 5,000 | All commodities |
| Team | 20,000 | All commodities |
Authentication
Every request must include your API key in the Authorization header as a Bearer token. Get your key from Dashboard → API Key.
Every authenticated response carries three rate-limit headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Daily request quota for your tier |
| X-RateLimit-Remaining | Requests left today |
| X-RateLimit-Reset | Epoch seconds when the quota resets (midnight UTC) |
Account
/v1/meReturns your API key's identity: tier, daily rate limit, requests used today, remaining requests, allowed commodities, and a feature flag map. Useful for dynamically adjusting your integration based on your plan.
Example
curl -X GET "https://api.datacrop.dev/v1/me" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"api_key_id": "3f9a1b2c-...",
"tier": "max",
"rate_limit_per_day": 5000,
"requests_today": 134,
"requests_remaining": 4866,
"resets_at": "2026-06-05T00:00:00+00:00",
"allowed_commodities": ["corn", "soybean", "wheat"],
"features": {
"forecasts": true,
"procurement_tools": true,
"alerts": true,
"raw_export": false,
"dashboard": true,
"write": false
}
}Quickstart
Add &format=csv to /v1/series and the response body is rows and nothing else — no envelope to unwrap, no normalization pass. It loads directly into pandas, R, or an Excel sheet that refreshes on open.
import pandas as pd
URL = "https://api.datacrop.dev/v1/series"
PARAMS = "?commodity=corn&source=AMS&format=csv&limit=1000"
df = pd.read_csv(
URL + PARAMS,
storage_options={"Authorization": "Bearer YOUR_API_KEY"},
parse_dates=["date", "ingested_at"],
)
# Already normalised: one unit per identifier, parity rows excluded,
# republished readings collapsed to one row per (source_ref, date).
print(df.groupby("source_ref")["value"].describe())Series Data
/v1/seriesFetch paginated time-series price observations for a commodity. Supports three sources: AMS (weekly cash bids), FRED (monthly PPI index and global prices), and NASS (annual price received). Free tier is limited to corn, wheat & soybean and the past 1 year; Pro and above get all commodities and full history.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn, soybean, wheat |
| source | string[] | no | Filter by source: FRED | AMS | NASS (repeatable) |
| start_date | string | no | Floor date YYYY-MM-DD |
| end_date | string | no | Ceiling date YYYY-MM-DD |
| limit | integer | no | Page size, max 1000 (default 1000) |
| offset | integer | no | Pagination offset (default 0) |
| as_of | string | no | Vintage cutoff YYYY-MM-DD — return data as it was known on this date |
| format | string | no | json (default) | csv — csv returns rows only; envelope moves to X-DataCrop-* headers |
Example
curl -X GET \
"https://api.datacrop.dev/v1/series?commodity=corn&source=AMS&limit=5" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"pagination": { "limit": 5, "offset": 0, "total": 1843, "has_more": true },
"series": [
{ "source": "AMS", "source_ref": "3223", "date": "2026-06-04",
"value": 4.52, "unit": "USD/bu", "region": "IL" },
{ "source": "AMS", "source_ref": "3223", "date": "2026-05-28",
"value": 4.48, "unit": "USD/bu", "region": "IL" }
],
"freshness": { "last_updated": "2026-06-04", "stale": false },
"warnings": []
}/v1/commoditiesReturns the list of commodity slugs your API key is allowed to query, along with the earliest available date for your tier. Use this to build dynamic dropdowns or validate commodity inputs before calling /v1/series.
Example
curl -X GET "https://api.datacrop.dev/v1/commodities" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"tier": "max",
"commodities": ["corn", "soybean", "wheat"],
"date_range": { "start": "2015-01-01", "end": null }
}/v1/freshness/{commodity}Returns the last-ingested date for each data source (AMS, FRED, NASS) for the given commodity. Use this to show users when data was last updated or to detect stale feeds.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug in the URL path e.g. /v1/freshness/corn |
Example
curl -X GET "https://api.datacrop.dev/v1/freshness/corn" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"sources": {
"AMS": { "last_date": "2026-06-04", "stale": false },
"FRED": { "last_date": "2026-05-01", "stale": false },
"NASS": { "last_date": "2026-01-01", "stale": false }
}
}Forecasts
/v1/forecastsPro+Returns the latest LightGBM forecast for a grain commodity (corn, soybean, or wheat — the series with live USDA AMS cash bids) as P10 (bear), P50 (base), and P90 (bull) price bands. The band is published up to three weeks ahead; the response carries band_max_horizon_weeks and band_horizon_cap_note stating why. Commodities without an AMS bid feed have price data but no forecast. Refreshed weekly, after the AMS/FRED/NASS ingestion completes.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Grain commodity slug: corn, soybean, or wheat |
Example
curl -X GET \
"https://api.datacrop.dev/v1/forecasts?commodity=corn" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"run_date": "2026-06-02",
"forecasts": [
{ "target_date": "2026-06-09", "p10": 4.21, "p50": 4.52,
"p90": 4.83, "unit": "USD/bu", "model_version": "lgbm-v3.1",
"skill_vs_naive": 0.18 },
{ "target_date": "2026-06-16", "p10": 4.18, "p50": 4.49,
"p90": 4.80, "unit": "USD/bu", "model_version": "lgbm-v3.1",
"skill_vs_naive": 0.18 }
]
}/v1/forecasts/historyPro+Returns summaries of historical forecast runs — useful for tracking how the model scores against its baseline over time. Each run includes the p50 range and the skill vs. naive score (positive = better than carrying the last price forward; it is frequently negative, and we publish it either way).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug |
| limit | integer | no | Number of past runs to return (default 12, max 24) |
Example
curl -X GET \
"https://api.datacrop.dev/v1/forecasts/history?commodity=corn&limit=3" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"total_runs": 24,
"runs": [
{ "run_date": "2026-06-02", "model_version": "lgbm-v3.1",
"skill_vs_naive": 0.18, "horizon_weeks": 13,
"p50_range": { "min": 4.31, "max": 4.89 } },
{ "run_date": "2026-05-26", "model_version": "lgbm-v3.1",
"skill_vs_naive": 0.15, "horizon_weeks": 13,
"p50_range": { "min": 4.22, "max": 4.77 } }
]
}Procurement
/v1/procurement/signalMax+Returns the current procurement risk signal for a commodity based on trailing AMS price volatility. The coefficient of variation (CV) determines the risk level (Low / Moderate / Elevated / High); the response also reports price vs. the window mean and a short-term trend. Statistics only — no buy/sell/wait instruction. Cached for 24 hours.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
| weeks | integer | no | Lookback window in weeks (default 13, range 4–52) |
Example
curl -X GET \
"https://api.datacrop.dev/v1/procurement/signal?commodity=corn" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"as_of": "2026-06-04",
"current_price": 4.52,
"price_unit": "USD/bu",
"data_window_weeks": 13,
"volatility": {
"cv_pct": 4.8, "risk_level": "Low", "risk_score": 19,
"description": "Price variation is within normal seasonal range."
},
"signal": {
"price_vs_mean_pct": -0.9,
"trend": "stable",
"summary": "Low volatility · price 0.9% below the 13-week mean."
}
}/v1/procurement/marginMax+Compute margin, profit, and break-even price for your specific cost basis. If you omit selling_price, the latest AMS market price is fetched automatically and used as the selling price. The response also includes the current procurement signal for context.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
| cost_per_unit | number | ✱ yes | Your cost per unit (USD/bu) |
| selling_price | number | no | Selling price per unit — defaults to latest AMS price |
| volume | number | no | Units to buy (default 1000) |
| target_margin_pct | number | no | Target margin % for break-even calculation |
Example
curl -X POST "https://api.datacrop.dev/v1/procurement/margin" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"commodity":"corn","cost_per_unit":3.80,"volume":10000}'Response
{
"commodity": "corn",
"inputs": { "cost_per_unit": 3.80, "selling_price": 4.52,
"volume": 10000, "selling_price_source": "ams_latest" },
"market": { "current_ams_price": 4.52, "price_date": "2026-06-04",
"spread_vs_cost": 0.72 },
"margin": { "margin_pct": 15.93, "profit_per_unit": 0.72,
"total_profit": 7200.0, "above_break_even": true },
"signal": { "price_vs_mean_pct": -0.9, "trend": "stable" }
}Alerts
Price threshold alerts fire when AMS market bids cross a level you define. Rules are evaluated every Monday after ingestion. You can have up to 20 active rules per API key. Notifications are sent via email and/or a webhook URL you provide at rule creation.
/v1/alertsMax+Create a price threshold alert rule. Set threshold_type to 'above' to be notified when the price rises above your threshold, or 'below' to be notified when it falls below. Optionally provide an email or webhook URL for notifications.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
| threshold_type | string | ✱ yes | "above" or "below" |
| threshold_value | number | ✱ yes | Price level in USD/bu |
| source | string | no | Data source to watch (default AMS) |
| notify_email | string | no | Email address for notifications |
| notify_webhook_url | string | no | Webhook URL to POST when triggered |
Example
curl -X POST "https://api.datacrop.dev/v1/alerts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"commodity": "corn",
"threshold_type": "above",
"threshold_value": 5.00,
"notify_email": "buyer@example.com"
}'Response
{
"id": "aaaaaaaa-1234-...",
"commodity": "corn",
"source": "AMS",
"threshold_type": "above",
"threshold_value": 5.0,
"notify_email": "buyer@example.com",
"active": true,
"created_at": "2026-06-04T12:00:00Z"
}/v1/alertsMax+List all active alert rules for your API key. Filter by commodity or active status.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | no | Filter by commodity slug |
| active | boolean | no | Filter by active status (true/false) |
| limit | integer | no | Page size (default 50, max 100) |
| offset | integer | no | Pagination offset |
Example
curl -X GET "https://api.datacrop.dev/v1/alerts" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"rules": [
{ "id": "aaaaaaaa-1234-...", "commodity": "corn",
"threshold_type": "above", "threshold_value": 5.0,
"active": true, "created_at": "2026-06-04T12:00:00Z" }
],
"total": 1
}/v1/alerts/{id}Max+Soft-delete an alert rule by its ID. The rule is deactivated immediately — it will no longer be evaluated. Returns HTTP 204 (no body) on success.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | ✱ yes | Alert rule UUID (from create or list response) |
Example
curl -X DELETE \
"https://api.datacrop.dev/v1/alerts/aaaaaaaa-1234-..." \
-H "Authorization: Bearer YOUR_API_KEY"
# Returns 204 No Content on successResponse
HTTP 204 No Content/v1/alerts/{id}/eventsMax+Returns the trigger history for a single alert rule — every time the threshold was crossed and a notification was (or was not) sent.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | ✱ yes | Alert rule UUID |
| limit | integer | no | Number of events (default 50, max 100) |
Example
curl -X GET \
"https://api.datacrop.dev/v1/alerts/aaaaaaaa-1234-.../events" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"rule_id": "aaaaaaaa-1234-...",
"total": 2,
"events": [
{ "id": "evt-001", "triggered_at": "2026-06-02T08:14:00Z",
"observed_value": 5.12, "threshold_value": 5.0,
"notification_sent": true },
{ "id": "evt-002", "triggered_at": "2026-05-26T08:11:00Z",
"observed_value": 5.08, "threshold_value": 5.0,
"notification_sent": true }
]
}Export
/v1/exportTeam+Generate a bulk CSV export of all observations matching your query. Returns a signed URL to a pre-generated file in Supabase Storage (valid for 7 days). Exports are content-addressed: the same query always returns the same cached file, avoiding re-generation on repeated calls.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
| source | string[] | no | Filter by source: FRED | AMS | NASS |
| start_date | string | no | Floor date YYYY-MM-DD |
| end_date | string | no | Ceiling date YYYY-MM-DD |
Example
curl -X GET \
"https://api.datacrop.dev/v1/export?commodity=corn&source=AMS" \
-H "Authorization: Bearer YOUR_API_KEY"
# Then download the CSV:
# curl -L "$URL" -o corn_ams.csvResponse
{
"url": "https://xxx.supabase.co/storage/v1/object/sign/exports/a3f9c2d1.csv?token=...",
"expires_at": "2026-06-11T12:00:00Z",
"row_count": 4382,
"file_size_bytes": 287441,
"cached": true,
"generated_at": "2026-06-02T08:14:33Z",
"license": {
"redistribution_summary": { "permitted": true, "conditions_apply": false },
"attribution": ["Source: USDA Agricultural Marketing Service, Market News."]
}
}Provenance
/v1/revisionsPro+The log of USDA restatements. When a published price is revised, the observation row is overwritten in place — the previous value is captured at the moment of overwrite, which is the only moment it is still knowable. Revision history therefore runs forward from when tracking began and cannot be backfilled, by DataCrop or by anyone starting to snapshot the USDA endpoints today. coverage.tracking_since is fixed at that start date and is present even before the first restatement is recorded — it does not mean none were missed, only that none have happened yet.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
| source | string | no | Filter by source: AMS | FRED | NASS |
| source_ref | string | no | Pin to one identifier — see /v1/identifiers |
| observed_date | string | no | Only restatements of this observation date YYYY-MM-DD |
| since | string | no | Only restatements observed on/after this date YYYY-MM-DD |
| limit | integer | no | Page size, max 500 (default 100) |
| offset | integer | no | Pagination offset (default 0) |
Example
curl -X GET \
"https://api.datacrop.dev/v1/revisions?commodity=corn&since=2026-01-01" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"since": "2026-01-01",
"coverage": {
"tracking_since": "2026-07-31T00:00:00+00:00",
"last_revision_at": "2026-06-03T04:19:55Z",
"backfillable": false,
"note": "Revision history is recorded when DataCrop observes a value change..."
},
"stats": {
"revision_count": 37,
"revised_observations": 34,
"mean_abs_delta": 0.0412,
"max_abs_delta": 0.19,
"upward_count": 21,
"downward_count": 16
},
"pagination": { "limit": 100, "offset": 0, "total": 37, "has_more": false },
"revisions": [
{ "source": "AMS", "source_ref": "2850", "observed_date": "2026-05-28",
"region": "IL", "unit": "USD/bu",
"previous_value": 4.48, "new_value": 4.51,
"delta": 0.03, "pct_change": 0.6696,
"previously_ingested_at": "2026-05-28T04:18:02Z",
"revised_at": "2026-06-03T04:19:55Z" }
]
}/v1/lineageThe normalization receipt for a commodity — every filter, conversion and exclusion applied between the raw USDA response and the series you query, itemised from the same catalog files the pipeline runs on. Read it as an audit trail if you already subscribe, or as the real scope of the job if you are deciding whether to build this yourself. Counts are derived at request time; no time-saved estimate is given, because that depends on your team and DataCrop cannot measure it.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
Example
curl -X GET "https://api.datacrop.dev/v1/lineage?commodity=corn" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"identifiers": {
"total": 27,
"by_source": { "AMS": 25, "FRED": 1, "NASS": 1 },
"primary_by_source": { "AMS": "2850", "FRED": "WPU012202" },
"excluded_from_training_target": ["3511", "3667", "3668"],
"note": "Identifiers are resolved from the source catalogs, not from a name match..."
},
"units": {
"canonical_unit": "USD/bu",
"canonical_measure": "bu",
"lb_per_bushel": 56.0,
"distinct_raw_units": 3,
"conversion_required": true,
"observed": [
{ "raw_unit": "$ Per Bushel", "source_refs": 21,
"converts_to": "USD/bu", "conversion_factor": null },
{ "raw_unit": "$ Per Ton", "source_refs": 3,
"converts_to": "USD/bu", "conversion_factor": 0.028 }
],
"sample_rows": 1000
},
"filters": [
{ "name": "parity_exclusion", "applies_to": "NASS", "description": "..." },
{ "name": "intraday_deduplication", "applies_to": "all sources", "description": "..." },
{ "name": "value_range_gate", "applies_to": "training target", "description": "..." }
],
"vintage": { "ingested_at_retained": true, "as_of_supported": true, "revision_log": true },
"warnings": { "total": 2, "by_severity": { "warn": 2 }, "blocks_response": false },
"manual_equivalent": {
"identifiers_to_reconcile": 27,
"unit_conversions_to_implement": 2,
"documented_relationships_to_encode": 2,
"steps": ["Resolve which source identifiers carry corn across AMS, FRED and NASS..."]
}
}/v1/licenseMachine-readable provenance and redistribution terms for every upstream source. DataCrop carries only U.S. federal source data, so redistribution is permitted — with conditions on FRED, which is a retrieval point rather than the originating agency. Every entry links the upstream statement it summarises so the claim is checkable rather than taken on trust. Available on the free tier: the redistribution question gates the decision to build on a feed at all.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| source | string[] | no | Scope to specific sources: AMS | FRED | NASS. Omit for all. |
Example
curl -X GET "https://api.datacrop.dev/v1/license?source=AMS&source=FRED" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"sources": [
{ "source": "AMS", "publisher": "U.S. Department of Agriculture",
"status": "public_domain", "redistribution": "permitted",
"attribution_required": false,
"verify_url": "https://mymarketnews.ams.usda.gov/mymarketnews-api" },
{ "source": "FRED", "publisher": "Federal Reserve Bank of St. Louis",
"status": "public_domain_via_aggregator",
"redistribution": "permitted_with_conditions",
"attribution_required": true,
"verify_url": "https://fred.stlouisfed.org/legal" }
],
"redistribution_summary": {
"permitted": true,
"conditions_apply": true,
"conditional_sources": ["FRED"],
"statement": "Redistribution permitted; conditions apply to FRED."
},
"attribution": [
"Source: USDA Agricultural Marketing Service, Market News.",
"Source: U.S. Bureau of Labor Statistics, retrieved from FRED, Federal Reserve Bank of St. Louis."
],
"derived_content": "Source observations are U.S. federal data and carry the terms above...",
"disclaimer": "Summary of publicly stated upstream terms... Not legal advice."
}Basis
/v1/basisPro+Each AMS identifier's cash spread against a benchmark identifier, in the commodity's canonical unit. The catalog warns against pooling state bids with barge and terminal prices — the gap between them is a real transport-and-handling cost, and averaging it away destroys the number a procurement desk needs. This endpoint is the operation those warnings point toward: spread the identifiers instead of pooling them, and report the gap itself. Legs are converted to one unit before subtraction and paired to the benchmark as-of their own observation date, because report calendars do not line up.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| commodity | string | ✱ yes | Commodity slug e.g. corn |
| benchmark | string | no | source_ref to spread against — defaults to the curated primary AMS identifier |
| window | integer | no | Lookback in days: 30 | 60 | 90 | 180 | 365 (default 90) |
Example
curl -X GET \
"https://api.datacrop.dev/v1/basis?commodity=corn&window=90" \
-H "Authorization: Bearer YOUR_API_KEY"Response
{
"commodity": "corn",
"unit": "USD/bu",
"window_days": 90,
"benchmark": {
"source": "AMS", "source_ref": "2850", "region": "IL",
"is_primary": true, "latest_value": 4.51,
"latest_date": "2026-06-04", "observations": 62
},
"legs": [
{ "source_ref": "3043", "label": "Nebraska Grain Bids", "region": "NE",
"latest_basis": -0.34, "latest_value": 4.17, "latest_date": "2026-06-04",
"benchmark_value": 4.51, "benchmark_date": "2026-06-04",
"benchmark_lag_days": 0,
"mean_basis": -0.31, "stdev_basis": 0.042,
"min_basis": -0.39, "max_basis": -0.22,
"paired_observations": 58 }
],
"excluded_identifiers": [],
"method": {
"unit_normalization": "Every leg converted to USD/bu... before subtraction.",
"pairing": "Each leg reading is spread against the most recent benchmark reading at or before its own observation date.",
"max_benchmark_lag_days": 14,
"row_limit_reached": false
}
}Excel & Google Sheets
GET /v1/series?format=csv returns plain CSV, which both Excel and Google Sheets can ingest directly — as long as the request can carry the Authorization: Bearer header. The recipes below do exactly that. The JSON envelope (pagination, freshness, warnings, attribution) moves to X-DataCrop-* response headers on CSV responses, and a Link: </v1/license>; rel="license" header points at the redistribution terms — the provenance still travels with the data even when the body is just rows.
Your key lives in the workbook. Both recipes store the API key inside the spreadsheet (Power Query connection / Apps Script source), where anyone you share the file with can read it. Only share such workbooks with people you would hand the key to, and rotate the key if a workbook leaks. Per-key scopes (a restricted, read-only key you could safely embed) are planned but not available yet — until they ship, the key in the sheet is your full key.
Excel — Power Query (M)
Data → Get Data → From Other Sources → Blank Query, then open the Advanced Editor and paste. When Excel asks how to connect, choose Anonymous— the credential travels in the header below, not in Excel's credential store.
let
Source = Csv.Document(
Web.Contents(
"https://api.datacrop.dev/v1/series",
[
Query = [
commodity = "corn",
format = "csv",
start_date = "2026-01-01"
],
Headers = [Authorization = "Bearer YOUR_API_KEY"]
]
),
[Delimiter = ",", Encoding = 65001, QuoteStyle = QuoteStyle.Csv]
),
Promoted = Table.PromoteHeaders(Source, [PromoteAllScalars = true])
in
PromotedGoogle Sheets — Apps Script
Sheets' built-in IMPORTDATA()cannot send request headers, so it cannot authenticate against the API — there is no keyless route, and we won't pretend otherwise. Use a small Apps Script instead (Extensions → Apps Script), which fetches with the Bearer header and writes the rows into a tab. Run it manually or add a time-driven trigger.
function importDataCrop() {
// The key is stored in this script, visible to every editor of the sheet.
const API_KEY = 'YOUR_API_KEY';
const url =
'https://api.datacrop.dev/v1/series' +
'?commodity=corn&format=csv&start_date=2026-01-01';
const resp = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + API_KEY },
muteHttpExceptions: true,
});
if (resp.getResponseCode() !== 200) {
// 422 = unacknowledged critical data warnings — read the body, then
// re-request with ?acknowledge=<id1>,<id2> if you accept them.
throw new Error('DataCrop ' + resp.getResponseCode() + ': ' + resp.getContentText());
}
// Envelope metadata rides on the response headers, e.g.:
// X-DataCrop-Last-Updated, X-DataCrop-Stale, X-DataCrop-Warnings,
// X-DataCrop-Attribution, and Link: </v1/license>; rel="license"
const headers = resp.getAllHeaders();
Logger.log('Attribution: ' + headers['x-datacrop-attribution']);
const rows = Utilities.parseCsv(resp.getContentText());
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('DataCrop') || ss.insertSheet('DataCrop');
sheet.clearContents();
sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
}CSV responses keep their provenance
A CSV body has no room for the JSON envelope, so it moves to headers: X-DataCrop-Sources, X-DataCrop-Last-Updated / -Stale, X-DataCrop-Warnings / -Warning-Ids, pagination (-Total, -Has-More), and X-DataCrop-Attribution. The Link header points at /v1/license for the full redistribution terms. Power Query discards response headers, so if your workbook redistributes the data, check /v1/license once and keep the attribution line in the sheet.
Errors
All error responses share the same shape: {"error": "machine_code", "message": "Human text."}. The HTTP status code and error field together identify what went wrong.
| HTTP | error code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Key missing, revoked, or malformed |
| 403 | commodity_not_allowed | Your tier does not include this commodity |
| 403 | forecasts_not_included | Forecasts require Pro or above |
| 403 | procurement_not_included | Procurement tools require Max or above |
| 403 | alerts_not_included | Alerts require Max or above |
| 403 | export_not_included | Bulk export requires Team or above |
| 404 | alert_not_found | Alert ID not found or belongs to a different key |
| 422 | alert_limit_exceeded | 20 active alert rules per API key maximum |
| 422 | no_market_price | No AMS price found — provide selling_price explicitly |
| 429 | rate_limit_exceeded | Daily request quota for your tier reached |
| 503 | data_store_unavailable | Supabase temporarily unreachable — retry in 30s |
