You are building a tool that checks planting history for farm parcels, and someone has suggested DTN's Land Crop API. You have a pool of credits on your contract, a loop that pages through results, and a retry handler that backs off whenever it sees a 429. You are about to find out why that last piece matters.
The API is small. It has exactly one operation, GET /api/v1/land/{landId}/crop, and it returns the planting history of a single land parcel. You pass the landId in the path as a required integer. Optionally you pass a year to filter by crop year, a limit that defaults to 10 and runs from 1 to 100, and an offset that defaults to 0. What comes back is a list of LandCrop records, each with landId, cropYear, acres, lciId, and nullable planted and harvested timestamps.
Two details save you from bad assumptions. The lciId is a Land Cover Index code, and the schema tells you to resolve it through /codes/crops, which is not in this contract, so you will need to look elsewhere for the lookup. And the documented example returns three years of a 29-record history with planted and harvested both null on every row. Do not assume dates exist.
Now the part that makes this contract worth your time: it states the price of every call. The cost is 1 credit per crop-year record returned. Three headers come back on a 200: X-Credit-Cost, X-Credits-Remaining, and X-Credit-Pool-Reset, the last one a date-time for when your contract resets. The description separates metered endpoints, which cover producer and land data, from unmetered discovery endpoints for search, codes, and geographic areas.
That pricing shapes how you write your code. Because the meter counts records, limit is your spend control. A parcel with 29 crop years costs 29 credits however you page it, but a year filter costs one. If you only need a single season, ask for it.
Then there is the error you would have walked straight into. When credits run out, the API returns a 429, typed insufficient-credits. The documented detail reads, "This request requires 8 land credits, but only 5 remain." That is the rate-limit status code reused for a billing condition. Your handler treats every 429 as back off and retry, so it would retry forever against an empty pool. The fix is to branch on the type field, not the status. Errors otherwise follow RFC 7807 problem details, with an instance URN that carries the request ID, and 400s add a per-parameter errors array. Auth is OAuth 2.0 client credentials against api.auth.dtn.com.
Before you wire this in, there is a turn you should know about, and it is about the catalog that brought you here. APIs.io lists DTN with a Kin Score of 50.9, which is developing, and an Agent Readiness of 32.0, agent-ready, across 99 API entries. This API belongs to a family: the contract's own title is DTN Farm Intelligence Land Crop API, and its description places it inside a Farm Intelligence product with Producer, Land, Linked Data and Shared Utilities surfaces.
The post is frank that the catalog's own record has three inconsistencies, and they would have bitten you. The baseURL reads weather.api.dtn.com, while the contract's servers are the api-farm-intelligence hosts. The entry_base is the development server, the first of three the contract lists, when production is api-farm-intelligence.prd.ag.zones.dtn.com. And the documentation link points to DTN's Weather Conditions API docs, not Farm Intelligence. The same dev entry_base appears on all four Land entries: Crop, Details, Geospatial, and Search. So if you trusted the listing alone, you would point at the wrong host and read the wrong docs.
What you take away is a short checklist. Treat this as a priced data call. Read X-Credits-Remaining on every response. Filter by year whenever you can. Handle insufficient-credits as a stop, not a retry. And call production, not the host the catalog record lists. The contract itself is the source you trust here.
Read the original: https://apis.io/2026/11/11/dtns-land-crop-api-bills-a-credit-per-crop-year-and-tells-you-the-cost-in-a-header/