Embed listings on your website
Show your live WOHNO listings on an external site (WordPress, plain HTML) with a browser-safe publishable key and an origin allowlist.
This guide takes you from zero to a working listings grid on your own website —
WordPress, a static page, or any front-end framework. You will use a
publishable key that is safe to ship in the browser, locked to your domain
with an origin allowlist, and read your organization's listings over
https://wohno.de/api/v1.
The problem this solves: you maintain your listings in WOHNO but want them to appear on your agency's marketing site, kept in sync automatically, without running a server.
Prerequisites
- A WOHNO account with at least one listing.
- A publishable key (
pk_live_…). Publishable keys are read-only and require an origin allowlist — that is what makes them safe to expose in a browser bundle. - The scope
listings:read. - The
LISTINGS_PUBLIC_API_ENABLEDfeature is generally available — no extra flag needed.
Never use a secret key (
sk_…) in browser code. Secret keys carry write and delete scopes; exposing one would let anyone mutate your data.
Step 1 — Create a publishable key with an origin allowlist
- Open Dashboard → Settings → API.
- Click Create API key and choose Publishable key (
pk_…). - Grant the
listings:readscope. - Add your site's origins to the origin allowlist, for example
https://www.example.com. A subdomain wildcard such ashttps://*.example.commatches exactly one subdomain level. HTTPS is required (excepthttp://localhostfor local testing). - Save and copy the key. Publishable keys are browser-safe, so you can paste this one straight into your front-end.
The allowlist is enforced server-side: browsers automatically send the Origin
header, and a call from a domain that is not on the list is rejected with
403 ORIGIN_NOT_ALLOWED. A pk_ call with no Origin header at all returns
403 ORIGIN_REQUIRED.
Step 2 — Fetch listings from the browser
Send a GET to /api/v1/listings with the key in the X-API-Key header. The
endpoint is cursor-paginated and accepts filters:
curl "https://wohno.de/api/v1/listings?city=Berlin&rooms_min=2&limit=12" \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Origin: https://www.example.com"A successful response (200):
{
"data": [
{
"id": "9f1c2a3b-1111-2222-3333-444455556666",
"title": "Bright 2-room apartment",
"city": "Berlin",
"zip": "10115",
"rooms": 2,
"rent_cold": 980,
"property_type": "apartment",
"status": "published"
}
],
"pagination": {
"next_cursor": "eyJpZCI6...",
"has_more": true,
"limit": 12
}
}Available filters include city, zip, rooms_min, rooms_max, rent_max,
property_type, wbs_only, pets and a free-text q. limit ranges from 1 to
50 (default 20). See the
listings reference for the full field whitelist.
Step 3 — Render the grid (vanilla JS embed)
Drop this snippet into any page — a WordPress Custom HTML block works the
same as a static .html file:
<div id="wohno-listings"></div>
<script>
const WOHNO_PK = "pk_live_xxxxxxxxxxxxxxxxxxxxxxxx";
async function loadListings() {
const res = await fetch(
"https://wohno.de/api/v1/listings?city=Berlin&limit=12",
{ headers: { "X-API-Key": WOHNO_PK } },
);
if (!res.ok) {
const body = await res.json();
console.error("WOHNO API error:", body.error.code);
return;
}
const { data } = await res.json();
const root = document.getElementById("wohno-listings");
root.innerHTML = data
.map(
(l) => `
<a class="wohno-card" href="https://wohno.de/inserate/${l.id}">
<h3>${l.title}</h3>
<p>${l.city} · ${l.rooms} rooms · ${l.rent_cold} € cold</p>
</a>`,
)
.join("");
}
loadListings();
</script>The browser supplies the Origin header automatically, so you do not set it in
fetch — it is added by the browser and validated against your allowlist.
Step 4 — Paginate
To load more, pass the next_cursor from the previous response as ?cursor=
while pagination.has_more is true:
curl "https://wohno.de/api/v1/listings?city=Berlin&limit=12&cursor=eyJpZCI6..." \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Origin: https://www.example.com"Error handling
Branch on the stable error.code, never the message text:
| Code | HTTP | What happened | Fix |
|---|---|---|---|
ORIGIN_REQUIRED | 403 | pk_ key called without an Origin header | Call from a browser, not a bare server script. |
ORIGIN_NOT_ALLOWED | 403 | Calling domain is not on the key's allowlist | Add the domain in Settings → API. |
INSUFFICIENT_SCOPE | 403 | Key lacks listings:read | Grant the scope. |
RATE_LIMITED | 429 | Over 1000 requests/hour for this key | Cache results; respect Retry-After. |
See the conventions reference for the full error table.
Best practices
- Cache responses. Listings change slowly; the endpoint returns an
ETag. Send it back asIf-None-Matchto get a cheap304 Not Modified. - One key per site. Scope each publishable key to a single origin so you can rotate it without breaking other embeds.
Next steps
- Sync listings from your CRM — push listings into WOHNO from a server.
- Authentication guide — key types, origin allowlists and rotation.
- API Reference — the full
listingsfield whitelist.