Embed a WBS eligibility check
Add a stateless WBS (Wohnberechtigungsschein) eligibility estimator to your front-end with a browser-safe publishable key.
This guide shows how to add a WBS eligibility estimator to your website — a small form where a household enters its size, federal state and net income and gets an unbinding eligibility estimate. The check is stateless: nothing is persisted, so it is safe to run straight from the browser.
The problem this solves: prospective tenants want to know upfront whether they likely qualify for a WBS before applying. Embedding the check reduces unqualified applications.
Beta / dark-shipped. The WBS check (Plan 60) is gated behind the feature flag
WBS_API_ENABLED; while off it returns404. It is GA-ready and may be the first Plan 60 endpoint enabled for your organization — confirm with your WOHNO contact. The operation isx-internaland not yet in the public reference.
Prerequisites
- A publishable key (
pk_live_…) with an origin allowlist —wbs:checkis on the publishable whitelist, so this is safe in the browser. - The scope
wbs:check. WBS_API_ENABLEDenabled for your organization.
Step 1 — Call the check
It is a single stateless POST. The body is strict:
curl -X POST https://wohno.de/api/v1/wbs/check \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Origin: https://www.example.com" \
-H "Content-Type: application/json" \
-d '{
"bundesland": "BE",
"household_size": 3,
"monthly_net_income": 2800
}'bundesland— the ISO-3166-2 two-letter German state code (e.g.BE,BY).household_size— 1 to 12.monthly_net_income— household net income in EUR.city_override— optional, for cities with their own income limits.
Response (200):
{
"data": {
"eligible": true,
"income_limit": 3400,
"headroom": 600,
"basis": "Unverbindliche Schätzung auf Basis der Landeseinkommensgrenzen. Keine Rechtsverbindlichkeit."
}
}basis carries the legally required "unbinding estimate" disclaimer — always
display it next to the result.
Step 2 — Wire up a minimal form
<form id="wbs-form">
<select name="bundesland" required>
<option value="BE">Berlin</option>
<option value="BY">Bayern</option>
<!-- … all 16 states … -->
</select>
<input name="household_size" type="number" min="1" max="12" required />
<input name="monthly_net_income" type="number" min="0" required />
<button type="submit">Check eligibility</button>
</form>
<p id="wbs-result"></p>
<script>
const WOHNO_PK = "pk_live_xxxxxxxxxxxxxxxxxxxxxxxx";
document.getElementById("wbs-form").addEventListener("submit", async (e) => {
e.preventDefault();
const f = new FormData(e.target);
const res = await fetch("https://wohno.de/api/v1/wbs/check", {
method: "POST",
headers: { "X-API-Key": WOHNO_PK, "Content-Type": "application/json" },
body: JSON.stringify({
bundesland: f.get("bundesland"),
household_size: Number(f.get("household_size")),
monthly_net_income: Number(f.get("monthly_net_income")),
}),
});
const out = document.getElementById("wbs-result");
if (!res.ok) {
const { error } = await res.json();
out.textContent = `Could not check (${error.code}).`;
return;
}
const { data } = await res.json();
out.textContent = data.eligible
? `Likely eligible — about ${data.headroom} € below the limit. ${data.basis}`
: `Likely not eligible. ${data.basis}`;
});
</script>The browser adds the Origin header; it is validated against your key's
allowlist.
Error handling
| Code | HTTP | What happened | Fix |
|---|---|---|---|
VALIDATION_ERROR | 400 | Unknown state code or unknown field | Send a valid ISO-2 bundesland; strict body. |
ORIGIN_NOT_ALLOWED | 403 | Calling domain not on the key's allowlist | Add the domain in Settings → API. |
ORIGIN_REQUIRED | 403 | pk_ key called without Origin | Call from a browser. |
NOT_FOUND | 404 | WBS_API_ENABLED is off | Ask to enable the flag. |
RATE_LIMITED | 429 | Over 60 requests / 60 s for this scope | Debounce input; respect Retry-After. |
See the conventions reference.
Best practices
- Show the disclaimer. The estimate is unbinding; always render
basis. - Debounce. This scope is rate-limited to 60 requests per 60 seconds — do not call on every keystroke.
- No persistence needed. The check stores nothing; you own any results you decide to keep.
Next steps
- Embed listings on your website — pair the check with a
wbs_onlyfiltered listing grid. - Build a discovery aggregator — the other publishable Plan 60 endpoint.
- API Reference — WBS input and result fields.