Skip to content
Contents

Listings

Listings are the busiest resource. Seven endpoints — four that read or write, three that move a listing between the states it can be in.

The listing object

One listing, with its category, its tags and your directory's own custom fields folded in.

Fields

Field Type Description
id string The listing's uuid.
public_id string The stable short id the listing is addressed by, here and on the public site.
slug string The listing's URL segment. It changes when the name does, and the old one keeps redirecting.
name string The business's name.
description string The description shown on the listing's public page.
website_url string The address we check for a linkback.
status string draft, published or archived.
verification_status string What the last linkback check found.
primary_category object The listing's category, with its id, name and full path.
tags array The listing's tags, as slugs.
properties object Every custom field your directory defines, with a null where a listing has no value, so the shape does not change from row to row.
created_at string When the listing was created.
updated_at string When the listing last changed, to the microsecond. This is the value you echo back as expected_updated_at.
content_updated_at string When the listing's published content last changed. This is what a client syncing incrementally wants — an enforcement pass does not move it.

Every custom field your directory defines appears on every listing, with a null where it has no value, so the shape does not change from row to row. A value we cannot read comes back as null: a read reports what is stored, never what a write would have made of it.

Response

{ "data": { "id": "018f7c2a-0000-7000-8000-0000000000c2", "public_id": "9834hd7", "slug": "my-cool-cafe", "name": "My Cool Cafe", "description": "Third-wave coffee in Kreuzberg.", "website_url": "https://mycoolcafe.example", "status": "published", "verification_status": "verified", "primary_category": { "id": "018f7c2a-0000-7000-8000-0000000000a1", "name": "Cafes", "path": "food/cafes" }, "tags": [ "wifi", "outdoor-seating" ], "properties": { "opening_hours": "9-5" }, "created_at": "2026-08-01T09:14:22Z", "updated_at": "2026-08-27T11:02:05.418291Z", "content_updated_at": "2026-08-27T11:02:05Z" } }

List listings

GET /api/v1/directories/DIRECTORY_ID/listings

listings:read The capability a key must carry to call this.

List every listing in the directory, in name order, a page at a time.

Query parameters

Parameter Type Description
page integer Which page to return. Counting starts at 1. A page past the end returns no rows rather than an error, and the envelope still says how many pages there are.
per_page integer How many rows a page holds. The default is 25 and the maximum is 100; anything larger is capped rather than refused.

curl

curl -X GET \ -H "Authorization: Bearer YOUR_KEY" \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings?page=1&per_page=25"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings?page=1&per_page=25") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer YOUR_KEY" response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)

Response

{ "data": [ { "id": "018f7c2a-0000-7000-8000-0000000000c2", "public_id": "9834hd7", "slug": "my-cool-cafe", "name": "My Cool Cafe", "description": "Third-wave coffee in Kreuzberg.", "website_url": "https://mycoolcafe.example", "status": "published", "verification_status": "verified", "primary_category": { "id": "018f7c2a-0000-7000-8000-0000000000a1", "name": "Cafes", "path": "food/cafes" }, "tags": [ "wifi", "outdoor-seating" ], "properties": { "opening_hours": "9-5" }, "created_at": "2026-08-01T09:14:22Z", "updated_at": "2026-08-27T11:02:05.418291Z", "content_updated_at": "2026-08-27T11:02:05Z" } ], "meta": { "page": 1, "per_page": 25, "count": 412, "pages": 17 } }

Read a listing

GET /api/v1/directories/DIRECTORY_ID/listings/LISTING_ID

listings:read The capability a key must carry to call this.

Read one listing by its public id. The slug-and-id spelling the public site uses resolves here too.

A listing's identity here is its public_id. The slug-and-id spelling the public site uses resolves too, so a URL copied from the site works without editing.

curl

curl -X GET \ -H "Authorization: Bearer YOUR_KEY" \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer YOUR_KEY" response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)

Create a listing

POST /api/v1/directories/DIRECTORY_ID/listings

listings:write The capability a key must carry to call this.

Create a listing. It starts as a draft — publishing is a separate call, so nothing goes live by accident.

Body parameters

Parameter Type Description
name required string The business's name. Required when you create a listing.
description string The description shown on the listing's public page.
website_url string The address we check for a linkback back to you.
logo_url string A logo to mirror. We fetch and store it; there is no file upload on this API.
primary_category_id string The category the listing belongs to. Required before a listing can be published.
properties object Your directory's custom fields, as a JSON object keyed by field key.
tags array The tags to apply, as an array of names. Sending this replaces the listing's tags.

A new listing is a draft. Publishing is a separate call, so nothing reaches the public site because a script sent one field too many.

There is no file upload on this API. Send logo_url and we fetch the image and store our own copy.

curl

curl -X POST \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Cool Cafe", "website_url": "https://mycoolcafe.example", "description": "Third-wave coffee in Kreuzberg.", "primary_category_id": "018f7c2a-0000-7000-8000-0000000000a1", "tags": [ "wifi", "outdoor-seating" ] }' \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer YOUR_KEY" request["Content-Type"] = "application/json" request.body = { "name" => "My Cool Cafe", "website_url" => "https://mycoolcafe.example", "description" => "Third-wave coffee in Kreuzberg.", "primary_category_id" => "018f7c2a-0000-7000-8000-0000000000a1", "tags" => ["wifi", "outdoor-seating"] }.to_json response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)

Response

{ "data": { "id": "018f7c2a-0000-7000-8000-0000000000c2", "public_id": "9834hd7", "slug": "my-cool-cafe", "name": "My Cool Cafe", "description": "Third-wave coffee in Kreuzberg.", "website_url": "https://mycoolcafe.example", "status": "published", "verification_status": "verified", "primary_category": { "id": "018f7c2a-0000-7000-8000-0000000000a1", "name": "Cafes", "path": "food/cafes" }, "tags": [ "wifi", "outdoor-seating" ], "properties": { "opening_hours": "9-5" }, "created_at": "2026-08-01T09:14:22Z", "updated_at": "2026-08-27T11:02:05.418291Z", "content_updated_at": "2026-08-27T11:02:05Z" } }

Update a listing

PATCH /api/v1/directories/DIRECTORY_ID/listings/LISTING_ID

listings:write The capability a key must carry to call this.

Change a listing's details. Send only the attributes you are changing.

Body parameters

Parameter Type Description
name required string The business's name. Required when you create a listing.
description string The description shown on the listing's public page.
website_url string The address we check for a linkback back to you.
logo_url string A logo to mirror. We fetch and store it; there is no file upload on this API.
primary_category_id string The category the listing belongs to. Required before a listing can be published.
properties object Your directory's custom fields, as a JSON object keyed by field key.
tags array The tags to apply, as an array of names. Sending this replaces the listing's tags.
expected_updated_at string The updated_at you last read, echoed back. If the record has moved since, nothing is written and you get 409 with the record as it now stands. Leave it out to write unconditionally.

curl

curl -X PATCH \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "description": "Now open on Sundays." }' \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID") request = Net::HTTP::Patch.new(uri) request["Authorization"] = "Bearer YOUR_KEY" request["Content-Type"] = "application/json" request.body = { "description" => "Now open on Sundays." }.to_json response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)

Publish, unpublish, archive

A listing's three states, each moved by a call named for what it does. Publishing checks two things: the listing needs a primary category, and a draft counts against your plan's listing limit.

There is no DELETE. A listing carries slug history, linkback checks and enforcement events, which are the evidence behind an enforcement decision, so archiving is a listing's only exit — and a verb that lied about that is a verb your retry logic would believe.

POST /api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/publish

listings:write The capability a key must carry to call this.

Put the listing on the public site. A draft needs a primary category first, and your plan's listing limit is checked here.

curl

curl -X POST \ -H "Authorization: Bearer YOUR_KEY" \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/publish"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/publish") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer YOUR_KEY" response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)
POST /api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/unpublish

listings:write The capability a key must carry to call this.

Take the listing off the public site. It keeps everything it has and can be published again.

curl

curl -X POST \ -H "Authorization: Bearer YOUR_KEY" \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/unpublish"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/unpublish") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer YOUR_KEY" response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)
POST /api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/archive

listings:write The capability a key must carry to call this.

Archive the listing. This is a listing's only exit — there is no delete, because a listing carries the evidence behind an enforcement decision.

curl

curl -X POST \ -H "Authorization: Bearer YOUR_KEY" \ "https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/archive"

Ruby

require "net/http" require "json" uri = URI("https://directories.prosvita.io/api/v1/directories/DIRECTORY_ID/listings/LISTING_ID/archive") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer YOUR_KEY" response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)