{ "info": { "name": "AgMission — Data Export API", "description": "End-to-end testing collection for the Data Export API.\n\n## Setup\n1. Set the `baseUrl` variable to your server (e.g. `https://localhost:4100`).\n2. Log in via **[Auth] Login** — the `jwt` variable is captured automatically.\n3. Use **[Keys] Create API Key** — the `apiKey` variable is captured automatically.\n4. Set `jobId` and `fileId` to real IDs from your database.\n\n## Folders\n- **[Auth]** — Get a JWT for key-management endpoints\n- **[Keys]** — Manage API keys (`/api/keys`, JWT-protected)\n- **[Public API]** — Data export endpoints (`/api/v1/`, X-API-Key protected)\n- **[Export Workflow]** — Full async export flow (trigger → poll → download)", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ { "key": "baseUrl", "value": "https://localhost:4100", "type": "string" }, { "key": "jwt", "value": "", "type": "string", "description": "Captured automatically by the Login request" }, { "key": "apiKey", "value": "", "type": "string", "description": "Captured automatically by Create API Key" }, { "key": "keyId", "value": "", "type": "string", "description": "Captured automatically by Create API Key" }, { "key": "jobId", "value": "12345", "type": "string", "description": "AgMission job ID (integer)" }, { "key": "fileId", "value": "", "type": "string", "description": "AppFile _id — captured from Get Sessions response" }, { "key": "exportId", "value": "", "type": "string", "description": "Captured automatically by Trigger Export" } ], "item": [ { "name": "[Auth]", "item": [ { "name": "Login (get JWT)", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "if (r && r.token) {", " pm.collectionVariables.set('jwt', r.token);", " console.log('JWT captured');", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [{ "key": "Content-Type", "value": "application/json" }], "body": { "mode": "raw", "raw": "{\n \"username\": \"your@email.com\",\n \"password\": \"yourpassword\"\n}" }, "url": { "raw": "{{baseUrl}}/api/users/login", "host": ["{{baseUrl}}"], "path": ["api", "users", "login"] }, "description": "Standard AgMission login. Stores the returned JWT in the `jwt` collection variable for subsequent key-management requests." } } ] }, { "name": "[Keys] API Key Management", "description": "JWT-protected endpoints for managing API keys. Pass the JWT from the Login request in the Authorization header.", "item": [ { "name": "Create API Key", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "if (r && r.key) {", " pm.collectionVariables.set('apiKey', r.key);", " pm.collectionVariables.set('keyId', r._id);", " console.log('API Key captured — save it now, it will not be shown again:', r.key);", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Authorization", "value": "Bearer {{jwt}}" } ], "body": { "mode": "raw", "raw": "{\n \"label\": \"Postman Test Key\",\n \"service\": \"data_export\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/api/keys", "host": ["{{baseUrl}}"], "path": ["api", "keys"] }, "description": "Creates a new API key. The plain `key` field is returned **once** — the test script captures it to `apiKey`. `service` can be `data_export` (default) or `partner_api`." } }, { "name": "List API Keys", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "Bearer {{jwt}}" } ], "url": { "raw": "{{baseUrl}}/api/keys", "host": ["{{baseUrl}}"], "path": ["api", "keys"] }, "description": "Returns all keys (active and revoked) for the authenticated applicator. Admins can append `?ownerId=` to list another account's keys." } }, { "name": "Revoke API Key", "request": { "method": "DELETE", "header": [ { "key": "Authorization", "value": "Bearer {{jwt}}" } ], "url": { "raw": "{{baseUrl}}/api/keys/{{keyId}}", "host": ["{{baseUrl}}"], "path": ["api", "keys", "{{keyId}}"] }, "description": "Soft-deletes the key (sets `active: false`). Uses the `keyId` variable captured by Create API Key." } }, { "name": "Create API Key (Admin — on behalf of owner)", "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Authorization", "value": "Bearer {{jwt}}" } ], "body": { "mode": "raw", "raw": "{\n \"label\": \"Partner Integration Key\",\n \"service\": \"partner_api\",\n \"ownerId\": \"\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/api/keys", "host": ["{{baseUrl}}"], "path": ["api", "keys"] }, "description": "Admin-only. Creates a key for a different account by supplying `ownerId`. Key will have `managedBy: 'admin'`." } } ] }, { "name": "[Public API] /api/v1", "description": "External data-export endpoints. All require the `X-API-Key` header with the full 64-char hex key captured by Create API Key.", "item": [ { "name": "Get Sessions (job summary)", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "// Capture first fileId for the records endpoint", "if (r && r.sessions && r.sessions.length > 0) {", " pm.collectionVariables.set('fileId', r.sessions[0].fileId);", " console.log('fileId captured:', r.sessions[0].fileId);", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "X-API-Key", "value": "{{apiKey}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/jobs/{{jobId}}/sessions", "host": ["{{baseUrl}}"], "path": ["api", "v1", "jobs", "{{jobId}}", "sessions"] }, "description": "Returns one session summary per uploaded application file for the job.\n\n- `reportConfirmed: false` means the applicator has not yet confirmed values in Report Settings — re-fetch when this flips.\n- `avgSpraySpeed` is in m/s (metric) or mph (US — not applicable to this endpoint, metric only).\n- Captures the first `fileId` for use in the records endpoint." } }, { "name": "Get Session Records (raw GPS trace)", "request": { "method": "GET", "header": [ { "key": "X-API-Key", "value": "{{apiKey}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/jobs/{{jobId}}/sessions/{{fileId}}/records?limit=100&interval=1", "host": ["{{baseUrl}}"], "path": ["api", "v1", "jobs", "{{jobId}}", "sessions", "{{fileId}}", "records"], "query": [ { "key": "limit", "value": "100", "description": "Max records per page (default 500, max 2000)" }, { "key": "interval", "value": "1", "description": "Return one record per N seconds of GPS time (thinning). Remove for all points." }, { "key": "startingAfter", "value": "", "description": "Cursor for next page — use _id from last record of previous page", "disabled": true } ] }, "description": "Paginated raw GPS trace for one session file. Cursor-based pagination using `startingAfter=`.\n\n`sprayStat` values:\n- `0` = spray off\n- `1` / `2` = spray on (application data)\n- `3` = segment START marker (filtered out automatically)" } }, { "name": "Get Spray Areas (GeoJSON)", "request": { "method": "GET", "header": [ { "key": "X-API-Key", "value": "{{apiKey}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/jobs/{{jobId}}/areas", "host": ["{{baseUrl}}"], "path": ["api", "v1", "jobs", "{{jobId}}", "areas"] }, "description": "Returns a GeoJSON FeatureCollection of planned spray-area polygons for the job. Suitable for ArcGIS / QGIS import." } } ] }, { "name": "[Export Workflow] Async Bulk Export", "description": "Full async export flow: POST trigger → GET poll until ready → GET download.\n\nRun in order:\n1. Trigger Export\n2. Poll Export Status (repeat until `status` = `ready`)\n3. Download Export File", "item": [ { "name": "1 — Trigger Export (CSV, metric)", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "if (r && r.exportId) {", " pm.collectionVariables.set('exportId', r.exportId);", " console.log('exportId captured:', r.exportId);", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-API-Key", "value": "{{apiKey}}" } ], "body": { "mode": "raw", "raw": "{\n \"format\": \"csv\",\n \"interval\": 1,\n \"units\": \"metric\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/api/v1/jobs/{{jobId}}/export", "host": ["{{baseUrl}}"], "path": ["api", "v1", "jobs", "{{jobId}}", "export"] }, "description": "Triggers async export generation. Returns `exportId` immediately (status = `pending`).\n\nBody fields:\n- `format`: `csv` | `geojson`\n- `interval`: GPS thinning in seconds (omit for all points)\n- `units`: `metric` (default) | `us`" } }, { "name": "1 — Trigger Export (CSV, US units)", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "if (r && r.exportId) {", " pm.collectionVariables.set('exportId', r.exportId);", " console.log('exportId captured (US units):', r.exportId);", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-API-Key", "value": "{{apiKey}}" } ], "body": { "mode": "raw", "raw": "{\n \"format\": \"csv\",\n \"interval\": 1,\n \"units\": \"us\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/api/v1/jobs/{{jobId}}/export", "host": ["{{baseUrl}}"], "path": ["api", "v1", "jobs", "{{jobId}}", "export"] }, "description": "Same as CSV metric but with `units: 'us'`. Column headers will use US unit suffixes (e.g. `groundSpeed_mph`, `alt_ft`, `temp_f`, `appRateApplied_galAc`)." } }, { "name": "1 — Trigger Export (GeoJSON)", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "if (r && r.exportId) {", " pm.collectionVariables.set('exportId', r.exportId);", " console.log('exportId captured (GeoJSON):', r.exportId);", "}" ], "type": "text/javascript" } } ], "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-API-Key", "value": "{{apiKey}}" } ], "body": { "mode": "raw", "raw": "{\n \"format\": \"geojson\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "{{baseUrl}}/api/v1/jobs/{{jobId}}/export", "host": ["{{baseUrl}}"], "path": ["api", "v1", "jobs", "{{jobId}}", "export"] }, "description": "Triggers a GeoJSON FeatureCollection export with all GPS points (no thinning)." } }, { "name": "2 — Poll Export Status", "event": [ { "listen": "test", "script": { "exec": [ "const r = pm.response.json();", "console.log('Export status:', r.status, '| units:', r.units, '| format:', r.format);", "if (r.status === 'ready') {", " console.log('Ready to download:', r.downloadUrl);", "} else if (r.status === 'error') {", " console.error('Export failed:', r.error);", "}" ], "type": "text/javascript" } } ], "request": { "method": "GET", "header": [ { "key": "X-API-Key", "value": "{{apiKey}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/exports/{{exportId}}", "host": ["{{baseUrl}}"], "path": ["api", "v1", "exports", "{{exportId}}"] }, "description": "Poll until `status` = `ready`. When ready, the response includes `downloadUrl`. Possible statuses: `pending` | `processing` | `ready` | `error`." } }, { "name": "3 — Download Export File", "request": { "method": "GET", "header": [ { "key": "X-API-Key", "value": "{{apiKey}}" } ], "url": { "raw": "{{baseUrl}}/api/v1/exports/{{exportId}}/download", "host": ["{{baseUrl}}"], "path": ["api", "v1", "exports", "{{exportId}}", "download"] }, "description": "Streams the generated file with `Content-Disposition: attachment`. The file is deleted from disk after streaming (TTL = 24 hours). Only works when status = `ready`.\n\nIn Postman, use **Save Response → Save to a file** to download." } } ] } ] }