{
  "openapi": "3.1.0",
  "info": {
    "title": "DirectoryDuck Public API",
    "version": "1.0.0",
    "summary": "Read a directory's published listings and approved reviews, and submit reviews and inquiries.",
    "description": "The public v1 API of DirectoryDuck (https://directoryduck.com). Each directory has its own API key: in the dashboard, open Directories, pick the directory, then Settings → API Key. Every request acts on the directory that owns the key.\n\n**Authentication.** Send `Authorization: Bearer <api_key>`. GET requests may pass the key as the `key` query parameter instead; POST requests always need the header. When the header is present, the query parameter is ignored. A missing, malformed or unknown key returns 401.\n\n**Rate limits.** Per directory key and per endpoint: 100 GET and 50 POST requests per minute (fixed one-minute windows), the same on every plan. Rate-limited responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; over the limit the answer is 429 with `Retry-After`. `GET /api/v1/public/listings/{slug}` is not rate-limited.\n\n**Caching.** A GET authenticated with the `key` query parameter (no Authorization or Cookie header) is answered with `Cache-Control: public, s-maxage=60, stale-while-revalidate=300` and cached by the CDN; a cached answer does not count against the rate limit. Everything else is `private, no-store`.\n\n**Envelope.** Success: `{ \"data\": ... }`, and lists add `\"meta\": { \"total\", \"page\", \"pageSize\" }`. Error: `{ \"error\": { \"code\", \"message\" } }`.\n\n**CORS.** Every route answers cross-origin calls (`Access-Control-Allow-Origin: *`) and the OPTIONS preflight (204), so the widgets and the full-directory embed can call it from any site.\n\n**Visibility.** Calls without a signed-in DirectoryDuck session (every cross-site call) see published (`active`) listings and approved reviews only.",
    "contact": {
      "name": "DirectoryDuck support",
      "email": "support@directoryduck.com",
      "url": "https://directoryduck.com/contact"
    }
  },
  "externalDocs": {
    "description": "API reference, widgets and the full-directory embed",
    "url": "https://directoryduck.com/docs/api"
  },
  "servers": [
    {
      "url": "https://directoryduck.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Directory",
      "description": "The directory that owns the API key."
    },
    {
      "name": "Listings",
      "description": "Published listings."
    },
    {
      "name": "Reviews",
      "description": "Approved reviews, and new reviews that wait for moderation."
    },
    {
      "name": "Inquiries",
      "description": "Messages to the directory's owner."
    },
    {
      "name": "Widget events",
      "description": "Views and clicks recorded by the embeddable widgets."
    }
  ],
  "paths": {
    "/api/v1/public/directory": {
      "get": {
        "operationId": "getDirectory",
        "tags": [
          "Directory"
        ],
        "summary": "Get the directory that owns the API key",
        "description": "The directory's public fields, plus `embed`: what the full-directory embed may show for it.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyQuery": []
          }
        ],
        "responses": {
          "200": {
            "description": "The directory.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Directory"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/public/listings": {
      "get": {
        "operationId": "listListings",
        "tags": [
          "Listings"
        ],
        "summary": "List published listings",
        "description": "The directory's listings with pagination: featured listings first (`featured: true`), then the rest; each group sorted by name (A to Z). A listing is featured while the date its owner set has not passed and the owner's plan includes featured listings (Starter and up); after that it goes back to its place by name, with no change on your side. Stop paging at `meta.total`: a page past the end can return 500 `DATABASE_ERROR` rather than an empty list.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyQuery": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Listings per page. Values above 100 are treated as 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 20
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Case-insensitive match anywhere in the name or description. The characters `\"` `\\` `%` `*` and control characters are removed, and the term is cut to 100 characters; `_` matches any one character.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "description": "Only listings tagged with this category (exact match).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Listing status filter; `all` removes it. Calls without a signed-in session see published listings only, so any value other than `active` or `all` returns an empty list.",
            "schema": {
              "type": "string",
              "default": "active"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of listings.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Listing"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The listings could not be read.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "DATABASE_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/listings/{slug}": {
      "get": {
        "operationId": "getListing",
        "tags": [
          "Listings"
        ],
        "summary": "Get one listing by slug",
        "description": "One published listing in the directory, with its approved-review count and average rating. This route is not rate-limited and sends no X-RateLimit headers.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyQuery": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The listing's slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The listing.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ListingDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No published listing with this slug in the directory.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "NOT_FOUND"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/reviews": {
      "get": {
        "operationId": "listReviews",
        "tags": [
          "Reviews"
        ],
        "summary": "List a listing's approved reviews",
        "description": "Approved reviews for one listing, newest first. Pass `listing` (slug) or `listingId`; if both are given, `listingId` is used. Stop paging at `meta.total`: a page past the end can return 500 `DATABASE_ERROR` rather than an empty list.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyQuery": []
          }
        ],
        "parameters": [
          {
            "name": "listing",
            "in": "query",
            "description": "The listing's slug, looked up among the directory's published listings.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "listingId",
            "in": "query",
            "description": "The listing's id. A value that is not a UUID returns 500 `DATABASE_ERROR`.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Reviews per page. Values above 50 are treated as 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of reviews.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Review"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Neither `listing` nor `listingId` was given.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "BAD_REQUEST"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No published listing with this slug in the directory.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "NOT_FOUND"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The reviews could not be read.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "DATABASE_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "submitReview",
        "tags": [
          "Reviews"
        ],
        "summary": "Submit a review",
        "description": "A review of one published listing in the directory. It is stored as pending and appears only after the directory's owner approves it in the dashboard.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitReviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The review was stored, pending moderation.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Submission"
                        },
                        {
                          "properties": {
                            "message": {
                              "const": "Review submitted successfully. It will be visible after moderation."
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`BAD_REQUEST`: the body is not JSON. `VALIDATION_ERROR`: a field failed validation; the message names the first problem.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "BAD_REQUEST",
                                "VALIDATION_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No published listing with this slug in the directory.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "NOT_FOUND"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The review could not be stored.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "DATABASE_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/inquiries": {
      "post": {
        "operationId": "submitInquiry",
        "tags": [
          "Inquiries"
        ],
        "summary": "Submit an inquiry",
        "description": "A message to the directory's owner; it lands in the directory's Inquiries tab in the dashboard. `listing` links the inquiry to a listing only when that slug is a published listing in the directory; otherwise the inquiry is still accepted, unlinked.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitInquiryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The inquiry was stored.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Submission"
                        },
                        {
                          "properties": {
                            "message": {
                              "const": "Inquiry submitted successfully. We will be in touch soon."
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`BAD_REQUEST`: the body is not JSON. `VALIDATION_ERROR`: a field failed validation; the message names the first problem.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "BAD_REQUEST",
                                "VALIDATION_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The inquiry could not be stored.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "DATABASE_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/public/widget-events": {
      "post": {
        "operationId": "recordWidgetEvent",
        "tags": [
          "Widget events"
        ],
        "summary": "Record a widget event",
        "description": "Used by DirectoryDuck's embeddable widgets to record views and clicks for the directory's analytics. The request's User-Agent header is stored with the event.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WidgetEventRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The event was recorded. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            }
          },
          "400": {
            "description": "`PARSE_ERROR`: the body could not be read as JSON. `VALIDATION_ERROR`: `widget_type` or `event_type` is missing or not one of the allowed values.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "PARSE_ERROR",
                                "VALIDATION_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The event could not be stored.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control-No-Store"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "properties": {
                        "error": {
                          "properties": {
                            "code": {
                              "enum": [
                                "DATABASE_ERROR"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The directory's API key: `Authorization: Bearer <api_key>`. Required for POST."
      },
      "apiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "The same API key as the `key` query parameter, GET only. Without a Cookie header, these responses are cached by the CDN for about a minute."
      }
    },
    "parameters": {
      "Page": {
        "name": "page",
        "in": "query",
        "description": "Page number, starting at 1.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests allowed per window for this key, endpoint and method.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "When the current window ends, in Unix seconds.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (at least 1).",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Cache-Control": {
        "description": "`public, s-maxage=60, stale-while-revalidate=300` for a GET authenticated with the `key` query parameter and no Cookie header; `private, no-store` otherwise.",
        "schema": {
          "type": "string",
          "enum": [
            "public, s-maxage=60, stale-while-revalidate=300",
            "private, no-store"
          ]
        }
      },
      "Cache-Control-No-Store": {
        "description": "Errors and writes are never cached.",
        "schema": {
          "type": "string",
          "const": "private, no-store"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "The API key is missing, malformed or unknown, or a POST was sent without the Authorization header. No rate-limit headers.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control-No-Store"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "properties": {
                    "error": {
                      "properties": {
                        "code": {
                          "const": "UNAUTHORIZED"
                        }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "Over the rate limit for this key, endpoint and method.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control-No-Store"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "properties": {
                    "error": {
                      "properties": {
                        "code": {
                          "const": "RATE_LIMIT_EXCEEDED"
                        }
                      }
                    }
                  }
                }
              ]
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "UNAUTHORIZED",
                  "RATE_LIMIT_EXCEEDED",
                  "BAD_REQUEST",
                  "VALIDATION_ERROR",
                  "PARSE_ERROR",
                  "NOT_FOUND",
                  "DATABASE_ERROR"
                ]
              },
              "message": {
                "type": "string",
                "description": "A human-readable explanation."
              }
            }
          }
        },
        "example": {
          "error": {
            "code": "UNAUTHORIZED",
            "message": "Invalid API key"
          }
        }
      },
      "PageMeta": {
        "type": "object",
        "required": [
          "total",
          "page",
          "pageSize"
        ],
        "properties": {
          "total": {
            "type": "integer",
            "description": "All matching results, across pages."
          },
          "page": {
            "type": "integer",
            "description": "The page returned."
          },
          "pageSize": {
            "type": "integer",
            "description": "The page size used (after the cap)."
          }
        }
      },
      "Directory": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "description",
          "embed"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string",
            "description": "The hosted page is https://directoryduck.com/{slug}."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "embed": {
            "type": "object",
            "description": "What the full-directory embed may show. `fullEmbed` is true on every plan today; `branding` is false only for a Pro directory whose owner hid \"Powered by DirectoryDuck\".",
            "required": [
              "fullEmbed",
              "branding"
            ],
            "properties": {
              "fullEmbed": {
                "type": "boolean",
                "description": "The whole directory (search, grid, listing pages, inquiry form)."
              },
              "branding": {
                "type": "boolean",
                "description": "Show the \"Powered by DirectoryDuck\" footer."
              }
            }
          }
        }
      },
      "Listing": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "description",
          "email",
          "websiteUrl",
          "logoUrl",
          "isVerified",
          "categories",
          "location",
          "reviewCount",
          "averageRating",
          "createdAt",
          "featured"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "logoUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "isVerified": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "location": {
            "$ref": "#/components/schemas/ListingLocation"
          },
          "reviewCount": {
            "type": "integer",
            "description": "From the listing's review stats; 0 when it has none."
          },
          "averageRating": {
            "type": [
              "number",
              "null"
            ],
            "description": "From the listing's review stats; null when it has none."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "featured": {
            "type": "boolean",
            "description": "Featured by the directory owner right now (Starter and up). Featured listings come first in the list."
          }
        }
      },
      "ListingDetail": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "description",
          "email",
          "websiteUrl",
          "logoUrl",
          "isVerified",
          "verifiedAt",
          "categories",
          "location",
          "createdAt",
          "updatedAt",
          "featured",
          "stats"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "websiteUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "logoUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "isVerified": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "verifiedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "location": {
            "$ref": "#/components/schemas/ListingLocation"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "featured": {
            "type": "boolean",
            "description": "Featured by the directory owner right now (Starter and up)."
          },
          "stats": {
            "type": "object",
            "required": [
              "reviewCount",
              "averageRating"
            ],
            "properties": {
              "reviewCount": {
                "type": "integer",
                "description": "Approved reviews."
              },
              "averageRating": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Average of approved ratings, rounded to one decimal; null with no reviews."
              }
            }
          }
        }
      },
      "ListingLocation": {
        "type": "object",
        "description": "The listing's address, as its owner entered it or AI discovery read it from the business's website. Every key is always present; a part that is not set is null, and a listing with no address has all five null.",
        "required": [
          "street_address",
          "city",
          "state",
          "postal_code",
          "country"
        ],
        "properties": {
          "street_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "The street line: number, street and any suite or unit, without the city, state or postal code."
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "State, province or region."
          },
          "postal_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "ZIP or postal code."
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "example": {
          "street_address": "1234 S Lamar Blvd, Suite 100",
          "city": "Austin",
          "state": "TX",
          "postal_code": "78704",
          "country": "US"
        }
      },
      "Review": {
        "type": "object",
        "required": [
          "id",
          "reviewerName",
          "reviewerCompany",
          "reviewerTitle",
          "rating",
          "title",
          "body",
          "pros",
          "cons",
          "isVerified",
          "createdAt",
          "response"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reviewerName": {
            "type": "string"
          },
          "reviewerCompany": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewerTitle": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": "string"
          },
          "pros": {
            "type": [
              "string",
              "null"
            ]
          },
          "cons": {
            "type": [
              "string",
              "null"
            ]
          },
          "isVerified": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "The reviewer is verified."
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "response": {
            "description": "The directory owner's public response, or null.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "body",
                  "respondedAt"
                ],
                "properties": {
                  "body": {
                    "type": "string"
                  },
                  "respondedAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Submission": {
        "type": "object",
        "required": [
          "id",
          "message",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "message": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SubmitReviewRequest": {
        "type": "object",
        "description": "Fields not listed here are ignored.",
        "required": [
          "listing",
          "reviewerName",
          "reviewerEmail",
          "rating",
          "title",
          "body"
        ],
        "properties": {
          "listing": {
            "type": "string",
            "minLength": 1,
            "description": "The listing's slug. It must be a published listing."
          },
          "reviewerName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "reviewerEmail": {
            "type": "string",
            "format": "email",
            "description": "Stored for moderation; never returned by the API."
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "body": {
            "type": "string",
            "minLength": 10,
            "maxLength": 5000
          },
          "pros": {
            "type": "string",
            "maxLength": 1000
          },
          "cons": {
            "type": "string",
            "maxLength": 1000
          }
        },
        "example": {
          "listing": "joes-pizza",
          "reviewerName": "John D.",
          "reviewerEmail": "john@example.com",
          "rating": 5,
          "title": "Best pizza in town",
          "body": "Thin crust, fast service, friendly staff.",
          "pros": "Fast service",
          "cons": "Small dining room"
        }
      },
      "SubmitInquiryRequest": {
        "type": "object",
        "description": "Leading and trailing spaces are trimmed before the length checks. Fields not listed here are ignored.",
        "required": [
          "name",
          "email",
          "message"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "message": {
            "type": "string",
            "minLength": 10,
            "maxLength": 5000
          },
          "listing": {
            "type": "string",
            "maxLength": 200,
            "description": "A listing slug. Links the inquiry to that listing if it is published in the directory."
          },
          "company": {
            "type": "string",
            "maxLength": 100
          },
          "phone": {
            "type": "string",
            "maxLength": 50
          },
          "subject": {
            "type": "string",
            "maxLength": 200
          },
          "source": {
            "type": "string",
            "maxLength": 100,
            "description": "Where the inquiry came from (default `widget`)."
          }
        },
        "example": {
          "name": "Jane Smith",
          "email": "jane@example.com",
          "message": "I'd like to book a table for six on Friday.",
          "listing": "joes-pizza",
          "company": "Acme Co",
          "phone": "+1 555 0100",
          "subject": "Group booking"
        }
      },
      "WidgetEventRequest": {
        "type": "object",
        "required": [
          "widget_type",
          "event_type"
        ],
        "properties": {
          "widget_type": {
            "type": "string",
            "enum": [
              "listings",
              "reviews",
              "badge",
              "rating",
              "inquiry",
              "form"
            ]
          },
          "event_type": {
            "type": "string",
            "enum": [
              "impression",
              "click",
              "search",
              "load_more",
              "submit"
            ]
          },
          "listing_id": {
            "type": "string",
            "format": "uuid",
            "description": "The listing the event is about, if any."
          },
          "referrer_url": {
            "type": "string",
            "description": "The page the widget is on."
          },
          "metadata": {
            "type": "object",
            "description": "Free-form event details (default `{}`)."
          }
        },
        "example": {
          "widget_type": "listings",
          "event_type": "impression"
        }
      }
    }
  }
}