API Reference
Read your directory's listings and reviews from any site, and send in reviews and inquiries, with the REST API. All endpoints return JSON.
Machine-readable spec: /openapi.json (OpenAPI 3.1), for API clients, code generators and AI agents.
Authentication
All API requests require an API key. Each directory has its own key: in the dashboard, open Directories, pick your directory, then go to Settings → API Key. You can copy or regenerate it there.
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://directoryduck.com/api/v1/public/listingsInclude your API key in the Authorization header with every request. A missing or wrong key returns 401 with { "error": { "code": "UNAUTHORIZED", "message": "…" } }.
GET requests may pass the key as a key query parameter instead, for example /api/v1/public/listings?key=YOUR_API_KEY. Those responses are cached by our CDN (about a minute, then refreshed in the background), which is what the full-directory embed uses. POST requests always need the Authorization header.
Base URL
https://directoryduck.comAll endpoint paths below are relative to this base URL, for example https://directoryduck.com/api/v1/public/listings.
Endpoints
/api/v1/public/directoryGet the directory that owns your API key: its name, slug, and description, and what the full-directory embed shows (branding is false only for a Pro directory that hid "Powered by DirectoryDuck").
Response
{
"data": {
"id": "8f2c…",
"name": "Chicago Restaurants",
"slug": "chicago-restaurants",
"description": "Best restaurants in Chicago",
"embed": { "fullEmbed": true, "branding": true }
}
}/api/v1/public/listingsGet the published listings in your directory with pagination: featured listings first (featured: true, Starter and up, until the date you set), then the rest, each sorted by name.
Query Parameters
pagepage number, default 1pageSizeresults per page, default 20, max 100searchmatches name or descriptioncategorylistings tagged with this category
Response
{
"data": [
{
"id": "3b9a…",
"name": "Joe's Pizza",
"slug": "joes-pizza",
"description": "…",
"email": "hello@joespizza.example",
"websiteUrl": "https://joespizza.example",
"logoUrl": null,
"isVerified": true,
"categories": ["pizza"],
"location": {
"street_address": "123 Main St",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"country": "US"
},
"reviewCount": 42,
"averageRating": 4.8,
"createdAt": "2026-10-01T12:00:00Z",
"featured": true
}
],
"meta": { "total": 150, "page": 1, "pageSize": 20 }
}/api/v1/public/listings/:slugGet a single listing by its slug, with its review count and average rating.
Response
{
"data": {
"id": "3b9a…",
"name": "Joe's Pizza",
"slug": "joes-pizza",
"description": "…",
"email": "hello@joespizza.example",
"websiteUrl": "https://joespizza.example",
"logoUrl": null,
"isVerified": true,
"verifiedAt": "2026-10-02T09:30:00Z",
"categories": ["pizza"],
"location": {
"street_address": "123 Main St",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"country": "US"
},
"createdAt": "2026-10-01T12:00:00Z",
"updatedAt": "2026-10-05T08:00:00Z",
"featured": true,
"stats": { "reviewCount": 42, "averageRating": 4.8 }
}
}/api/v1/public/reviewsGet the approved reviews for one listing, newest first. Pass either listing or listingId.
Query Parameters
listinglisting sluglistingIdlisting idpagepage number, default 1limitresults per page, default 10, max 50
Response
{
"data": [
{
"id": "c41d…",
"reviewerName": "John D.",
"reviewerCompany": null,
"reviewerTitle": null,
"rating": 5,
"title": "Best pizza in town",
"body": "Thin crust, fast service, friendly staff.",
"pros": null,
"cons": null,
"isVerified": false,
"createdAt": "2026-10-03T18:20:00Z",
"response": null
}
],
"meta": { "total": 42, "page": 1, "pageSize": 10 }
}/api/v1/public/reviewsSubmit a review for a published listing. It stays pending until you approve it in the dashboard. pros and cons are optional.
Request Body
{
"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"
}Response
{
"data": {
"id": "c41d…",
"message": "Review submitted successfully. It will be visible after moderation.",
"createdAt": "2026-10-11T15:00:00Z"
}
}/api/v1/public/inquiriesSubmit an inquiry to your directory, optionally about one listing. It lands in the Inquiries tab of your directory. listing, company, phone, and subject are optional; listing links the inquiry only if that listing is published (status Active), otherwise the inquiry arrives unlinked.
Request Body
{
"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"
}Response
{
"data": {
"id": "9e7f…",
"message": "Inquiry submitted successfully. We will be in touch soon.",
"createdAt": "2026-10-11T15:00:00Z"
}
}Rate Limits
GET requests
100
per minute, per directory API key
POST requests
50
per minute, per directory API key
These limits are the same on every plan today. GET requests authenticated with the key query parameter are cached by our CDN for about a minute, and a cached answer does not count against the limit.
Rate-limited responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Over the limit, you get a 429 with a Retry-After header.
Code Examples
JavaScript / TypeScript
const response = await fetch(
'https://directoryduck.com/api/v1/public/listings?page=1&pageSize=20',
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
},
}
);
const { data: listings, meta } = await response.json();
console.log(listings, meta.total);cURL
curl -X GET \
'https://directoryduck.com/api/v1/public/listings?page=1&pageSize=20' \
-H 'Authorization: Bearer YOUR_API_KEY'