Skip to content

Open Beta – help us test! All listings are examples only.

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 returns 404. It is GA-ready and may be the first Plan 60 endpoint enabled for your organization — confirm with your WOHNO contact. The operation is x-internal and not yet in the public reference.

Prerequisites

  • A publishable key (pk_live_…) with an origin allowlistwbs:check is on the publishable whitelist, so this is safe in the browser.
  • The scope wbs:check.
  • WBS_API_ENABLED enabled 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

CodeHTTPWhat happenedFix
VALIDATION_ERROR400Unknown state code or unknown fieldSend a valid ISO-2 bundesland; strict body.
ORIGIN_NOT_ALLOWED403Calling domain not on the key's allowlistAdd the domain in Settings → API.
ORIGIN_REQUIRED403pk_ key called without OriginCall from a browser.
NOT_FOUND404WBS_API_ENABLED is offAsk to enable the flag.
RATE_LIMITED429Over 60 requests / 60 s for this scopeDebounce 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