⚡ Try it in the sandbox — no signup

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.

Base URLhttps://api.datacrop.dev
Local devhttp://localhost:8000
Response formatJSON / UTF-8
TLSRequired in production
TierRequests / dayCommodities
Free500Corn, wheat & soybean
Pro2,000All commodities
Max5,000All commodities
Team20,000All commodities

Authentication

Every request must include your API key in the Authorization header as a Bearer token. Get your key from Dashboard → API Key.

Authorization: Bearer YOUR_API_KEY

Every authenticated response carries three rate-limit headers:

HeaderDescription
X-RateLimit-LimitDaily request quota for your tier
X-RateLimit-RemainingRequests left today
X-RateLimit-ResetEpoch seconds when the quota resets (midnight UTC)

Account

GET/v1/meAll tiers

Returns 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())

Why the metadata is in headers

A CSV body that carries pagination and warnings in comment lines stops being a CSV. Putting them in X-DataCrop-* response headers keeps the body parseable by every tool above while leaving the guardrails reachable. Warning messages stay JSON-only — re-request without format=csv when X-DataCrop-Warnings is non-zero.

Series Data

GET/v1/seriesFree+

Fetch 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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn, soybean, wheat
sourcestring[]noFilter by source: FRED | AMS | NASS (repeatable)
start_datestringnoFloor date YYYY-MM-DD
end_datestringnoCeiling date YYYY-MM-DD
limitintegernoPage size, max 1000 (default 1000)
offsetintegernoPagination offset (default 0)
as_ofstringnoVintage cutoff YYYY-MM-DD — return data as it was known on this date
formatstringnojson (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": []
}
GET/v1/commoditiesAll tiers

Returns 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 }
}
GET/v1/freshness/{commodity}All tiers

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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity 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

GET/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

ParameterTypeRequiredDescription
commoditystring✱ yesGrain 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 }
  ]
}
GET/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug
limitintegernoNumber 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

GET/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn
weeksintegernoLookback 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."
  }
}
POST/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn
cost_per_unitnumber✱ yesYour cost per unit (USD/bu)
selling_pricenumbernoSelling price per unit — defaults to latest AMS price
volumenumbernoUnits to buy (default 1000)
target_margin_pctnumbernoTarget 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.

POST/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn
threshold_typestring✱ yes"above" or "below"
threshold_valuenumber✱ yesPrice level in USD/bu
sourcestringnoData source to watch (default AMS)
notify_emailstringnoEmail address for notifications
notify_webhook_urlstringnoWebhook 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"
}
GET/v1/alertsMax+

List all active alert rules for your API key. Filter by commodity or active status.

Parameters

ParameterTypeRequiredDescription
commoditystringnoFilter by commodity slug
activebooleannoFilter by active status (true/false)
limitintegernoPage size (default 50, max 100)
offsetintegernoPagination 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
}
DELETE/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

ParameterTypeRequiredDescription
idstring✱ yesAlert 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 success

Response

HTTP 204 No Content
GET/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

ParameterTypeRequiredDescription
idstring✱ yesAlert rule UUID
limitintegernoNumber 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

GET/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn
sourcestring[]noFilter by source: FRED | AMS | NASS
start_datestringnoFloor date YYYY-MM-DD
end_datestringnoCeiling 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.csv

Response

{
  "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

GET/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn
sourcestringnoFilter by source: AMS | FRED | NASS
source_refstringnoPin to one identifier — see /v1/identifiers
observed_datestringnoOnly restatements of this observation date YYYY-MM-DD
sincestringnoOnly restatements observed on/after this date YYYY-MM-DD
limitintegernoPage size, max 500 (default 100)
offsetintegernoPagination 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" }
  ]
}

What this looks like before any restatement

The example above shows a commodity with restatement history. A commodity with none yet — true for every commodity as of this log's launch — still gets a populated tracking_since: this is what makes it possible to tell “nothing has been revised since we started watching” apart from “this log isn't running.”

{
  "coverage": {
    "tracking_since": "2026-07-31T00:00:00+00:00",
    "last_revision_at": null,
    "backfillable": false,
    "note": "Revision history is recorded when DataCrop observes a value change..."
  },
  "stats": { "revision_count": 0, "revised_observations": 0, "upward_count": 0, "downward_count": 0 },
  "revisions": []
}
GET/v1/lineageAll tiers

The 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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity 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..."]
  }
}
GET/v1/licenseAll tiers

Machine-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

ParameterTypeRequiredDescription
sourcestring[]noScope 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

GET/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

ParameterTypeRequiredDescription
commoditystring✱ yesCommodity slug e.g. corn
benchmarkstringnosource_ref to spread against — defaults to the curated primary AMS identifier
windowintegernoLookback 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
    Promoted

Google 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.

HTTPerror codeMeaning
401invalid_api_keyKey missing, revoked, or malformed
403commodity_not_allowedYour tier does not include this commodity
403forecasts_not_includedForecasts require Pro or above
403procurement_not_includedProcurement tools require Max or above
403alerts_not_includedAlerts require Max or above
403export_not_includedBulk export requires Team or above
404alert_not_foundAlert ID not found or belongs to a different key
422alert_limit_exceeded20 active alert rules per API key maximum
422no_market_priceNo AMS price found — provide selling_price explicitly
429rate_limit_exceededDaily request quota for your tier reached
503data_store_unavailableSupabase temporarily unreachable — retry in 30s