Endpoints
This content is not available in your language yet.
This page is generated from /v1/openapi.json by
agrovisio-backend/scripts/publicapi_docs_build.py and copied into the site
at build time (last sync: 2026-11-04). It is the static fallback for the
interactive reference, which loads the same document live and
can send requests from the browser.
The examples use the production base URL. For the sandbox, point them at the
same paths with a sk_test_ key; the sandbox and live keys share one base
URL and the key decides which world you see.
Account
Section titled “Account”Plan, quota and billing period status.
GET /v1/account - Account, plan and quota status
Section titled “GET /v1/account - Account, plan and quota status”Returns the plan, the products it includes, rate limits and the current billing period usage.
This endpoint stays available while the account is suspended for an overdue payment - every other endpoint returns 402 payment_required.
curl https://api.agrovisio.ai/v1/account \ -H 'Authorization: Bearer sk_live_...'GET /v1/status - Service status
Section titled “GET /v1/status - Service status”Public service status - no API key required.
status is ok, degraded (API answers, but limits, jobs or a data
queue are impaired) or down (database unreachable). Poll it from your
monitoring at most once per 30 s; it is not rate limited but it is not
free for us either. The HTTP code is always 200 so that a monitoring
client can distinguish “the status endpoint failed” from “the service
reports a problem”.
Subsidies, eco-schemes and rotation compliance.
POST /v1/cap/eco-schemes/optimize - Optimal eco-scheme combination
Section titled “POST /v1/cap/eco-schemes/optimize - Optimal eco-scheme combination”Picks the highest value combination of eco-schemes each parcel is eligible for, respecting the incompatibility rules of the national catalogue, and reports what to add and what to drop against the schemes already claimed.
conservative (the default) leaves out organic farming, which requires a certificate the holding may not have; aggressive includes the whole catalogue.
Schemes that depend on where the parcel is rather than what grows on it (biodiversity strips, water buffers) are not recommended automatically. They are returned under candidates, because claiming one without a site check is how a holding fails an inspection.
Idempotency-Key is required, as on every POST. Repeating the same key with the same body replays the stored response with Idempotent-Replay: true; repeating it with a different body returns 409 idempotency_conflict. The calculation is a read: both the first call and a replay answer 200.
Crops may be sent as canonical codes (winter_wheat) or as free text in the national language. Recognition goes through the canonical crop registry, never through substring matching, so ‘Vasariniai kvieciai’ and ‘spring wheat’ resolve to the same code. A value that cannot be resolved is reported in warnings with crop_not_recognised and simply earns no coupled support; it is never guessed.
curl -X POST 'https://api.agrovisio.ai/v1/cap/eco-schemes/optimize' \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 7a2d3e4f-5b6c-4d7e-8f90-a1b2c3d4e5f6' \ -H 'Content-Type: application/json' \ -d '{"country":"LT","mode":"conservative","parcels":[{"ref":"field-12","area_ha":24.5,"crop":"winter_wheat"}]}'GET /v1/cap/rotation/check - Crop rotation compliance
Section titled “GET /v1/cap/rotation/check - Crop rotation compliance”Checks a crop history against the national monoculture limit (in Lithuania: the same crop may be grown for at most three consecutive seasons, so a fourth season in a row is a violation - the same reading the declaration validator applies).
History is passed as YYYY:crop items separated by commas, for example 2022:winter_wheat,2023:winter_wheat,2024:winter_wheat,2025:winter_wheat. Years must be four digits, not earlier than 1990 and not later than next year (a planned sowing may be included); anything else is a validation_error. Only adjacent years form a run: 2021, 2022 and 2024 is not a three season streak.
diversity is informational and does not affect compliant - the EU rule limits monoculture, not low crop diversity. A crop that could not be resolved is listed in unrecognised_crops and breaks the run rather than being assumed to be the same crop as its neighbour.
Crops may be sent as canonical codes (winter_wheat) or as free text in the national language. Recognition goes through the canonical crop registry, never through substring matching, so ‘Vasariniai kvieciai’ and ‘spring wheat’ resolve to the same code. A value that cannot be resolved is reported in warnings with crop_not_recognised and simply earns no coupled support; it is never guessed.
curl 'https://api.agrovisio.ai/v1/cap/rotation/check?country=LT&history=2023:winter_wheat,2024:winter_wheat,2025:winter_wheat' \ -H 'Authorization: Bearer sk_live_...'POST /v1/cap/subsidy/calculate - Direct payments for a holding
Section titled “POST /v1/cap/subsidy/calculate - Direct payments for a holding”Calculates basic income support (BISS), the complementary redistributive payment (CRIS) and coupled support for every parcel, using the published national rates.
Send the whole holding in one call. CRIS applies to the first N hectares of the holding, so splitting the parcels across two requests would award the full redistributive band twice and overstate the result.
Eco-schemes are voluntary and are excluded from total_eur. Their value is reported separately as eco_potential_eur, because a farm that has not enrolled will not receive it, and a lender must not book it as income.
A parcel below the national minimum field area (Lithuania: 0.1 ha, ministerial order 3D-92 point 5.6) receives no direct payment. It is returned with the warning area_below_biss_minimum, an empty lines list and total_eur: 0, and it does not consume the CRIS band for the other parcels - the same reading the declaration validator applies. ref is your own reference and does not have to be unique (the same field split into two crops is fine): results are returned in the order the parcels were sent, and every row keeps its own eco_potential_eur.
Idempotency-Key is required, as on every POST. Repeating the same key with the same body replays the stored response with Idempotent-Replay: true; repeating it with a different body returns 409 idempotency_conflict. The calculation is a read: both the first call and a replay answer 200.
Crops may be sent as canonical codes (winter_wheat) or as free text in the national language. Recognition goes through the canonical crop registry, never through substring matching, so ‘Vasariniai kvieciai’ and ‘spring wheat’ resolve to the same code. A value that cannot be resolved is reported in warnings with crop_not_recognised and simply earns no coupled support; it is never guessed.
curl -X POST 'https://api.agrovisio.ai/v1/cap/subsidy/calculate' \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 6f1c2f7e-1f0e-4a0a-9a8e-1b2c3d4e5f60' \ -H 'Content-Type: application/json' \ -d '{"country":"LT","parcels":[{"ref":"field-12","area_ha":24.5,"crop":"winter_wheat"}]}'Damage
Section titled “Damage”Before/after damage assessment and certificates.
POST /v1/damage/assess - Order a damage assessment
Section titled “POST /v1/damage/assess - Order a damage assessment”Orders a before/after damage assessment for one parcel and one insured event. The assessment compares Sentinel-1 backscatter in the 14 days before the event with the 14 days after it, in the same relative orbit, and removes the expected seasonal change using the parcel baseline.
The work is asynchronous: this endpoint returns 202 with a job_id. Poll GET /v1/jobs/{job_id} or subscribe to the damage.completed webhook. A finished job carries the full result, and GET /v1/damage/{assessment_id} returns it again at any time.
Billing. This is a metered unit (EUR 35 per assessment). You are charged only when the job finishes with status: completed and confidence of at least 0.40. An assessment that ends as insufficient_data, or whose confidence stays below that, is recorded for audit with billable: false and costs nothing. Idempotency-Key is required: retrying with the same key returns the same job with its current status and is never charged twice.
Event date. The 14 day window after the event must have closed: an event_date less than 14 days ago returns 400 date_range_invalid with detail.reason: post_window_open and detail.available_from, the first day the order is accepted. drought has no window after the event and is accepted up to today.
Perils. hail and storm expect a rise in backscatter (lodging), flood a strong drop confirmed by rising NDWI, fire a drop with NDVI at or below 0.2, and drought uses the water stress index and the NDVI deficit over the 30 days before the event instead of SAR.
curl -X POST https://api.agrovisio.ai/v1/damage/assess \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 6f1c2f7e-1f0e-4a0a-9a8e-1b2c3d4e5f60' \ -H 'Content-Type: application/json' \ -d '{"parcel_id":"prc_9F3AMNPQRSTVWXYZ0123","event_date":"2026-07-15","peril":"hail","claim_ref":"CLM-2026-8812"}'GET /v1/damage/{assessment_id} - Read a damage assessment
Section titled “GET /v1/damage/{assessment_id} - Read a damage assessment”Returns a finished assessment with everything needed to defend the number in a claim file: the damaged zones, the bootstrap confidence interval, the scenes used, the seasonal correction and the method version with its configuration hash.
status: insufficient_data is a normal outcome, not an error: the job ran, but the satellite record did not support a result. reason says what was missing and billable is false.
curl https://api.agrovisio.ai/v1/damage/dmg_44C1MNPQRSTVWXYZ012 \ -H 'Authorization: Bearer sk_live_...'GET /v1/damage/{assessment_id}/certificate.pdf - Download the damage certificate
Section titled “GET /v1/damage/{assessment_id}/certificate.pdf - Download the damage certificate”Returns the PDF certificate for an assessment. The document is generated deterministically from the stored result, so the sha256 you received with the assessment stays valid for every later download - that is what makes the certificate usable as evidence.
A certificate is issued only when confidence is at least 0.50. Below that the assessment is still available as JSON, but a signed document would give it a weight the data does not carry, so this endpoint returns 422 insufficient_data.
curl -o certificate.pdf \ https://api.agrovisio.ai/v1/damage/dmg_44C1MNPQRSTVWXYZ012/certificate.pdf \ -H 'Authorization: Bearer sk_live_...'Indices
Section titled “Indices”NDVI, NDWI and EVI time series with baselines.
POST /v1/indices/batch - Index time series for up to 500 parcels
Section titled “POST /v1/indices/batch - Index time series for up to 500 parcels”Returns the same series as GET /parcels/{id}/indices for a list of parcels in one call. Parcels are processed independently: an unknown or deleted id is reported as status: not_found in its own result row instead of failing the whole request.
Idempotency-Key is required. Repeating the same key with the same body replays the stored response with Idempotent-Replay: true; repeating it with a different body returns 409 idempotency_conflict. The batch is a read: both the first call and a replay answer 200.
The batch uses parcel baselines only - it never falls back to the district baseline, because that would mean up to 50 neighbour baselines per parcel in a single request. Parcels whose own history is too short come back with baseline.reason set; request them individually to get the district norm.
curl -X POST https://api.agrovisio.ai/v1/indices/batch \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 8f1c2d3e-...' \ -H 'Content-Type: application/json' \ -d '{"parcel_ids":["prc_9F3A"],"index":"NDVI","from":"2026-05-01","to":"2026-09-01"}'GET /v1/parcels/{parcel_id}/indices - NDVI / NDWI / EVI time series with baseline
Section titled “GET /v1/parcels/{parcel_id}/indices - NDVI / NDWI / EVI time series with baseline”Sentinel-2 L2A time series aggregated over the parcel with a 10 m inner buffer and the SCL mask applied (method indices-1.0).
Every observation carries its quality class: good (>= 80% valid pixels), partial (50-79%) or cloud_masked (< 50%). A cloud_masked observation is returned with value: null and reason: cloud_masked - the series is never interpolated.
quality keeps only observations of one class (good, partial, cloud_masked); observations counts what is returned.
The baseline block is the five season norm for the same day of year (method baseline-1.0). Every observation is compared with the norm of its own season - the five complete seasons before it - so a window that spans several years uses one norm per season, and anomaly.baseline_season says which. The baseline block describes the norm for the season of to. While the historical backfill is still running the endpoint returns 422 baseline_not_ready; pass baseline=false to receive the raw series in the meantime.
The date range must not exceed 5 years.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/indices?index=NDVI&from=2026-04-01&to=2026-09-01' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/indices/latest - Most recent usable index observation
Section titled “GET /v1/parcels/{parcel_id}/indices/latest - Most recent usable index observation”Returns the most recent Sentinel-2 observation for the parcel within the lookback window (30 days). Cloud covered scenes are skipped for this endpoint - use GET /parcels/{id}/indices to see them with value: null and their reason.
When the baseline backfill is still running the observation is returned without the deviation block and baseline is null with a reason - the measurement itself is still valid.
When the lookback window holds no usable observation (no overpass, or every scene cloud masked) the endpoint returns 422 insufficient_data with detail.reason, like every other synchronous product - never a 200 that looks like a measurement.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/indices/latest?index=NDVI' \ -H 'Authorization: Bearer sk_live_...'Asynchronous job polling.
GET /v1/jobs/{job_id} - Poll an asynchronous job
Section titled “GET /v1/jobs/{job_id} - Poll an asynchronous job”Returns the status of a job created by an asynchronous endpoint such as POST /v1/damage/assess or POST /v1/parcels/bulk.
A job that belongs to another client returns 404 not_found - not 403 - so that job identifiers cannot be probed across accounts.
curl https://api.agrovisio.ai/v1/jobs/job_7A1MNPQRSTVWXYZ01234 \ -H 'Authorization: Bearer sk_live_...'Parametric Index
Section titled “Parametric Index”Index definitions, evaluation and backtest.
POST /v1/index-definitions - Create a parametric index definition
Section titled “POST /v1/index-definitions - Create a parametric index definition”Creates a draft index definition. Drafts are free and can be evaluated and backtested as many times as you like; only POST /v1/index-definitions/{id}/publish starts the annual fee.
The target is exactly one of parcel_id, geometry or district_code. A geometry target is normalised and its geometry hash is frozen at creation time, so a future change in our geometry handling cannot silently move the satellite data a signed policy is based on.
The whole method configuration (quality classes, baseline rules, district aggregation) is frozen into the definition as well. Editing index-1.0.yaml later therefore affects new definitions only.
Triggers come in two forms and exactly one must be present: {"op": "<", "pct_of_baseline": 65} compares the window value against the parcel’s own history, {"op": "<", "absolute": 0.40} compares it against a fixed number and needs no baseline at all.
A published definition is immutable. Editing it returns 409 definition_published; to change the methodology create a new definition with supersedes pointing at the old one. The old definition keeps working and keeps every evaluation it has already produced, because policies written against it are still in force.
curl -X POST 'https://api.agrovisio.ai/v1/index-definitions' \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 4f1c8e20-2f1a-4a9d-9d1e-6a0b3c5d7e91' \ -H 'Content-Type: application/json' \ -d '{"name":"Winter wheat June drought","target":{"parcel_id":"prc_9F3AMNPQRSTVWXYZ0123"},"index":"NDVI","window":{"start":"06-01","end":"06-30"},"aggregation":"mean","baseline_years":5,"trigger":{"op":"<","pct_of_baseline":65},"min_observations":3}'GET /v1/index-definitions - List index definitions
Section titled “GET /v1/index-definitions - List index definitions”Cursor paginated list, newest first. Offsets are not supported: they are unstable while definitions are being added. Pass the returned next_cursor back as ?cursor= until has_more is false.
?status=published returns the definitions currently in force, ?status=draft the ones still being prepared. ?index=NDVI keeps only definitions of that index (case-insensitive). Filters are applied before pagination, so has_more and next_cursor stay correct when they are combined with limit.
curl 'https://api.agrovisio.ai/v1/index-definitions?status=published&limit=50' \ -H 'Authorization: Bearer sk_live_...'GET /v1/index-definitions/{definition_id} - Read one index definition
Section titled “GET /v1/index-definitions/{definition_id} - Read one index definition”Returns the definition exactly as it will be evaluated, including the frozen config_hash and method_version. Quote both in the policy document: together with scene_ids from each evaluation they are the full reproducibility key.
curl 'https://api.agrovisio.ai/v1/index-definitions/idx_7A1MNPQRSTVWXYZ01234' \ -H 'Authorization: Bearer sk_live_...'PATCH /v1/index-definitions/{definition_id} - Edit a draft index definition
Section titled “PATCH /v1/index-definitions/{definition_id} - Edit a draft index definition”Edits a draft. A published definition is immutable. Editing it returns 409 definition_published; to change the methodology create a new definition with supersedes pointing at the old one. The old definition keeps working and keeps every evaluation it has already produced, because policies written against it are still in force.
The method configuration is re-frozen on every edit, so a draft always carries the configuration that was current when it was last touched, and a published definition carries the one that was current when it was published.
curl -X PATCH 'https://api.agrovisio.ai/v1/index-definitions/idx_7A1MNPQRSTVWXYZ01234' \ -H 'Authorization: Bearer sk_live_...' \ -H 'Content-Type: application/json' \ -d '{"trigger":{"op":"<","pct_of_baseline":70}}'GET /v1/index-definitions/{definition_id}/backtest - Backtest an index over past seasons
Section titled “GET /v1/index-definitions/{definition_id}/backtest - Backtest an index over past seasons”Applies exactly the same logic to each past season and returns the per year outcome plus trigger_rate - the number you price the policy with.
Only closed windows are counted. The current, unfinished season is skipped: including it would systematically lower the trigger rate and therefore the premium.
trigger_rate divides by evaluated years, not by requested years. A season with no usable scenes is not a season that ‘did not trigger’; counting it as one would understate the risk. Check years_insufficient before trusting the rate.
Backtests are included in the annual index fee and are not charged separately, but they run on the expensive rate limit pool: ten years with a five year baseline reads fifteen seasons of history.
curl 'https://api.agrovisio.ai/v1/index-definitions/idx_7A1MNPQRSTVWXYZ01234/backtest?years=10' \ -H 'Authorization: Bearer sk_live_...'GET /v1/index-definitions/{definition_id}/evaluate - Evaluate an index for one season
Section titled “GET /v1/index-definitions/{definition_id}/evaluate - Evaluate an index for one season”Evaluates the definition for one year and returns whether it triggered, together with every scene used.
triggered is null, never false, when the data was not sufficient. A window needs at least min_observations good quality observations; below that the answer is status: insufficient_data with a reason. A cloudy scene is never turned into a zero - a zero NDVI would look like a triggered drought index and pay out a claim that did not happen.
Once the definition is published and the window has closed, the result is stored and every later call returns that stored result (final: true). An open window returns final: false and will keep changing as scenes arrive.
The answer is self-contained evidence: threshold_pct and trigger repeat the threshold that was applied, so a claim file does not need to quote the definition separately, and evidence_url is the permanent address of the stored record - present only when the result is final.
This endpoint never waits for Copernicus: it reads the shared satellite cache. A parcel whose history is still being backfilled reports insufficient_data rather than blocking for minutes.
curl 'https://api.agrovisio.ai/v1/index-definitions/idx_7A1MNPQRSTVWXYZ01234/evaluate?year=2026' \ -H 'Authorization: Bearer sk_live_...'POST /v1/index-definitions/{definition_id}/publish - Publish an index definition
Section titled “POST /v1/index-definitions/{definition_id}/publish - Publish an index definition”Freezes the definition and starts the annual fee of EUR 600 per index, charged once at publication (backtests are included).
From this moment the definition is immutable and every evaluation of a closed window is stored and returned unchanged forever: a later reprocessing of the satellite archive must not move a payout decision that has already been made.
Publishing twice is a no-op and is not charged twice - the second call returns the same document with Idempotent-Replay: true.
curl -X POST 'https://api.agrovisio.ai/v1/index-definitions/idx_7A1MNPQRSTVWXYZ01234/publish' \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 9f0c1d33-77aa-4c2e-8a0e-1b2c3d4e5f60'Parcels
Section titled “Parcels”Parcel registry - the billing unit.
GET /v1/cadastre/lookup - Look up a parcel geometry by cadastral number
Section titled “GET /v1/cadastre/lookup - Look up a parcel geometry by cadastral number”Resolves a national cadastral or LPIS block number to a geometry without creating a parcel. Use it to show the farmer’s field on a map before you commit to monitoring it.
Lithuania is served from the national LPIS layer. A number that does not exist returns 404 cadastre_not_found with detail.source. A parcel that exists but is not registered as farmland returns 200 with land_use: "non_agricultural" and a warning - satellite products on a parking lot are meaningless, but that is your decision, not ours. Registries that are not connected yet return 502 upstream_unavailable naming the registry.
curl 'https://api.agrovisio.ai/v1/cadastre/lookup?country=LT&no=000467-3875' \ -H 'Authorization: Bearer sk_live_...'POST /v1/parcels - Create a parcel
Section titled “POST /v1/parcels - Create a parcel”Registers one parcel from a GeoJSON geometry or a national cadastral number. The parcel is the billing unit: you pay for the peak number of monitored parcels per month, never per request.
Returns 201 for a new parcel and 200 with the existing parcel when the same geometry is already registered for your account (identical geom_hash).
data_status tells you how much history the parcel already has. When another account already monitors the same geometry the history is available immediately; otherwise a backfill is queued and baseline_ready stays false until it finishes. Anomaly values are not returned before that - a deviation computed from three observations looks exactly like a real one.
curl -X POST https://api.agrovisio.ai/v1/parcels \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 6f1c2f7e-1f0e-4a0a-9a8e-1b2c3d4e5f60' \ -H 'Content-Type: application/json' \ -d '{"external_ref":"POLICY-2026-00412","geometry":{"type":"Polygon","coordinates":[[[24.0,55.0],[24.005,55.0],[24.005,55.003],[24.0,55.003],[24.0,55.0]]]}}'GET /v1/parcels - List parcels
Section titled “GET /v1/parcels - List parcels”Cursor paginated list, newest first. Offsets are not supported: they are unstable while parcels are being added. Pass the returned next_cursor back as ?cursor= until has_more is false.
Filters are validated the same way as the create body: an unknown land_use value, a country that is not an ISO 3166-1 alpha-2 code or a query parameter that does not exist (offset, a misspelt filter) returns 400 validation_error with the supported values in detail, never a silently unfiltered or empty list.
Geometries are not included in the list - fetch GET /v1/parcels/{parcel_id} for one parcel’s geometry.
curl 'https://api.agrovisio.ai/v1/parcels?country=LT&limit=100' \ -H 'Authorization: Bearer sk_live_...'POST /v1/parcels/bulk - Bulk import parcels
Section titled “POST /v1/parcels/bulk - Bulk import parcels”Imports many parcels in one asynchronous job and returns 202 with a job_id. Poll GET /v1/jobs/{job_id} or subscribe to the job.completed webhook.
A single bad row never fails the import: the job result lists every rejected row with its zero based index, so you can find it in your own file. Rows whose geometry already exists for your account are counted as duplicates, not as errors.
The result also carries rows, one entry per submitted row in submission order: {index, external_ref, status, parcel_id, error} with status one of created, duplicate or failed. A duplicate row carries the parcel_id of the existing parcel it repeats, so every line of your file can be mapped to a parcel without a second lookup; parcel_ids lists only the created ones.
The parcel quota is checked before the job is created, but only against the rows that can actually create a parcel: rows that repeat a geometry already registered for your account (or another row of the same file) do not count, so re-importing a file you already loaded is accepted even at the quota limit. 429 parcel_quota_exceeded is returned immediately when the new rows alone do not fit the plan.
curl -X POST https://api.agrovisio.ai/v1/parcels/bulk \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 6f1c2f7e-1f0e-4a0a-9a8e-1b2c3d4e5f60' \ -H 'Content-Type: application/json' \ -d '{"parcels":[{"external_ref":"POLICY-1","cadastral_no":"000467-3875","country":"LT"}]}'GET /v1/parcels/{parcel_id} - Get one parcel
Section titled “GET /v1/parcels/{parcel_id} - Get one parcel”Returns the parcel with its canonical geometry and data_status.
A parcel that belongs to another account returns 404, never 403: a 403 would confirm that the identifier exists.
curl https://api.agrovisio.ai/v1/parcels/prc_9F3AKMNPQRSTVWXYZ012 \ -H 'Authorization: Bearer sk_live_...'DELETE /v1/parcels/{parcel_id} - Remove a parcel from monitoring
Section titled “DELETE /v1/parcels/{parcel_id} - Remove a parcel from monitoring”Stops monitoring the parcel. The row is soft deleted: the data stays available to support and audits for 90 days and the satellite cache is kept, because it is shared by geometry hash.
Billing: the parcel leaves your quota from the start of the next period. The current period is billed on its peak parcel count, so deleting on the last day of the month does not reduce that month’s invoice.
The call is idempotent: deleting a parcel you already deleted returns 204 again, so a retry after a network failure is safe. 404 means the identifier was never yours. The original deleted_at is kept.
curl -X DELETE https://api.agrovisio.ai/v1/parcels/prc_9F3A... \ -H 'Authorization: Bearer sk_live_...'Parcel risk scores and portfolio aggregates.
GET /v1/parcels/{parcel_id}/risk-score - Explainable parcel risk score
Section titled “GET /v1/parcels/{parcel_id}/risk-score - Explainable parcel risk score”Returns a 0-100 risk score (higher is riskier), its class A-E and the six components it was built from.
Every component is a raw indicator normalised to 0-100 by its percentile rank in a reference population: parcels in the same country and the same land use class. A score therefore answers ‘how does this parcel compare with its peers this season’, not ‘how does it compare with an absolute curve’. In a drought year the whole population moves, and a parcel that merely followed the country does not silently become high risk.
Weights: vegetation_trend 0.25, sar_events 0.20, water_heat_stress 0.20, yield_variability 0.15, soil_topography 0.10, land_use_compliance 0.10. A component that cannot be computed is dropped and the remaining weights are renormalised to 1.0; it still appears in components with available: false and a reason. When three or more components are missing the score is not computed at all and status becomes insufficient_data.
The score is produced by arithmetic only. No language model takes part in deriving it.
Scores for a past as_of are immutable: the stored result is returned unchanged, so a score quoted in a credit file a year ago can still be reproduced from model_version and config_hash.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A2B7C4D1E5F6A8B0C/risk-score?as_of=2026-09-01' \ -H 'Authorization: Bearer sk_live_...'GET /v1/portfolios/{portfolio_id}/parcels - Ranked parcel list for a portfolio
Section titled “GET /v1/portfolios/{portfolio_id}/parcels - Ranked parcel list for a portfolio”Every parcel in the portfolio with its latest score on or before as_of, highest score first, as a cursor paginated list. summary stops at the 20 riskiest parcels; this is the endpoint for ‘every class D and E parcel in the Kaunas district’ or ‘everything above 60’.
Filters: class (A-E), min_score (0-100) and district (a district_code). Parcels without a usable score are listed after every scored parcel with score: null and a status that says why (insufficient_data, or not_scored when nothing has been computed yet); class and min_score exclude them, because they cannot match. A parcel’s class is the one its rounded score falls into, exactly as risk-score reports it.
Offsets are not supported. Pass the returned next_cursor back as ?cursor= until has_more is false; an unknown query parameter is 400 validation_error, never a silently unfiltered list. A portfolio with no parcels at all is a 404, a filter that matches nothing is an empty page.
portfolio_id selects parcels you already registered: default is your whole book, district:LT-KAU one district, land_use:arable one land use class and country:LT one country. An unknown selector is a 404 rather than a silent widening to every parcel.
curl 'https://api.agrovisio.ai/v1/portfolios/default/parcels?class=D&min_score=60&limit=100' \ -H 'Authorization: Bearer sk_live_...'POST /v1/portfolios/{portfolio_id}/risk-score/refresh - Recompute every score in a portfolio
Section titled “POST /v1/portfolios/{portfolio_id}/risk-score/refresh - Recompute every score in a portfolio”Queues a batch recomputation and returns 202 with a job_id. Poll /v1/jobs/{job_id}; risk.score_changed webhooks are emitted for parcels whose class actually moved, and job.completed when the batch ends.
This is an expensive pool call: a 10 000 parcel book is hours of work, not a request you retry in a loop. Repeating the same Idempotency-Key returns the existing job instead of starting a second one.
portfolio_id selects parcels you already registered: default is your whole book, district:LT-KAU one district, land_use:arable one land use class and country:LT one country. An unknown selector is a 404 rather than a silent widening to every parcel.
curl -X POST 'https://api.agrovisio.ai/v1/portfolios/default/risk-score/refresh' \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 7b1f0c2e-9a44-4c1e-8f2d-0a5b6c7d8e9f'GET /v1/portfolios/{portfolio_id}/summary - Aggregated portfolio view
Section titled “GET /v1/portfolios/{portfolio_id}/summary - Aggregated portfolio view”Area weighted portfolio score, the A-E distribution, the change against the score 90 days ago and the 20 highest scoring parcels with your own external_ref.
Parcels that have no usable score are not averaged in. They are reported separately as parcels_without_score, because for a lender the difference between ‘this book is safe’ and ‘a quarter of this book is unknown’ is the whole point.
portfolio_id selects parcels you already registered: default is your whole book, district:LT-KAU one district, land_use:arable one land use class and country:LT one country. An unknown selector is a 404 rather than a silent widening to every parcel.
curl 'https://api.agrovisio.ai/v1/portfolios/default/summary' \ -H 'Authorization: Bearer sk_live_...'Sentinel-1 events: lodging, mowing, harvest, tillage.
GET /v1/parcels/{parcel_id}/sar - Sentinel-1 VV/VH backscatter time series
Section titled “GET /v1/parcels/{parcel_id}/sar - Sentinel-1 VV/VH backscatter time series”Calibrated sigma0 backscatter in dB for the parcel, one row per Sentinel-1 scene.
Every comparison is made within one relative orbit and one orbit direction. Scenes from different orbits see the parcel from a different angle, so mixing them produces events that never happened. Scenes without orbit metadata are excluded rather than guessed. That is why observations are grouped into tracks: each track is one relative orbit and one direction, and only scenes inside a track may be compared with each other.
Values are means inside a 10 m negative buffer, so field edges, ditches and roads do not drag the signal. A value is either measured or null with a reason - never interpolated.
Parcels below 1 ha return 422 insufficient_data: at that size the buffered parcel no longer contains enough Sentinel-1 pixels to mean anything.
orbit_dir and rel_orbit (1-175) restrict the answer to one track. When scenes exist but none is on the requested orbit, the response is 422 insufficient_data with detail.reason: no_scenes_for_filter and detail.available_tracks listing the orbits that do cover the parcel.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/sar?from=2026-06-01&to=2026-08-01' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/sar/events - Detected SAR events (lodging, harvest, mowing, tillage, regrowth)
Section titled “GET /v1/parcels/{parcel_id}/sar/events - Detected SAR events (lodging, harvest, mowing, tillage, regrowth)”Runs the sar-events-2.0 detector over the Sentinel-1 scenes of the parcel and returns the events it is confident about.
Every comparison is made within one relative orbit and one orbit direction. Scenes from different orbits see the parcel from a different angle, so mixing them produces events that never happened. Scenes without orbit metadata are excluded rather than guessed.
Confidence. Every event carries confidence (0.40*signal + 0.15*scenes + 0.25*phenology + 0.20*ndvi) and a label: high from 0.75, medium from 0.50, low from 0.40. Detections below 0.40 are stored for calibration but never returned - candidates_suppressed tells you how many there were.
Phenology. A lodging signal in March is not lodging. Each rule is checked against a crop and country specific day-of-year window; signals outside it lose confidence instead of being emitted silently.
Not enough data (parcel below 1 ha, or fewer than three scenes in one orbit within 30 days) returns 422 insufficient_data with detail.reason, never an empty list that looks like ‘no events’.
A change is measured between two neighbouring scenes, so the detector reads 30 days of scenes before from as context. You get back only the events whose window ends inside the range you asked for - asking for a single week still works.
Events are returned newest first, ordered by detected_window.from descending.
affected_ha and affected_pct are null here: detection works on the parcel mean, and per-pixel zoning is what the damage product does. A guessed area in an insurance claim costs more than no area.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/sar/events?from=2026-06-01&to=2026-08-01&type=lodging' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/sar/soil-moisture - Relative soil moisture from Sentinel-1
Section titled “GET /v1/parcels/{parcel_id}/sar/soil-moisture - Relative soil moisture from Sentinel-1”Relative soil moisture derived from VV backscatter (sar-moisture-1.0).
This is a relative index, not a volumetric measurement. unit is always relative_index. 0 means as dry as this parcel has been in the last 24 months on this orbit, 1 means as wet. The dry and wet ends come from the 5th and 95th VV percentile of the same relative orbit - comparing across orbits would compare different viewing geometries. Only scenes with a known NDVI < 0.45 enter the percentiles: a scene under canopy is biomass, not moisture, and would pull the wet end towards vegetation. reference.samples is the number of such scenes, reference.scenes_in_lookback the number of all scenes on that orbit in the 24 months.
Vegetation masks the soil. The index is only computed when NDVI < 0.45; above that the radar sees the canopy, not the ground. Those dates come back with value: null and status: vegetation_masked, and a window where every date is masked returns 422 vegetation_masked.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/sar/soil-moisture?from=2026-07-01&to=2026-07-31' \ -H 'Authorization: Bearer sk_live_...'Thermal
Section titled “Thermal”Land surface temperature and water stress.
GET /v1/parcels/{parcel_id}/thermal - Land surface temperature and anomaly versus the 10 year norm
Section titled “GET /v1/parcels/{parcel_id}/thermal - Land surface temperature and anomaly versus the 10 year norm”Sentinel-3 SLSTR land surface temperature for the parcel, with the anomaly against a ten year norm for the same day of year (+/- 7 days), built from the Sentinel-3 archive and NASA POWER T2M_MAX as specified by thermal-wsi-1.0.
Resolution is the limit of this product. Sentinel-3 SLSTR has a 1 km native pixel, resampled onto a 300 m grid. Parcels below 5 ha are refused with 422 insufficient_resolution; between 5 and 20 ha the response carries resolution_warning: true, a plain language note and a confidence already multiplied by 0.6. The caveat travels with the number, not only in this documentation.
A temperature is either measured or null with a reason (cloud_masked, no_valid_pixels). The anomaly is null with z_reason whenever the norm rests on too few samples or too little spread - a 0.0 anomaly would read as ‘a normal day’.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/thermal?from=2026-07-01&to=2026-07-31' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/water-stress - Water stress index, class and consecutive stress days
Section titled “GET /v1/parcels/{parcel_id}/water-stress - Water stress index, class and consecutive stress days”Water stress index (thermal-wsi-1.0) combining the land surface temperature anomaly with the NDWI anomaly:
raw = 0.6 * z_lst - 0.4 * z_ndwiWSI = 1 / (1 + exp(-1.2 * (raw - 1.0)))stress_class is low below 0.30, moderate below 0.60 and high from 0.60. consecutive_stress_days is the longest uninterrupted run with WSI >= 0.60 - over the whole season when season is given, otherwise in the last 30 days before to - the number that separates a hot week from a drought.
With season=YYYY the headline fields (water_stress_index, stress_class, lst_celsius, anomaly_vs_baseline, confidence) describe the season’s peak, the observation with the highest index, and headline_basis is season_peak: a drought in June must not be hidden by a mild December. Without season they describe the latest measured observation (headline_basis: latest). peak is returned in both cases and latest_date is always the date of the latest measured observation.
When NDWI is missing (clouds, or no baseline yet) the index is computed from the temperature term alone and confidence is multiplied by 0.7. When the temperature baseline itself is missing, the index is null with a reason: an insurer reading WSI = 0 would conclude the parcel is fine.
Resolution is the limit of this product. Sentinel-3 SLSTR has a 1 km native pixel, resampled onto a 300 m grid. Parcels below 5 ha are refused with 422 insufficient_resolution; between 5 and 20 ha the response carries resolution_warning: true, a plain language note and a confidence already multiplied by 0.6. The caveat travels with the number, not only in this documentation.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/water-stress?season=2026' \ -H 'Authorization: Bearer sk_live_...'Weather
Section titled “Weather”Forecast, history and agro-climate indicators.
GET /v1/parcels/{parcel_id}/weather/forecast - 7 day forecast for the parcel
Section titled “GET /v1/parcels/{parcel_id}/weather/forecast - 7 day forecast for the parcel”Daily forecast at the parcel centroid: maximum and minimum temperature, precipitation and its probability, maximum wind and FAO-56 reference evapotranspiration.
Fields the provider did not return are null - they are never filled in from a neighbouring day.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/weather/forecast?days=7' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/weather/history - Daily weather history from 1981
Section titled “GET /v1/parcels/{parcel_id}/weather/history - Daily weather history from 1981”Daily temperature, precipitation and humidity for the parcel centroid, plus the NASA POWER season aggregate.
History starts in 1981 and ends today: an earlier from or a to in the future returns 400 date_range_invalid (detail.reason: to_in_future; future days are the forecast endpoint’s job). One request may cover at most 10 years. The archive lags a few days behind real time, so the newest days may be missing from daily; days says how many were returned.
The summary block comes from a second provider (NASA POWER). If that provider does not answer, summary is null with summary_reason and the daily series is still returned.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/weather/history?from=2025-04-01&to=2025-09-01' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/weather/indicators - Agroclimate indicators for a season
Section titled “GET /v1/parcels/{parcel_id}/weather/indicators - Agroclimate indicators for a season”Growing degree days (base 5 C and 10 C, capped at 30 C), heat days above 30 C, frost days, dry days and the longest dry spell for the growing season (day of year 90-290, truncated at today).
Thresholds live in the versioned method configuration weather-1.0; the response repeats them so a number computed a year ago can be reproduced.
If less than 60% of the days in the window have data, the endpoint returns 422 insufficient_data with the coverage in detail (coverage_pct, days_counted, days_expected), like every other synchronous product - a half season of growing degree days looks exactly like a cold season, so no number is returned at all.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3A/weather/indicators?season=2026' \ -H 'Authorization: Bearer sk_live_...'Webhooks
Section titled “Webhooks”Event subscriptions and signed delivery.
GET /v1/webhooks - List webhook endpoints
Section titled “GET /v1/webhooks - List webhook endpoints”curl https://api.agrovisio.ai/v1/webhooks \ -H 'Authorization: Bearer sk_live_...'POST /v1/webhooks - Register a webhook endpoint
Section titled “POST /v1/webhooks - Register a webhook endpoint”Registers an HTTPS endpoint for the given event types and returns the signing secret once (an idempotent retry of the same request returns the same response).
The URL must be https://, at most 2048 characters, without credentials, and must point to a public host: loopback, private, link-local, cloud-metadata and internal names are rejected with validation_error (detail.fields[].field == "url").
Every delivery is signed: X-Agrovisio-Signature: v1=hex(hmac_sha256(secret, timestamp + '.' + body)). Reject the delivery when |now - X-Agrovisio-Timestamp| > 300.
curl -X POST https://api.agrovisio.ai/v1/webhooks \ -H 'Authorization: Bearer sk_live_...' \ -H 'Idempotency-Key: 6f1c2f7e-1f0e-4a0a-9a8e-1b2c3d4e5f60' \ -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/agrovisio","events":["sar.event_detected","damage.completed"]}'GET /v1/webhooks/events - Webhook event catalog
Section titled “GET /v1/webhooks/events - Webhook event catalog”Lists every event type that can be subscribed to.
curl https://api.agrovisio.ai/v1/webhooks/events \ -H 'Authorization: Bearer sk_live_...'DELETE /v1/webhooks/{endpoint_id} - Delete a webhook endpoint
Section titled “DELETE /v1/webhooks/{endpoint_id} - Delete a webhook endpoint”Deletes the endpoint together with its delivery history; pending retries are dropped.
curl -X DELETE https://api.agrovisio.ai/v1/webhooks/<id> \ -H 'Authorization: Bearer sk_live_...'Yield forecasts with confidence intervals.
GET /v1/parcels/{parcel_id}/yield - Yield forecast (alias of /yield-forecast)
Section titled “GET /v1/parcels/{parcel_id}/yield - Yield forecast (alias of /yield-forecast)”Identical to GET /v1/parcels/{parcel_id}/yield-forecast. Both paths are supported and return the same document.
Returns a yield forecast for one parcel with a mandatory confidence interval.
yield_t_ha = base_yield(crop) * f_ndvi * f_weather * f_disease, where f_ndvi is the season NDVI integral divided by the same integral of the parcel baseline (clipped to 0.4-1.4), and f_weather charges 0.6% per heat day above 30 C after the first five and 0.4% per dry day after the first ten. Every factor is returned with the reason it took its value - a single number without them cannot be argued with.
The width of the interval follows the growth phase: +/-40% before sowing, +/-20% during vegetative growth, +/-10% at flowering, +/-5% at ripening and +/-3% just before harvest. Quote the interval, not the point estimate.
Without a crop the forecast is not produced: this endpoint returns 400 crop_required and puts ndvi_integral and pct_of_baseline in error.detail, so you still get the vegetation picture. Pass ?crop= or store crop_hint on the parcel.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3AMNPQRSTVWXYZ0123/yield-forecast?crop=winter_wheat&season=2026' \ -H 'Authorization: Bearer sk_live_...'GET /v1/parcels/{parcel_id}/yield-forecast - Yield forecast with a confidence interval
Section titled “GET /v1/parcels/{parcel_id}/yield-forecast - Yield forecast with a confidence interval”Returns a yield forecast for one parcel with a mandatory confidence interval.
yield_t_ha = base_yield(crop) * f_ndvi * f_weather * f_disease, where f_ndvi is the season NDVI integral divided by the same integral of the parcel baseline (clipped to 0.4-1.4), and f_weather charges 0.6% per heat day above 30 C after the first five and 0.4% per dry day after the first ten. Every factor is returned with the reason it took its value - a single number without them cannot be argued with.
The width of the interval follows the growth phase: +/-40% before sowing, +/-20% during vegetative growth, +/-10% at flowering, +/-5% at ripening and +/-3% just before harvest. Quote the interval, not the point estimate.
Without a crop the forecast is not produced: this endpoint returns 400 crop_required and puts ndvi_integral and pct_of_baseline in error.detail, so you still get the vegetation picture. Pass ?crop= or store crop_hint on the parcel.
curl 'https://api.agrovisio.ai/v1/parcels/prc_9F3AMNPQRSTVWXYZ0123/yield-forecast?crop=winter_wheat&season=2026' \ -H 'Authorization: Bearer sk_live_...'