# AgMission Data Export API — Customer Integration Guide **Audience**: Technical integrators, BI teams, data warehouse engineers **Version**: 1.0 **Last Updated**: April 2026 --- ## Table of Contents 1. [Overview](#overview) 2. [Quick Start](#quick-start) 3. [Authentication](#authentication) 4. [API Endpoints](#api-endpoints) 5. [Rate Limiting](#rate-limiting) 6. [Data Formats](#data-formats) 7. [Use Cases](#use-cases) 8. [Error Handling](#error-handling) 9. [Support & SLAs](#support--slas) --- ## Overview The **AgMission Data Export API** provides programmatic access to spray application data for integration with business intelligence tools, data warehouses, and custom systems. ### Capabilities - **Real-time session summaries** — Coverage, timing, pilot, aircraft info (GET `/api/v1/jobs/:jobId/sessions`) - **Raw GPS trace records** — Point-by-point telemetry with cursor pagination (GET `/api/v1/jobs/:jobId/sessions/:fileId/records`) - **Spray area polygons** — GeoJSON boundaries for mapping (GET `/api/v1/jobs/:jobId/areas`) - **Async bulk export** — CSV or GeoJSON for full data lake ingestion (POST/GET `/api/v1/jobs/:jobId/export`) ### Who Should Use This API | Role | Use Case | |---|---| | BI Engineer | Power BI incremental refresh, Tableau connectors | | Data Warehouse | Nightly batch loads, transformation pipelines | | GIS Analyst | ArcGIS layer ingestion, spatial analysis | | Compliance Officer | Audit trails, proof-of-application records | | Agronomist | Yield correlation, efficacy analysis | ### Architecture ```mermaid graph TD ext[Your System] gw[AgMission API Gateway - Auth and Rate Limiting] sess[GET /api/v1/jobs/:id/sessions] recs[GET /api/v1/jobs/:id/sessions/:fid/records] areas[GET /api/v1/jobs/:id/areas] exp[POST /api/v1/jobs/:id/export] stat[GET /api/v1/exports/:id] dl[GET /api/v1/exports/:id/download] db[(MongoDB - Applications and GPS Trace)] ext -->|X-API-Key over HTTPS| gw gw --> sess gw --> recs gw --> areas gw --> exp gw --> stat gw --> dl sess --> db recs --> db areas --> db exp --> db stat --> db dl --> db style ext fill:#e3f2fd style gw fill:#f3e5f5 style db fill:#e8f5e9 ``` --- ## Quick Start ### 1. Get an API Key Contact your AgMission account manager or self-serve at `https://agmission.agnav.com/api-keys`: ``` ak_test_3v8x2j9kL4m5nQ6... (test key) ak_live_7p2r9w4tY3h8k1... (production key) ``` ### 2. List sessions for a job ```bash JOB_ID=12345 API_KEY="ak_test_3v8x2j9kL4m5nQ6..." curl -X GET "https://api.agmission.com/api/v1/jobs/${JOB_ID}/sessions" \ -H "X-API-Key: ${API_KEY}" ``` **Response**: ```json { "jobId": 12345, "clientId": "507f1f77bcf86cd799439055", "clientName": "Fazenda São Paulo Ltda", "mappedArea_ha": 48.5, "reportConfirmed": false, "areaSize_ha": 48.5, "coverage_ha": 45.2, "overSprayedPct": -6.8, "appRate": 50, "appRateUnit": "lit/ha", "appRateConfirmed": null, "sprayVolume": 2260, "volumeUnit": "lit", "useActualVolume": false, "actualVolume": null, "effectiveVolume": 2260, "useCustomWeather": false, "weather": null, "data": [ { "sessionId": "507f1f77bcf86cd799439011", "fileName": "flight_20260422_001.log", "startDateTime": "2026-04-22T09:00:00Z", "endDateTime": "2026-04-22T11:30:00Z", "totalFlightTime_s": 9000, "totalSprayTime_s": 7200, "totalTurnTime_s": 1800, "totalSprayed_ha": 45.2, "totalSprayMat": 2260, "totalSprayMatUnit": "lit", "avgSpraySpeed_ms": 39.5, "appRate": 50, "appRateUnit": "lit/ha", "matType": "wet", "flowController": "SatLoc G4", "sprayOnLag_s": 0.2, "sprayOffLag_s": 0.15, "pulsesPerLiter": 1800, "sprayZoneName": "Field A North", "sprayZoneArea_ha": 25.0, "files": [ { "fileId": "507f1f77bcf86cd799439022", "name": "flight_20260422_001.log" } ], "sessionPilotName": "John Smith", "pilotId": "507f1f77bcf86cd799439033", "pilotName": "John Smith", "aircraftName": "AT-802F", "aircraftTailNumber": "N1234AT", "assignedDate": "2026-04-21T18:00:00Z", "reportConfirmed": false, "areaSize_ha": 48.5, "coverage_ha": 45.2, "appRateConfirmed": null, "sprayVolume": 2260, "volumeUnit": "lit", "useActualVolume": false, "actualVolume": null, "effectiveVolume": 2260 } ] } ``` ### 3. Export to CSV ```bash JOB_ID=12345 API_KEY="ak_test_3v8x2j9kL4m5nQ6..." # Trigger export (async) EXPORT_ID=$(curl -s -X POST "https://api.agmission.com/api/v1/jobs/${JOB_ID}/export" \ -H "X-API-Key: ${API_KEY}" \ -H "Content-Type: application/json" \ -d '{"format":"csv","units":"metric"}' \ | jq -r '.exportId') echo "Export ID: $EXPORT_ID" # Poll for completion while true; do STATUS=$(curl -s -X GET "https://api.agmission.com/api/v1/exports/${EXPORT_ID}" \ -H "X-API-Key: ${API_KEY}" \ | jq -r '.status') echo "Status: $STATUS" if [ "$STATUS" = "ready" ]; then break fi sleep 5 done # Download curl -X GET "https://api.agmission.com/api/v1/exports/${EXPORT_ID}/download" \ -H "X-API-Key: ${API_KEY}" \ -o "export_job${JOB_ID}.csv" echo "Downloaded: export_job${JOB_ID}.csv" ``` --- ## Authentication ### API Key Format API keys are **Bearer tokens** supplied via the `X-API-Key` header (NOT `Authorization` header). ```mermaid sequenceDiagram participant Client as Your System participant API as AgMission API participant Auth as Auth Middleware participant DB as ApiKey Store Client->>API: GET /api/v1/jobs/123/sessions Note over Client,API: Header: X-API-Key: ak_live_abc123... API->>Auth: Verify key Auth->>DB: Lookup by prefix first 8 chars DB-->>Auth: Key candidate Auth->>Auth: bcrypt.compare key vs stored hash Auth-->>API: req.uid set to account owner API-->>Client: 200 JSON response ``` **DO NOT use** `Authorization: Bearer ak_test_...` — This will fail! ```bash # ✅ CORRECT curl -H "X-API-Key: ak_test_3v8x2j9kL4m5nQ6..." \ https://api.agmission.com/api/v1/jobs/12345/sessions # ❌ WRONG curl -H "Authorization: Bearer ak_test_3v8x2j9kL4m5nQ6..." \ https://api.agmission.com/api/v1/jobs/12345/sessions ``` ### Key Management - **Create** new keys at `https://agmission.agnav.com/api-keys` - **Rotate** keys by creating new ones and disabling old ones - **Scope** keys by job or account (coming soon) - **Revoke** immediately if compromised ### Security Best Practices 1. **Never commit keys to version control** — Use environment variables or secrets manager ```bash export AGMISSION_API_KEY="ak_test_..." curl -H "X-API-Key: $AGMISSION_API_KEY" https://api.agmission.com/... ``` 2. **Use HTTPS only** — API endpoints enforce TLS 1.3+ 3. **Rotate keys quarterly** — Implement key rotation in your automation 4. **Monitor key usage** — Check activity logs for suspicious patterns --- ## API Endpoints ### 1. List Sessions **Endpoint**: `GET /api/v1/jobs/:jobId/sessions` Returns one summary per uploaded flight log file. **Parameters**: - `jobId` (path) — Job ID (integer) **Response** (200 OK): ```json { "jobId": 12345, "mappedArea_ha": 48.5, "reportConfirmed": false, "areaSize_ha": 48.5, "coverage_ha": 45.2, "overSprayedPct": -6.8, "appRate": 50, "appRateUnit": "lit/ha", "appRateConfirmed": null, "sprayVolume": 2260, "volumeUnit": "lit", "useActualVolume": false, "actualVolume": null, "effectiveVolume": 2260, "useCustomWeather": false, "weather": null, "data": [ { "sessionId": "507f1f77bcf86cd799439011", "fileName": "flight_20260422_001.log", "startDateTime": "2026-04-22T09:00:00Z", "endDateTime": "2026-04-22T11:30:00Z", "totalFlightTime_s": 9000, "totalSprayTime_s": 7200, "totalTurnTime_s": 1800, "totalSprayed_ha": 45.2, "totalSprayMat": 2260, "totalSprayMatUnit": "lit", "avgSpraySpeed_ms": 39.5, "appRate": 50, "appRateUnit": "lit/ha", "matType": "wet", "flowController": "SatLoc G4", "sprayOnLag_s": 0.2, "sprayOffLag_s": 0.15, "pulsesPerLiter": 1800, "sprayZoneName": "Field A North", "sprayZoneArea_ha": 25.0, "files": [ { "fileId": "507f1f77bcf86cd799439022", "name": "flight_20260422_001.log" } ], "sessionPilotName": "John Smith", "pilotId": "507f1f77bcf86cd799439033", "pilotName": "John Smith", "aircraftName": "AT-802F", "aircraftTailNumber": "N1234AT", "assignedDate": "2026-04-21T18:00:00Z", "reportConfirmed": false, "areaSize_ha": 48.5, "coverage_ha": 45.2, "appRateConfirmed": null, "sprayVolume": 2260, "volumeUnit": "lit", "useActualVolume": false, "actualVolume": null, "effectiveVolume": 2260 } ] } ``` **Confirmed vs Fallback Values**: When `reportConfirmed: true`, the applicator has manually confirmed spray records in Report Settings: - `areaSize_ha`, `coverage_ha`, `appRate`, `actualVolume`, `weather` come from the report - Otherwise, system-calculated fallbacks are used --- ### 2. Get Records (Paginated GPS Trace) **Endpoint**: `GET /api/v1/jobs/:jobId/sessions/:fileId/records` Streams raw GPS points with cursor-based pagination. **Parameters**: - `jobId` (path) — Job ID - `fileId` (path) — Session/file ID - `startingAfter` (query) — Cursor for pagination - `limit` (query) — Records per page (default 500, max 2000) - `interval` (query) — GPS thinning interval in seconds (float) **Example**: Fetch 500 records, every 5 seconds ```bash curl "https://api.agmission.com/api/v1/jobs/12345/sessions/507f1f77.../records?limit=500&interval=5" \ -H "X-API-Key: ak_test_..." ``` **Response** (200 OK): ```json { "data": [ { "timeUtc": "2026-04-22T09:00:15Z", "gpsTime": 1745312415, "lat": 40.7128, "lon": -74.0060, "alt": 150.5, "grSpeed": 39.8, "heading": 180, "sprayStat": 1, "flowRateApplied": 48.5, "appRateApplied": 49.3, "windSpeed_kt": 6.22, "windDir_deg": 225, "temp_c": 22.5, "humidity_pct": 65 } ], "hasMore": true, "startingAfter": "507f191e810c19729de8605f", "endingBefore": "507f1f77bcf86cd799439011" } ``` **Cursor field meanings:** - `startingAfter` — pass as query param to get the next page - `endingBefore` — pass as query param to get the previous page - `hasMore: false` + no `startingAfter` means you have reached the last page **Pagination**: ```bash # Get next page curl "https://api.agmission.com/api/v1/jobs/12345/sessions/507f1f77.../records?startingAfter=507f191e810c19729de8605f" \ -H "X-API-Key: ak_test_..." ``` **Use Cases**: - **Power BI incremental refresh**: Use `startingAfter` to fetch only new records since last sync - **Lightweight queries**: Use `interval=5` to reduce data volume by 5x - **Real-time dashboards**: Long-poll this endpoint every 10 seconds --- ### 3. Get Spray Areas **Endpoint**: `GET /api/v1/jobs/:jobId/areas` Returns GeoJSON FeatureCollection of planned spray zones. **Response** (200 OK): ```json { "type": "FeatureCollection", "jobId": 12345, "features": [ { "type": "Feature", "properties": { "name": "North Field", "type": "area", "area_ha": 48.5, "appRate": 50, "appRateUnit": "lit/ha" }, "geometry": { "type": "Polygon", "coordinates": [[ [-74.0060, 40.7128], [-74.0050, 40.7128], [-74.0050, 40.7118], [-74.0060, 40.7118] ]] } }, { "type": "Feature", "properties": { "name": "Exclude - Power Lines", "type": "xcl" }, "geometry": { "type": "Polygon", "coordinates": [[ [-74.0055, 40.7125], [-74.0053, 40.7125], [-74.0053, 40.7120] ]] } } ] } ``` **Field Meanings**: - `type: "area"` — Planned spray zone (will have appRate/unit) - `type: "xcl"` — Exclusion zone (no-spray boundary, skipped fields) - `area_ha` — Polygon area in hectares - `appRateUnit` — Material unit string (`'lit/ha'`, `'oz/ac'`, etc.) **Import to ArcGIS**: ```javascript // JavaScript + ArcGIS JS API const response = await fetch('https://api.agmission.com/api/v1/jobs/12345/areas', { headers: { 'X-API-Key': apiKey } }); const featureCollection = await response.json(); const layer = new FeatureLayer({ source: featureCollection.features, objectIdField: 'OBJECTID', fields: [...], renderer: {...} }); map.add(layer); ``` --- ### 4. Trigger Export (Async) **Endpoint**: `POST /api/v1/jobs/:jobId/export` Initiates async generation of a bulk export. ```mermaid stateDiagram-v2 [*] --> pending: POST /export returns 202 pending --> processing: async generation starts processing --> ready: file written to disk processing --> error: generation failed ready --> [*]: 24h TTL expires error --> [*]: TTL expires ``` Poll `GET /exports/:exportId` until `status: "ready"`, then call the download endpoint. **Request Body**: ```json { "format": "csv", "units": "metric", "interval": null } ``` **Parameters**: - `format` (string) — `"csv"` or `"geojson"` - `units` (string, optional) — `"metric"` (default) or `"us"` - `interval` (number, optional) — GPS point thinning in seconds (float) - `fm` (boolean, optional) — `true` to include Flight Master/AgDisp FM fields (`sprayHeight_m`, `driftX_m`, `driftY_m`, `depositX_m`, `depositY_m`, `radarAlt_m`, `laserAlt_m`). Default `false`. Only applicable for customers with FM-enabled equipment. **Response** (202 Accepted): ```json { "exportId": "66f4a8c1...", "status": "pending", "format": "csv", "units": "metric", "createdAt": "2026-04-22T14:00:00Z" } ``` **Status Codes**: - `202` — Export created and queued - `200` — Existing export reused (deduplication — same job/format/units within 5 minutes): ```json { "exportId": "66f4a8c1...", "status": "ready", "format": "csv", "units": "metric", "createdAt": "2026-04-22T14:00:00Z", "reused": true, "downloadUrl": "/api/v1/exports/66f4a8c1.../download" } ``` - `429` — Rate limit exceeded (check `Retry-After` header) - `409` — Invalid parameters > **Deduplication**: If you POST the same `jobId + format + units` within 5 minutes, the server returns the existing export (HTTP 200) instead of creating a new one. When `reused: true` and `status: "ready"`, `downloadUrl` is included immediately — skip polling. --- ### 5. Poll Export Status **Endpoint**: `GET /api/v1/exports/:exportId` Check generation progress. **Response** (200 OK — Pending): ```json { "exportId": "66f4a8c1...", "status": "pending", "format": "csv", "units": "metric", "createdAt": "2026-04-22T14:00:00Z", "expiresAt": null } ``` **Response** (200 OK — Ready): ```json { "exportId": "66f4a8c1...", "status": "ready", "format": "csv", "units": "metric", "createdAt": "2026-04-22T14:00:00Z", "expiresAt": "2026-04-23T14:00:00Z", "downloadUrl": "/api/v1/exports/66f4a8c1.../download" } ``` **Response** (200 OK — Error): ```json { "exportId": "66f4a8c1...", "status": "error", "error": "Job has no app data to export", "createdAt": "2026-04-22T14:00:00Z" } ``` **Polling Best Practice**: ```python import time import requests def poll_export(export_id, api_key, max_wait_seconds=600): start = time.time() while time.time() - start < max_wait_seconds: response = requests.get( f'https://api.agmission.com/api/v1/exports/{export_id}', headers={'X-API-Key': api_key} ) data = response.json() if data['status'] == 'ready': return data['downloadUrl'] if data['status'] == 'error': raise Exception(f"Export failed: {data.get('error')}") # Exponential backoff: 1s, 2s, 4s, ... time.sleep(min(2 ** (time.time() - start) / 10, 30)) raise TimeoutError('Export generation timeout') ``` --- ### 6. Download Export **Endpoint**: `GET /api/v1/exports/:exportId/download` Stream the ready file. **Response** (200 OK): ``` Content-Type: text/csv (or application/geo+json) Content-Disposition: attachment; filename="export_job12345_66f4a8c1.csv" [Binary file stream] ``` **Examples**: ```bash # Download as file curl -X GET "https://api.agmission.com/api/v1/exports/66f4a8c1.../download" \ -H "X-API-Key: ak_test_..." \ -o "export_$(date +%Y%m%d).csv" ``` ```python # Python with requests import requests response = requests.get( 'https://api.agmission.com/api/v1/exports/66f4a8c1.../download', headers={'X-API-Key': api_key}, stream=True ) with open('export.csv', 'wb') as f: for chunk in response.iter_content(8192): f.write(chunk) ``` ```javascript // JavaScript / Node.js fetch('https://api.agmission.com/api/v1/exports/66f4a8c1.../download', { headers: { 'X-API-Key': apiKey } }) .then(r => r.blob()) .then(blob => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'export.csv'; a.click(); }); ``` --- ## Rate Limiting See [DATA_EXPORT_API_RATE_LIMITING.md](DATA_EXPORT_API_RATE_LIMITING.md) for comprehensive rate limit documentation including examples and best practices. **Quick Reference**: | Header | Meaning | |---|---| | `RateLimit-Limit: 20` | Max requests per account per window | | `RateLimit-Remaining: 18` | Requests left in current window | | `RateLimit-Reset: 1745353200` | Unix timestamp of window reset | | `Retry-After: 45` | Seconds to wait before retrying (on 429) | --- ## Data Formats ### CSV Export Columns All CSV exports include these columns (order may vary by unit system): **Job/Session Metadata** (repeated per record): - `jobId` — Job ID - `orderNumber` — Customer PO number - `jobName` — Job name - `clientId` — Client account ID (the applicator's customer this job was done for) - `clientName` — Client account name - `sessionId` — Flight file ID - `fileName` — Log file name - `pilotName` — Pilot name **GPS Data**: - `timeUtc` — ISO 8601 timestamp - `lat` — Latitude (decimal degrees) - `lon` — Longitude (decimal degrees) - `alt_m` (metric) / `alt_ft` (US) — Altitude - `grSpeed_ms` (metric) / `groundSpeed_mph` (US) — Ground speed **Application Data**: - `appRateApplied_Lha` (metric) / `appRateApplied_galAc` (US) — Actual application rate - `flowRateApplied_Lmin` (metric) / `flowRateApplied_galMin` (US) — Spray system flow rate - `swathWidth_m` (metric) / `swathWidth_ft` (US) — Boom width **Environment**: - `windSpeed_kt` (metric) / `windSpeed_mph` (US) — Wind speed (knots / mph) - `windDir_deg` — Wind direction (0-360°) - `temp_c` (metric) / `temp_f` (US) — Temperature - `humidity_pct` — Relative humidity ### GeoJSON Export Format Each point becomes a Feature with Point geometry: ```json { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-74.0060, 40.7128, 150.5] }, "properties": { "timeUtc": "2026-04-22T09:00:15Z", "sprayStat": 1, "grSpeed": 39.8 } } ``` --- ## Use Cases ### Use Case 1: Power BI Incremental Refresh **Goal**: Update a Power BI dataset nightly with new GPS records. **Solution**: ```python import requests from datetime import datetime, timedelta def sync_to_powerbi(job_id, api_key): # Get sessions sessions = requests.get( f'https://api.agmission.com/api/v1/jobs/{job_id}/sessions', headers={'X-API-Key': api_key} ).json() for session in sessions['data']: file_id = session['sessionId'] # Paginate records cursor = None records = [] while True: params = {'limit': 2000} if cursor: params['startingAfter'] = cursor page = requests.get( f'https://api.agmission.com/api/v1/jobs/{job_id}/sessions/{file_id}/records', params=params, headers={'X-API-Key': api_key} ).json() records.extend(page['data']) if not page.get('hasMore'): break cursor = page.get('startingAfter') # Push to Power BI (REST API or XMLA endpoint) # ... ``` ### Use Case 2: ArcGIS Map Automation **Goal**: Update ArcGIS Online layer with spray area boundaries. ```javascript const job_id = 12345; const api_key = 'ak_test_...'; // Fetch areas const areaResponse = await fetch( `https://api.agmission.com/api/v1/jobs/${job_id}/areas`, { headers: { 'X-API-Key': api_key } } ); const areas = await areaResponse.json(); // Convert to Feature Service format const features = areas.features.map(feature => ({ geometry: feature.geometry, attributes: { name: feature.properties.name, type: feature.properties.type, area_ha: feature.properties.area_ha } })); // Add to ArcGIS layer via REST API const updateResponse = await fetch( 'https://services.arcgis.com/.../updates', { method: 'POST', body: new URLSearchParams({ features: JSON.stringify(features), token: agolToken }) } ); ``` ### Use Case 3: Nightly Data Warehouse Load **Goal**: Daily batch load all jobs' data into a data lake (S3, Snowflake, etc.). ```bash #!/bin/bash API_KEY="ak_live_..." JOBS=(12345 12346 12347) S3_BUCKET="s3://company-spray-data" DATE=$(date +%Y%m%d) for job_id in "${JOBS[@]}"; do echo "Exporting job $job_id..." # Trigger export export_id=$(curl -s -X POST "https://api.agmission.com/api/v1/jobs/${job_id}/export" \ -H "X-API-Key: ${API_KEY}" \ -H "Content-Type: application/json" \ -d '{"format":"csv","units":"metric"}' \ | jq -r '.exportId') # Poll until ready while true; do status=$(curl -s -X GET "https://api.agmission.com/api/v1/exports/${export_id}" \ -H "X-API-Key: ${API_KEY}" \ | jq -r '.status') [ "$status" = "ready" ] && break sleep 5 done # Download and upload to S3 curl -s -X GET "https://api.agmission.com/api/v1/exports/${export_id}/download" \ -H "X-API-Key: ${API_KEY}" \ | aws s3 cp - "${S3_BUCKET}/spray_data/job${job_id}/data_${DATE}.csv" echo "Completed: job $job_id → ${S3_BUCKET}/spray_data/job${job_id}/data_${DATE}.csv" done ``` --- ## Error Handling ### Error Response Format All errors follow this structure: ```json { "error": { ".tag": "error_constant", "message": "Human-readable details (dev mode only)" } } ``` ### Common HTTP Status Codes | Code | Condition | Solution | |---|---|---| | 200 | Success | — | | 202 | Export accepted (async) | Poll `/exports/:exportId` for completion | | 400 | Bad request (invalid params) | Check endpoint docs for required fields | | 401 | Invalid/missing API key | Verify `X-API-Key` header is present and valid | | 404 | Resource not found | Check jobId, exportId, fileId exist and belong to your account | | 409 | Conflict (e.g., invalid format) | Check format is `"csv"` or `"geojson"` | | 429 | Rate limit exceeded | Wait `Retry-After` seconds, see rate limit docs | | 500 | Server error | Retry with exponential backoff; contact support if persists | ### Example: Handling 429 Rate Limit ```python import time import requests def request_with_backoff(url, api_key, max_retries=3): for attempt in range(max_retries): response = requests.get( url, headers={'X-API-Key': api_key} ) if response.status_code == 429: retry_after = int(response.headers.get('Retry-After', 60)) print(f"Rate limited. Waiting {retry_after} seconds...") time.sleep(retry_after) continue response.raise_for_status() return response.json() raise Exception("Max retries exceeded") ``` --- ## Support & SLAs ### Support Channels | Channel | Response Time | |---|---| | **Email**: `support@agnav.com` | 4 hours (business hours) | | **Phone**: 1-800-AGNAV-11 | 1 hour (9am-5pm ET) | | **Slack** (Enterprise): Dedicated channel | 1 hour | ### API SLA - **Availability**: 99.5% monthly uptime - **Rate limit quota**: 20 requests/min per account (configurable) - **Export timeout**: 1 hour max generation time - **File retention**: 24 hours after ready - **Data accuracy**: ±0.5% for area/volume calculations ### Status & Maintenance - **Status Page**: `https://status.agmission.com` - **Maintenance windows**: Tuesdays 2-4 AM ET (announced 7 days prior) - **Incident response**: PagerDuty escalation, max 15-min response ### API Versioning Current version: **v1** - Breaking changes will be announced 90 days in advance - Deprecation warnings via response headers: `Deprecation: true` - Version support policy: At least 3 versions maintained simultaneously --- ## Appendix: Code Examples ### cURL Examples ```bash # List sessions curl -X GET https://api.agmission.com/api/v1/jobs/12345/sessions \ -H "X-API-Key: ak_test_..." \ -H "Accept: application/json" # Get records with thinning curl "https://api.agmission.com/api/v1/jobs/12345/sessions/507f1f77.../records?interval=5&limit=1000" \ -H "X-API-Key: ak_test_..." # Trigger CSV export curl -X POST https://api.agmission.com/api/v1/jobs/12345/export \ -H "X-API-Key: ak_test_..." \ -H "Content-Type: application/json" \ -d '{"format":"csv","units":"metric"}' ``` ### JavaScript / Node.js ```javascript const apiKey = 'ak_test_...'; async function fetchSessions(jobId) { const response = await fetch(`https://api.agmission.com/api/v1/jobs/${jobId}/sessions`, { headers: { 'X-API-Key': apiKey } }); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); } async function exportAndDownload(jobId) { // Trigger export const exportRes = await fetch(`https://api.agmission.com/api/v1/jobs/${jobId}/export`, { method: 'POST', headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ format: 'csv', units: 'metric' }) }); const { exportId } = await exportRes.json(); // Poll for ready let status = 'pending'; while (status !== 'ready') { const statusRes = await fetch(`https://api.agmission.com/api/v1/exports/${exportId}`, { headers: { 'X-API-Key': apiKey } }); ({ status } = await statusRes.json()); if (status !== 'ready') await new Promise(r => setTimeout(r, 5000)); } // Download return fetch(`https://api.agmission.com/api/v1/exports/${exportId}/download`, { headers: { 'X-API-Key': apiKey } }); } ``` --- **Contact**: `technical-support@agnav.com` **Last Updated**: April 22, 2026 **Next Review**: October 22, 2026