{
  "openapi": "3.1.0",
  "info": {
    "title": "NestBuddy Public Quote Intake & Discovery API",
    "version": "1.0.0",
    "description": "Public, zero-key API for NestBuddy — Brisbane Bayside's verified local moving marketplace. Enables autonomous AI assistants (ChatGPT, Claude, Cursor), RAG systems, and proptech applications to discover verified Bayside trades and submit multi-trade quote requests with 1-tap email verification.",
    "contact": {
      "name": "NestBuddy Developer Team",
      "url": "https://nestbuddy.com.au/developers",
      "email": "hi@nestbuddy.com.au"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://nestbuddy.com.au/terms"
    },
    "x-deprecation-policy": "https://nestbuddy.com.au/developers/deprecation-policy",
    "x-api-version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://nestbuddy.com.au",
      "description": "Production Server"
    },
    {
      "url": "http://localhost:3000",
      "description": "Local Development Server"
    }
  ],
  "paths": {
    "/api/v1/quotes": {
      "get": {
        "summary": "Discover Available Services & Trade Requirements",
        "description": "Returns available moving service categories, location requirements, brand trust context, sandbox status, and conversational chunking guidelines for AI agents.",
        "operationId": "discoverQuoteServices",
        "parameters": [
          {
            "name": "service_id",
            "in": "query",
            "required": false,
            "description": "Optional service ID filter (e.g. removalist, bond_clean, storage, gardener, conveyancer).",
            "schema": {
              "type": "string",
              "enum": ["removalist", "bond_clean", "storage", "gardener", "conveyancer"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Service registry metadata, sandbox instructions, and schema links successfully retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteDiscoveryResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Submit Agent Quote Request or Run Diagnostic Check",
        "description": "Submits a structured quote request or validates partial payloads using ?dry_run=true to return clarification prompts. Agents should chunk questions into 2-3 bite-sized steps (Basics -> Trade Specifics -> Contact) rather than asking all questions at once.",
        "operationId": "submitAgentQuote",
        "parameters": [
          {
            "name": "dry_run",
            "in": "query",
            "required": false,
            "description": "Set to true to validate payload without creating database records or sending emails (instant sandbox check).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Structured multi-trade moving quote request payload.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteSubmissionPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Diagnostic sandbox validation successful (dry_run=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteDiagnosticResponse"
                }
              }
            }
          },
          "202": {
            "description": "Quote drafted successfully and 1-tap verification email dispatched to customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteSuccessResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body provided.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          },
          "413": {
            "description": "Payload exceeds 100 KB limit.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed — returns structured clarification prompts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteValidationErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (too many requests).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/vendors": {
      "get": {
        "summary": "List Verified Marketing Vendors",
        "description": "Returns a curated list of verified local moving, cleaning, storage, gardening, and conveyancing businesses featured in the NestBuddy public directory.",
        "operationId": "listMarketingVendors",
        "parameters": [
          {
            "name": "service",
            "in": "query",
            "required": false,
            "description": "Filter vendors by trade category (e.g. removalist, bond_cleaning, storage, gardener, conveyancer).",
            "schema": {
              "type": "string",
              "enum": ["removalist", "bond_cleaning", "storage", "gardener", "conveyancer"]
            }
          },
          {
            "name": "featured",
            "in": "query",
            "required": false,
            "description": "Filter to only featured partner vendors.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of verified vendors successfully retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MarketingVendor"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Server error loading vendor directory.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/blog-posts": {
      "get": {
        "summary": "List Moving Guides and Advice Articles",
        "description": "Returns the most recent moving logistics guides, tenancy advice, and packing checklists published on the NestBuddy blog.",
        "operationId": "listMovingArticles",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of articles to return (default 5).",
            "schema": {
              "type": "integer",
              "default": 5,
              "minimum": 1,
              "maximum": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of published blog articles successfully retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BlogPost"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          },
          "500": {
            "description": "Server error retrieving blog articles.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfc7807ProblemDetails"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "RemovalistServiceDetails": {
        "type": "object",
        "required": ["pickup_access", "dropoff_access"],
        "properties": {
          "pickup_access": {
            "type": "string",
            "enum": ["ground_floor", "elevator", "stairs"],
            "description": "Access type at pickup location."
          },
          "dropoff_access": {
            "type": "string",
            "enum": ["ground_floor", "elevator", "stairs"],
            "description": "Access type at drop-off location."
          },
          "packing": {
            "type": "string",
            "enum": ["none", "pack", "unpack", "both"],
            "default": "none",
            "description": "Packing service assistance requested."
          },
          "special_items": {
            "type": "array",
            "items": { "type": "string" },
            "description": "List of delicate or oversized items (e.g. piano, pool table, fragile artwork)."
          },
          "disassembly": {
            "type": "boolean",
            "default": false,
            "description": "Whether furniture disassembly/reassembly is required."
          }
        }
      },
      "BondCleanServiceDetails": {
        "type": "object",
        "required": ["property_type", "furnished"],
        "properties": {
          "property_type": {
            "type": "string",
            "enum": ["unit", "townhouse", "house"],
            "description": "Type of residential dwelling."
          },
          "furnished": {
            "type": "string",
            "enum": ["empty_standard", "furnished_takes_longer", "empty", "furnished"],
            "description": "Furnishing state during cleaning (empty standard is typical for bond cleans)."
          },
          "carpets_steam": {
            "type": "boolean",
            "default": false,
            "description": "Include professional carpet steam cleaning."
          },
          "pest_treatment": {
            "type": "boolean",
            "default": false,
            "description": "Include end-of-lease flea/pest treatment certificate."
          }
        }
      },
      "StorageServiceDetails": {
        "type": "object",
        "required": ["storage_size", "duration"],
        "properties": {
          "storage_size": {
            "type": "string",
            "enum": ["small_unit", "medium_garage", "large_house", "few_items"],
            "description": "Required storage unit volume."
          },
          "duration": {
            "type": "string",
            "enum": ["under_1_month", "1_to_3_months", "3_to_6_months", "6_plus_months", "unsure"],
            "description": "Estimated duration of storage."
          }
        }
      },
      "GardenerServiceDetails": {
        "type": "object",
        "required": ["service_type", "property_size"],
        "properties": {
          "service_type": {
            "type": "string",
            "enum": ["once_off_cleanup", "regular_maintenance", "garden_design_landscaping"],
            "description": "Type of gardening work requested."
          },
          "property_size": {
            "type": "string",
            "enum": ["small_courtyard", "standard_block", "large_block_acreage"],
            "description": "Approximate yard size."
          },
          "green_waste_removal": {
            "type": "boolean",
            "default": true,
            "description": "Whether green waste haulage is required."
          }
        }
      },
      "ConveyancerServiceDetails": {
        "type": "object",
        "required": ["client_type"],
        "properties": {
          "client_type": {
            "type": "string",
            "enum": ["buying", "selling", "both"],
            "description": "Whether the client is buying or selling property in Queensland."
          }
        }
      },
      "ServiceDetailsMap": {
        "type": "object",
        "description": "Trade-specific requirements for each requested service.",
        "properties": {
          "removalist": { "$ref": "#/components/schemas/RemovalistServiceDetails" },
          "bond_clean": { "$ref": "#/components/schemas/BondCleanServiceDetails" },
          "storage": { "$ref": "#/components/schemas/StorageServiceDetails" },
          "gardener": { "$ref": "#/components/schemas/GardenerServiceDetails" },
          "conveyancer": { "$ref": "#/components/schemas/ConveyancerServiceDetails" }
        }
      },
      "ServiceNotesMap": {
        "type": "object",
        "description": "Optional notes per trade service.",
        "properties": {
          "removalist": { "type": "string" },
          "bond_clean": { "type": "string" },
          "storage": { "type": "string" },
          "gardener": { "type": "string" },
          "conveyancer": { "type": "string" }
        }
      },
      "QuoteSummary": {
        "type": "object",
        "required": ["services", "locations", "timing", "home"],
        "properties": {
          "services": {
            "type": "array",
            "items": { "type": "string" }
          },
          "locations": {
            "type": "object",
            "properties": {
              "from": { "type": "string", "example": "Wynnum QLD 4178" },
              "to": { "type": "string", "example": "Manly QLD 4179" }
            }
          },
          "timing": {
            "type": "object",
            "properties": {
              "date": { "type": "string", "example": "2026-11-20" },
              "date_mode": { "type": "string", "example": "on" }
            }
          },
          "home": {
            "type": "object",
            "properties": {
              "bedrooms": { "type": "string", "example": "3" },
              "bathrooms": { "type": "string", "example": "2" },
              "property_type": { "type": "string", "example": "house" }
            }
          }
        }
      },
      "ClarificationPromptItem": {
        "type": "object",
        "required": ["field", "prompt"],
        "properties": {
          "field": { "type": "string", "example": "pickup_access" },
          "service": { "type": "string", "example": "removalist" },
          "prompt": { "type": "string", "example": "What is the access like at the pickup address?" },
          "options": {
            "type": "array",
            "items": { "type": "string" },
            "example": ["ground_floor", "elevator", "stairs"]
          }
        }
      },
      "QuoteDiscoveryResponse": {
        "type": "object",
        "required": ["name", "version", "description", "available_services", "usage", "schema_links"],
        "properties": {
          "name": { "type": "string", "example": "NestBuddy Agentic Quote Intake API" },
          "version": { "type": "string", "example": "v1" },
          "description": { "type": "string" },
          "about_nestbuddy": { "type": "string" },
          "authentication": {
            "type": "object",
            "required": ["type", "free_tier", "instant_access"],
            "properties": {
              "type": { "type": "string", "example": "none_required" },
              "free_tier": { "type": "boolean", "example": true },
              "instant_access": { "type": "boolean", "example": true },
              "description": { "type": "string", "example": "NestBuddy is 100% free and open access for home movers and AI agents. No API key required." }
            }
          },
          "sandbox": {
            "type": "object",
            "required": ["available", "endpoint", "method"],
            "properties": {
              "available": { "type": "boolean", "example": true },
              "endpoint": { "type": "string", "example": "/api/v1/quotes?dry_run=true" },
              "method": { "type": "string", "example": "POST" },
              "description": { "type": "string", "example": "Instant zero-friction diagnostic validation sandbox mode." }
            }
          },
          "available_services": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "label", "requires_two_locations", "required_fields"],
              "properties": {
                "id": { "type": "string", "example": "removalist" },
                "label": { "type": "string", "example": "Removalist" },
                "requires_two_locations": { "type": "boolean", "example": true },
                "required_fields": {
                  "type": "array",
                  "items": { "type": "string" }
                }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "step_1_discovery": { "type": "string" },
              "step_2_diagnostic": { "type": "string" },
              "step_3_submission": { "type": "string" }
            }
          },
          "schema_links": {
            "type": "object",
            "properties": {
              "openapi": { "type": "string", "example": "/openapi.json" },
              "llms_txt": { "type": "string", "example": "/llms.txt" },
              "llms_full_txt": { "type": "string", "example": "/llms-full.txt" },
              "mcp_server": { "type": "string", "example": "/.well-known/mcp" },
              "deprecation_policy": { "type": "string", "example": "/developers/deprecation-policy" }
            }
          }
        }
      },
      "QuoteSubmissionPayload": {
        "type": "object",
        "required": ["services", "contact", "timing", "locations"],
        "properties": {
          "lead_id": {
            "type": "string",
            "description": "Optional UUID of an existing active quote request to amend or append additional trade services to."
          },
          "force_new": {
            "type": "boolean",
            "default": false,
            "description": "Set to true to bypass automatic lead consolidation and force the creation of a distinct new move request."
          },
          "services": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["removalist", "bond_clean", "storage", "gardener", "conveyancer"]
            },
            "description": "Array of requested service IDs."
          },
          "contact": {
            "type": "object",
            "required": ["name", "email", "phone"],
            "properties": {
              "name": { "type": "string", "example": "Jane Doe" },
              "email": { "type": "string", "format": "email", "example": "jane@example.com" },
              "phone": { "type": "string", "example": "0400123456" },
              "preference": { "type": "string", "enum": ["email", "sms", "phone"], "default": "email" }
            }
          },
          "timing": {
            "type": "object",
            "required": ["date"],
            "properties": {
              "date": { "type": "string", "example": "2026-11-20" },
              "date_mode": { "type": "string", "enum": ["on", "before", "after", "flexible"], "default": "on" },
              "time_of_day": { "type": "string", "enum": ["morning", "afternoon", "flexible"] }
            }
          },
          "locations": {
            "type": "object",
            "properties": {
              "from": { "type": "string", "example": "Wynnum QLD 4178" },
              "to": { "type": "string", "example": "Manly QLD 4179" },
              "suburb": { "type": "string", "example": "Cleveland QLD 4163" }
            }
          },
          "home": {
            "type": "object",
            "properties": {
              "bedrooms": { "type": "string", "example": "3" },
              "bathrooms": { "type": "string", "example": "2" },
              "property_type": { "type": "string", "enum": ["house", "townhouse", "unit"] }
            }
          },
          "service_details": {
            "$ref": "#/components/schemas/ServiceDetailsMap"
          },
          "service_notes": {
            "$ref": "#/components/schemas/ServiceNotesMap"
          },
          "notes": {
            "type": "string",
            "description": "General customer move notes."
          }
        }
      },
      "QuoteSuccessResponse": {
        "type": "object",
        "required": ["ok", "lead_id", "status", "verification_required", "customer_email", "message", "guest_dashboard_url"],
        "properties": {
          "ok": { "type": "boolean", "example": true },
          "lead_id": { "type": "string", "example": "e4b2d180-87a3-4a11-8f3a-93f4e1f7c320" },
          "status": { "type": "string", "example": "pending_verification" },
          "verification_required": { "type": "boolean", "example": true },
          "customer_email": { "type": "string", "example": "jane@example.com" },
          "message": { "type": "string" },
          "guest_dashboard_url": { "type": "string", "example": "https://nestbuddy.com.au/quote/e4b2d180-87a3-4a11-8f3a-93f4e1f7c320/dashboard" },
          "summary": { "$ref": "#/components/schemas/QuoteSummary" },
          "suggested_next_steps": {
            "type": "array",
            "items": { "type": "string" }
          },
          "prompt_options": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      },
      "QuoteDiagnosticResponse": {
        "type": "object",
        "required": ["ok", "valid", "sandbox_mode", "message", "summary"],
        "properties": {
          "ok": { "type": "boolean", "example": true },
          "valid": { "type": "boolean", "example": true },
          "sandbox_mode": { "type": "boolean", "example": true },
          "message": { "type": "string", "example": "Payload is valid and ready for submission." },
          "about_nestbuddy": { "type": "string" },
          "summary": { "$ref": "#/components/schemas/QuoteSummary" }
        }
      },
      "QuoteValidationErrorResponse": {
        "type": "object",
        "required": ["ok", "valid", "error", "errors", "prompts"],
        "properties": {
          "ok": { "type": "boolean", "example": false },
          "valid": { "type": "boolean", "example": false },
          "error": { "type": "string", "example": "Validation failed. Please provide the missing trade or location details." },
          "errors": {
            "type": "array",
            "items": { "type": "string" }
          },
          "prompts": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ClarificationPromptItem" },
            "description": "Diagnostic prompts asking for missing fields."
          },
          "about_nestbuddy": { "type": "string" }
        }
      },
      "MarketingVendor": {
        "type": "object",
        "required": ["slug", "name", "service", "description", "image"],
        "properties": {
          "slug": { "type": "string", "example": "bayside-coastal-removals" },
          "name": { "type": "string", "example": "Bayside Coastal Removals" },
          "service": { "type": "string", "example": "Removalist" },
          "description": { "type": "string", "example": "Experienced local removalists specialising in Brisbane Bayside relocations." },
          "image": { "type": "string", "example": "/images/vendors/bayside-removals-logo.webp" },
          "featured": { "type": "boolean", "example": true },
          "url": { "type": "string", "example": "https://example.com.au" },
          "content": { "type": "string" }
        }
      },
      "BlogPost": {
        "type": "object",
        "required": ["slug", "frontmatter"],
        "properties": {
          "slug": { "type": "string", "example": "What-is-a-72-Hour-Re-Clean-Guarantee-for-QLD-End-of-Lease-Cleans" },
          "frontmatter": {
            "type": "object",
            "required": ["title", "date", "description"],
            "properties": {
              "title": { "type": "string", "example": "What is a 72-Hour Re-Clean Guarantee for QLD End-of-Lease Cleans?" },
              "date": { "type": "string", "example": "2026-07-15" },
              "description": { "type": "string", "example": "Understand how a 72-hour re-clean guarantee protects your rental bond under Queensland tenancy laws." },
              "category": { "type": "string", "example": "Cleaning" },
              "readTime": { "type": "string", "example": "5 min read" }
            }
          }
        }
      },
      "Rfc7807ProblemDetails": {
        "type": "object",
        "required": ["type", "title", "status", "code", "detail"],
        "properties": {
          "type": { "type": "string", "example": "https://nestbuddy.com.au/docs/errors#rate_limit_exceeded" },
          "title": { "type": "string", "example": "Rate Limit Exceeded" },
          "status": { "type": "integer", "example": 429 },
          "code": { "type": "string", "example": "RATE_LIMIT_EXCEEDED" },
          "detail": { "type": "string", "example": "Too many requests from IP 127.0.0.1. Please retry after 600 seconds." },
          "hint": { "type": "string", "example": "NestBuddy allows up to 20 requests per 10-minute window. Self-throttle using RateLimit-* headers." },
          "timestamp": { "type": "string", "format": "date-time", "example": "2026-08-23T10:00:00Z" }
        }
      }
    }
  }
}
