# NeedsDirt API — Full Reference > Complete API documentation for AI agents integrating with needsdirt.com Base URL: `https://needsdirt.com/api` --- ## Authentication All authenticated requests require the `X-Agent-Key` header: ``` X-Agent-Key: nd_your_api_key_here ``` Keys are prefixed with `nd_` and are 68 characters long. Store securely — they are shown only once at creation. Optional headers: - `Idempotency-Key: ` — Prevents duplicate operations on retries (recommended for all POST/PATCH/DELETE) --- ## Account Provisioning Flow ### Step 1: Request Provisioning Token ``` POST /api/agent/provision Content-Type: application/json { "email": "agent@example.com" } ``` **Response (200):** ```json { "provisionToken": "ndp_abc123...", "expiresAt": "2026-04-16T09:00:00.000Z", "nextStep": "POST /api/agent/register with this token to create your account" } ``` Rate limit: 3 requests per minute per IP. No authentication required. Token expires in 24 hours. One use only. Returns 409 if email already has an account. ### Step 2: Create Account ``` POST /api/agent/register Content-Type: application/json { "provisionToken": "ndp_abc123...", "name": "Agent Name", "password": "securepassword123", "company": "Construction Co", "phone": "555-0100", "address": "123 Main St, Louisville, KY", "zipCode": "40202" } ``` Required: `provisionToken`, `name`, `password` (min 8 chars) Optional: `company`, `phone`, `address`, `zipCode` If the ZIP code has fewer than 10 free users, the account gets free access (ZIP_FREE status). **Response (200):** ```json { "userId": "clxyz...", "email": "agent@example.com", "subscriptionStatus": "NONE", "nextStep": "POST /api/agent/billing/setup to add a payment method" } ``` ### Step 3: Add Payment Method ``` POST /api/agent/billing/setup Content-Type: application/json { "userId": "clxyz..." } ``` Also accepts `X-Agent-Key` header or session cookie for authentication. **Response (200):** ```json { "clientSecret": "seti_xxx_secret_yyy", "setupIntentId": "seti_xxx", "customerId": "cus_xxx", "nextStep": "Use clientSecret with Stripe.js confirmCardSetup() then POST /api/agent/billing/confirm" } ``` The `clientSecret` is used to call Stripe directly — NeedsDirt never sees card numbers (PCI compliant). ### Step 4: Confirm Billing & Get API Key ``` POST /api/agent/billing/confirm Content-Type: application/json { "setupIntentId": "seti_xxx", "subscribe": true, "keyName": "My Agent Key" } ``` `subscribe: true` creates a $25/month subscription. Set to `false` if account already has free access. **Response (200):** ```json { "apiKey": "nd_abc123...", "keyPrefix": "nd_abc123", "scopes": ["READ", "WRITE", "BILLING"], "rateLimitRpm": 60, "warning": "Store this key securely — it will not be shown again.", "usage": { "header": "X-Agent-Key: ", "example": "curl -H 'X-Agent-Key: nd_...' https://needsdirt.com/api/listings" } } ``` --- ## API Key Management ### List Keys ``` GET /api/agent/keys X-Agent-Key: nd_... ``` **Response (200):** ```json { "keys": [ { "id": "clxyz...", "keyPrefix": "nd_abc123", "name": "My Agent Key", "scopes": ["READ", "WRITE", "BILLING"], "status": "ACTIVE", "rateLimitRpm": 60, "lastUsedAt": "2026-04-15T09:00:00.000Z", "createdAt": "2026-04-15T08:00:00.000Z" } ] } ``` ### Create Key ``` POST /api/agent/keys X-Agent-Key: nd_... Content-Type: application/json { "name": "Read-Only Key", "scopes": ["READ"] } ``` Maximum 10 active keys per account. WRITE and BILLING scopes require an active subscription. **Response (200):** ```json { "apiKey": "nd_def456...", "keyPrefix": "nd_def456", "name": "Read-Only Key", "scopes": ["READ"], "rateLimitRpm": 60, "warning": "Store this key securely — it will not be shown again." } ``` ### Revoke Key ``` DELETE /api/agent/keys?id=clxyz... X-Agent-Key: nd_... ``` **Response (200):** ```json { "revoked": true, "keyId": "clxyz..." } ``` --- ## Listings ### Browse Listings ``` GET /api/listings?lat=38.25&lng=-85.76&maxMiles=50&type=DIRT_AVAILABLE X-Agent-Key: nd_... ``` Query parameters (all optional): - `lat` — Latitude for distance sorting - `lng` — Longitude for distance sorting - `maxMiles` — Filter by maximum distance - `type` — Filter by listing type: `DIRT_AVAILABLE`, `DUMP_AVAILABLE`, `DIRT_NEEDED`, `DUMP_NEEDED` Requires READ scope and active subscription. **Response (200):** ```json { "listings": [ { "id": "clxyz...", "type": "DIRT_AVAILABLE", "title": "Clean fill dirt - 500 yards", "description": "Clean fill from residential excavation...", "quantity": 500, "pricePerYard": 5.00, "isFree": false, "address": "123 Main St, Louisville, KY", "lat": 38.2527, "lng": -85.7585, "status": "ACTIVE", "expiresAt": "2026-05-15T00:00:00.000Z", "createdAt": "2026-04-15T00:00:00.000Z", "distanceMiles": 2.3, "user": { "name": "John Smith", "company": "Smith Excavation", "phone": "555-0100", "email": "john@example.com" } } ] } ``` ### Create Listing ``` POST /api/listings X-Agent-Key: nd_... Content-Type: application/json { "type": "DIRT_AVAILABLE", "title": "Clean fill dirt - 500 yards", "description": "Clean fill from residential excavation, no debris", "quantity": 500, "pricePerYard": 5.00, "isFree": false, "enablePayments": true, "address": "123 Main St, Louisville, KY 40202", "imageUrls": ["https://example.com/photo1.jpg"] } ``` Required: `type`, `title` (5-120 chars), `description` (10-2000 chars), `quantity`, `address` Optional: `pricePerYard`, `isFree`, `enablePayments`, `imageUrls` (max 6) Requires WRITE scope and active subscription. Listings expire after 30 days. **Response (201):** Full listing object with images. ### Get Listing ``` GET /api/listings/{id} X-Agent-Key: nd_... ``` Returns listing details. Owner only (matches userId on the key's account). Requires READ scope. ### Update Listing ``` PATCH /api/listings/{id} X-Agent-Key: nd_... Content-Type: application/json { "title": "Updated title", "quantity": 300, "status": "COMPLETED" } ``` All fields optional. Only the listing owner can update. Requires WRITE scope. Updatable fields: `title`, `description`, `quantity`, `pricePerYard`, `isFree`, `enablePayments`, `address`, `status` (ACTIVE, COMPLETED, EXPIRED) ### Delete Listing ``` DELETE /api/listings/{id} X-Agent-Key: nd_... ``` Permanently deletes the listing. Owner only. Requires WRITE scope. **Response (200):** ```json { "success": true } ``` --- ## Listing Types | Type | Description | |------|-------------| | `DIRT_AVAILABLE` | Excess fill dirt that needs to be hauled away | | `DUMP_AVAILABLE` | Land accepting clean fill, clay, or topsoil | | `DIRT_NEEDED` | Job site that needs fill material | | `DUMP_NEEDED` | Contractor looking for a legal dump site | ## Subscription Statuses | Status | Access | |--------|--------| | `ACTIVE` | Full access | | `TRIALING` | Full access (trial period) | | `ZIP_FREE` | Full access (free tier) | | `TEAM` | Full access (team plan) | | `NONE` | No access — subscription required | | `PAST_DUE` | Limited — payment failed | | `CANCELED` | No access | ## Rate Limits - **Per API key:** 60 requests per minute - **Provisioning:** 3 requests per minute per IP - Rate-limited responses return HTTP 429 with `Retry-After` header ## Error Responses All errors return JSON: ```json { "error": "Description of what went wrong" } ``` | Status | Meaning | |--------|---------| | 400 | Invalid request body or parameters | | 401 | Missing or invalid API key | | 403 | Insufficient scope or subscription required | | 404 | Resource not found | | 409 | Conflict (e.g., email already registered) | | 410 | Token expired or already used | | 429 | Rate limit exceeded | | 500 | Internal server error | ## Idempotency For write operations, include an `Idempotency-Key` header with a unique value (e.g., UUID). This prevents duplicate operations if your agent retries a failed request. ``` Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` --- ## Quick Start Example ```bash # 1. Get a provisioning token curl -X POST https://needsdirt.com/api/agent/provision \ -H "Content-Type: application/json" \ -d '{"email": "agent@example.com"}' # 2. Create account curl -X POST https://needsdirt.com/api/agent/register \ -H "Content-Type: application/json" \ -d '{"provisionToken": "ndp_...", "name": "My Agent", "password": "secure123", "zipCode": "40202"}' # 3. Browse listings (after getting API key) curl -H "X-Agent-Key: nd_..." \ "https://needsdirt.com/api/listings?lat=38.25&lng=-85.76&maxMiles=25" # 4. Create a listing curl -X POST https://needsdirt.com/api/listings \ -H "X-Agent-Key: nd_..." \ -H "Content-Type: application/json" \ -d '{"type": "DIRT_AVAILABLE", "title": "Clean fill - 200 yards", "description": "Clean excavation fill, ready for pickup", "quantity": 200, "address": "456 Oak St, Louisville, KY 40202"}' ``` ## Contact - support@needsdirt.com - https://needsdirt.com