{
  "openapi": "3.1.0",
  "info": {
    "title": "Junk B Gone agent API",
    "version": "1",
    "description": "Check whether Junk B Gone serves a ZIP code and send a pickup request for a customer. Junk B Gone will contact the customer by text, email or phone within an hour during business hours (7 a.m. to 8 p.m., every day) to confirm a time and price. Office phone: (206) 722-4285."
  },
  "servers": [
    {
      "url": "https://mrjunkbgoneseattle.com"
    }
  ],
  "paths": {
    "/api/agent/service-area": {
      "get": {
        "operationId": "checkServiceArea",
        "summary": "Is a ZIP code in the service area?",
        "parameters": [
          {
            "name": "zip",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{5}$"
            },
            "description": "5-digit ZIP code."
          }
        ],
        "responses": {
          "200": {
            "description": "served is true, false, or null when the ZIP code is not known. When it is null, call the office to ask.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "zip",
                    "served",
                    "city",
                    "page_url",
                    "phone",
                    "note"
                  ],
                  "properties": {
                    "zip": {
                      "type": "string"
                    },
                    "served": {
                      "type": [
                        "boolean",
                        "null"
                      ]
                    },
                    "city": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "page_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "phone": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "zip": "98004",
                  "served": true,
                  "city": "Bellevue",
                  "page_url": "https://mrjunkbgoneseattle.com/bellevue-junk-removal/",
                  "phone": "(206) 722-4285",
                  "note": "Bellevue is in our service area."
                }
              }
            }
          },
          "400": {
            "description": "The ZIP code is missing or not 5 digits.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "const": "BAD_ZIP"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "405": {
            "description": "Only GET is allowed here. The Allow header says so.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "const": "METHOD_NOT_ALLOWED"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/request": {
      "post": {
        "operationId": "requestPickup",
        "summary": "Send a pickup request for a customer",
        "description": "Send this only after the customer has asked you to request a pickup. First and last name, email, mobile number, street, ZIP code and either a pickup date or a timing (today, tomorrow, this week, flexible) are required, as on the website forms. Send what you have: the office fills gaps when it contacts the customer. This does not book a time, and it takes no payment. Junk B Gone will contact the customer by text, email or phone within an hour during business hours (7 a.m. to 8 p.m., every day) to confirm a time and price.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "mobile",
                  "street",
                  "zip",
                  "customer_confirmed"
                ],
                "allOf": [
                  {
                    "anyOf": [
                      {
                        "required": [
                          "preferred_date"
                        ]
                      },
                      {
                        "required": [
                          "preferred_timing"
                        ]
                      }
                    ]
                  },
                  {
                    "anyOf": [
                      {
                        "required": [
                          "first_name",
                          "last_name"
                        ]
                      },
                      {
                        "required": [
                          "name"
                        ]
                      }
                    ]
                  },
                  {
                    "anyOf": [
                      {
                        "required": [
                          "description"
                        ]
                      },
                      {
                        "required": [
                          "photo_urls"
                        ]
                      }
                    ]
                  }
                ],
                "properties": {
                  "first_name": {
                    "type": "string",
                    "description": "The customer's first name. Letters, spaces, hyphens, apostrophes and periods only.",
                    "maxLength": 40
                  },
                  "last_name": {
                    "type": "string",
                    "description": "The customer's last name. Same rules as first_name.",
                    "maxLength": 40
                  },
                  "name": {
                    "type": "string",
                    "description": "Alternative to first_name and last_name: the full name. It is split on the last space; one word is a missing last_name.",
                    "maxLength": 81
                  },
                  "email": {
                    "type": "string",
                    "description": "The customer's email address.",
                    "format": "email",
                    "maxLength": 200
                  },
                  "mobile": {
                    "type": "string",
                    "description": "The customer's mobile number, 10 digits. The office may text or call it.",
                    "maxLength": 30
                  },
                  "street": {
                    "type": "string",
                    "description": "Street address where the items are. It must start with a house number. A full address (\"123 Main St, Seattle, WA 98101, Apt 4B\") is accepted: the city, state and ZIP are dropped and a trailing unit moves to unit. No links or @.",
                    "maxLength": 200
                  },
                  "unit": {
                    "type": "string",
                    "description": "Apartment or unit number: letters, digits, space, # and hyphen.",
                    "maxLength": 10
                  },
                  "city": {
                    "type": "string",
                    "description": "Ignored when the ZIP code is known (the city comes from the ZIP). For an unknown ZIP it must look like a city name.",
                    "maxLength": 100
                  },
                  "zip": {
                    "type": "string",
                    "description": "ZIP code of the pickup address, 5 digits or ZIP+4 (stored as 5). May be left out when the street ends with \"WA 98101\"; if both are given they must match.",
                    "pattern": "^\\d{5}(-\\d{4})?$"
                  },
                  "preferred_date": {
                    "type": "string",
                    "description": "Pickup date from today (Pacific time) to 60 days out, like 2026-11-07, 11/7/2026, 11/7 or Nov 7 (a date with no year means its next occurrence; a leading weekday such as \"Sat\" is ignored). Required unless preferred_timing is given."
                  },
                  "preferred_timing": {
                    "type": "string",
                    "enum": [
                      "today",
                      "tomorrow",
                      "this-week",
                      "flexible"
                    ],
                    "description": "Use when the customer has no date. Required unless preferred_date is given; the date wins when both are sent."
                  },
                  "preferred_window": {
                    "type": "string",
                    "enum": [
                      "morning",
                      "afternoon",
                      "anytime"
                    ],
                    "description": "Optional time window on that date (default anytime). Free text is not accepted; the server writes the text the office sees."
                  },
                  "description": {
                    "type": "string",
                    "description": "What needs to go. Required unless photo_urls is given. It goes to the office note only.",
                    "maxLength": 3000
                  },
                  "photo_urls": {
                    "type": "array",
                    "maxItems": 5,
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "pattern": "^https://",
                      "maxLength": 500
                    },
                    "description": "Up to 5 https links to photos. Stored as text for the office; the server never opens them."
                  },
                  "estimated_volume": {
                    "type": "string",
                    "enum": [
                      "min",
                      "1-8",
                      "1-4",
                      "1-2",
                      "3-4",
                      "full",
                      "unknown"
                    ],
                    "description": "Your rough size estimate. The response then includes the price range for that size."
                  },
                  "agent_name": {
                    "type": "string",
                    "description": "Name of the assistant sending the request.",
                    "maxLength": 60
                  },
                  "is_business": {
                    "type": "boolean",
                    "description": "True when the pickup is for a business."
                  },
                  "company": {
                    "type": "string",
                    "description": "Company name, for a business pickup.",
                    "maxLength": 200
                  },
                  "customer_confirmed": {
                    "type": "boolean",
                    "description": "Must be true. It states that the customer asked you to request this pickup."
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "mobile",
                  "street",
                  "zip",
                  "customer_confirmed"
                ],
                "allOf": [
                  {
                    "anyOf": [
                      {
                        "required": [
                          "preferred_date"
                        ]
                      },
                      {
                        "required": [
                          "preferred_timing"
                        ]
                      }
                    ]
                  },
                  {
                    "anyOf": [
                      {
                        "required": [
                          "first_name",
                          "last_name"
                        ]
                      },
                      {
                        "required": [
                          "name"
                        ]
                      }
                    ]
                  },
                  {
                    "anyOf": [
                      {
                        "required": [
                          "description"
                        ]
                      },
                      {
                        "required": [
                          "photo_urls"
                        ]
                      }
                    ]
                  }
                ],
                "properties": {
                  "first_name": {
                    "type": "string",
                    "description": "The customer's first name. Letters, spaces, hyphens, apostrophes and periods only.",
                    "maxLength": 40
                  },
                  "last_name": {
                    "type": "string",
                    "description": "The customer's last name. Same rules as first_name.",
                    "maxLength": 40
                  },
                  "name": {
                    "type": "string",
                    "description": "Alternative to first_name and last_name: the full name. It is split on the last space; one word is a missing last_name.",
                    "maxLength": 81
                  },
                  "email": {
                    "type": "string",
                    "description": "The customer's email address.",
                    "format": "email",
                    "maxLength": 200
                  },
                  "mobile": {
                    "type": "string",
                    "description": "The customer's mobile number, 10 digits. The office may text or call it.",
                    "maxLength": 30
                  },
                  "street": {
                    "type": "string",
                    "description": "Street address where the items are. It must start with a house number. A full address (\"123 Main St, Seattle, WA 98101, Apt 4B\") is accepted: the city, state and ZIP are dropped and a trailing unit moves to unit. No links or @.",
                    "maxLength": 200
                  },
                  "unit": {
                    "type": "string",
                    "description": "Apartment or unit number: letters, digits, space, # and hyphen.",
                    "maxLength": 10
                  },
                  "city": {
                    "type": "string",
                    "description": "Ignored when the ZIP code is known (the city comes from the ZIP). For an unknown ZIP it must look like a city name.",
                    "maxLength": 100
                  },
                  "zip": {
                    "type": "string",
                    "description": "ZIP code of the pickup address, 5 digits or ZIP+4 (stored as 5). May be left out when the street ends with \"WA 98101\"; if both are given they must match.",
                    "pattern": "^\\d{5}(-\\d{4})?$"
                  },
                  "preferred_date": {
                    "type": "string",
                    "description": "Pickup date from today (Pacific time) to 60 days out, like 2026-11-07, 11/7/2026, 11/7 or Nov 7 (a date with no year means its next occurrence; a leading weekday such as \"Sat\" is ignored). Required unless preferred_timing is given."
                  },
                  "preferred_timing": {
                    "type": "string",
                    "enum": [
                      "today",
                      "tomorrow",
                      "this-week",
                      "flexible"
                    ],
                    "description": "Use when the customer has no date. Required unless preferred_date is given; the date wins when both are sent."
                  },
                  "preferred_window": {
                    "type": "string",
                    "enum": [
                      "morning",
                      "afternoon",
                      "anytime"
                    ],
                    "description": "Optional time window on that date (default anytime). Free text is not accepted; the server writes the text the office sees."
                  },
                  "description": {
                    "type": "string",
                    "description": "What needs to go. Required unless photo_urls is given. It goes to the office note only.",
                    "maxLength": 3000
                  },
                  "photo_urls": {
                    "type": "array",
                    "maxItems": 5,
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "pattern": "^https://",
                      "maxLength": 500
                    },
                    "description": "Up to 5 https links to photos. Stored as text for the office; the server never opens them."
                  },
                  "estimated_volume": {
                    "type": "string",
                    "enum": [
                      "min",
                      "1-8",
                      "1-4",
                      "1-2",
                      "3-4",
                      "full",
                      "unknown"
                    ],
                    "description": "Your rough size estimate. The response then includes the price range for that size."
                  },
                  "agent_name": {
                    "type": "string",
                    "description": "Name of the assistant sending the request.",
                    "maxLength": 60
                  },
                  "is_business": {
                    "type": "boolean",
                    "description": "True when the pickup is for a business."
                  },
                  "company": {
                    "type": "string",
                    "description": "Company name, for a business pickup.",
                    "maxLength": 200
                  },
                  "customer_confirmed": {
                    "type": "boolean",
                    "description": "Must be true. It states that the customer asked you to request this pickup."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request was received.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "request_id",
                    "status",
                    "estimate",
                    "next_step",
                    "phone",
                    "minimum"
                  ],
                  "properties": {
                    "request_id": {
                      "type": "string"
                    },
                    "status": {
                      "const": "received"
                    },
                    "estimate": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The price range and standard wording, when estimated_volume was given."
                    },
                    "next_step": {
                      "type": "string"
                    },
                    "phone": {
                      "type": "string"
                    },
                    "minimum": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "One or more fields are missing or invalid. fields maps each field name to a code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "fields"
                  ],
                  "properties": {
                    "error": {
                      "const": "VALIDATION"
                    },
                    "fields": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string",
                        "enum": [
                          "REQUIRED",
                          "BAD_EMAIL",
                          "BAD_PHONE",
                          "BAD_ZIP",
                          "TOO_LONG",
                          "BAD_URL",
                          "BAD_TEXT",
                          "NOT_CONFIRMED",
                          "BAD_VALUE"
                        ]
                      }
                    }
                  }
                },
                "example": {
                  "error": "VALIDATION",
                  "fields": {
                    "mobile": "BAD_PHONE"
                  }
                }
              }
            }
          },
          "405": {
            "description": "Only POST is allowed here. The Allow header says so.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "const": "METHOD_NOT_ALLOWED"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The body is over 32 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "const": "PAYLOAD_TOO_LARGE"
                    }
                  }
                },
                "example": {
                  "error": "PAYLOAD_TOO_LARGE"
                }
              }
            }
          },
          "415": {
            "description": "Send JSON (application/json) or a form (application/x-www-form-urlencoded).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "const": "UNSUPPORTED_MEDIA"
                    }
                  }
                },
                "example": {
                  "error": "UNSUPPORTED_MEDIA"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this address, phone number or email.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "const": "RATE_LIMITED"
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The request could not be stored. Ask the customer to call the office.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "phone"
                  ],
                  "properties": {
                    "error": {
                      "const": "TEMPORARILY_UNAVAILABLE"
                    },
                    "phone": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
