Partner API
The API an integration partner such as VolgJeWoning uses to map projects and bouwnummers, read a buyer's confirmed choices, and hand a signed-in buyer over to their own portal
Partner API
An integration partner sits in front of the buyer: the buyer signs in there, sees their home there, and clicks through to Alpha to configure it. This API is what that partner drives Alpha through.
It was built for VolgJeWoning, and the shape follows what a partner actually has to do, in order:
- match its own project against an Alpha project;
- match its own woningen against Alpha bouwnummers, and store that mapping;
- send a buyer into their house without a second login;
- read back the confirmed choices to process in its own system.
The endpoints are deliberately hand-written and narrow rather than the generic CRUD surface. A partner contract has to stay stable across our own refactors, and the internal entities carry plenty a partner has no business seeing: cost prices, worker events, model instances.
Authentication
Every call takes an API key scoped to one tenant and carrying the READ_PROJECTS permission.
Create one under Settings → API keys; see Roles & permissions
for how the permission is granted.
A partner key reads exactly what a read-only staff account of that tenant could, and nothing more. There is no cross-tenant partner key: issue one per tenant.
Everything below is relative to the API base path, and every path starts with the partner's own namespace code:
/partner/{partnerCode}partnerCode (vjw, 4ps, …) is a namespace, not a credential. The API key decides what the
caller may do; the code only decides whose mapping is read and written, so two partners never
overwrite each other's references on the same house.
Errors
| Status | When |
|---|---|
401 | Missing or invalid API key |
403 | The key lacks READ_PROJECTS, or the tenant is inactive |
404 | The project, house or configuration does not exist in the key's tenant |
400 | The request cannot be honoured, with a message saying why |
A record in another tenant answers 404, never 403. The response must not confirm that something
exists somewhere else.
Mapping projects
GET /partner/{partnerCode}/projectsEvery project in the tenant. Match on slug, which is the closest thing Alpha has to a project
number, and fall back to name.
[
{
"id": "project-1",
"name": "De Waarden fase IV",
"slug": "de-waarden-iv",
"phase": "SALES",
"houseCount": 48
}
]houseCount counts houses across every scenario, as a sanity check that both sides matched the
same plan.
Mapping houses
GET /partner/{partnerCode}/projects/{projectId}/houses[
{
"bnr": "12",
"projectId": "project-1",
"blockId": "block-1",
"typeId": "type-a",
"status": "SOLD",
"hasConfirmedConfiguration": true,
"confirmedConfigurationId": "config-a",
"configurationCount": 3,
"hasBuyer": true,
"externalRef": "VJW-9912"
}
]bnr is the bouwnummer, and it is the natural key both sides recognise. It is unique within the
project, not across the tenant, and partners spell it differently (12, BNR 12, 0012), which
is why a confirmed mapping is stored rather than re-derived on every call:
PUT /partner/{partnerCode}/projects/{projectId}/houses/{bnr}/external-ref
{ "externalRef": "VJW-9912" }Send a blank or null externalRef to clear it. The mapping is stored under the calling
partnerCode only, so writing a VolgJeWoning reference never disturbs the 4PS one.
hasBuyer says whether a buyer is recorded, without exposing who: needed to know whether a sign-in
hand-off can work at all, and nothing more.
Handing a buyer over
POST /partner/{partnerCode}/projects/{projectId}/houses/{bnr}/sign-in-url
{ "redirectPath": "/portal/configurations" }{
"url": "https://alpha.asrr.nl/portal/magic-link?token=…&tenant=…&redirect=…",
"bnr": "12",
"expiresAt": "2026-09-11T09:42:00Z"
}Hand the returned URL straight to the buyer's browser. The buyer lands signed in, on their own
house by default, or wherever redirectPath points inside the portal.
Request one per click. Never store one. The URL is single-use and expires in minutes. This is deliberately unlike the link in a configuration confirmation email, which stays valid and reusable for 30 days because it lives in a mailbox the buyer returns to. A partner key that could mint 30-day reusable links for any bouwnummer would be a master key to every buyer's portal.
Two things are refused rather than honoured:
- An
emailthat is not the buyer recorded on the house. The field exists for partners that are authoritative about who the buyer is; supplying a different address is refused, so the endpoint cannot mint a portal session for an arbitrary person on someone else's bouwnummer. Omit it and the recorded buyer is used. - A
redirectPaththat is not a path on our own frontend. Anything not starting with/is ignored and the buyer lands on their house.
400 also comes back when no buyer is recorded on the house, when the buyer has no email address,
when the buyer portal is switched off for the tenant or project (see
Configurations & drawings), or when the address belongs to an account
Alpha must not auto-provision, meaning staff or an unverified signup.
Reading the choices
A house can carry several configurations, because a buyer may iterate until their phase closes. The partner must be told which one is definitive rather than assuming the newest is.
GET /partner/{partnerCode}/projects/{projectId}/houses/{bnr}/configurationsNewest first. confirmed marks the one assigned to the house; source is CUSTOMER when the buyer
submitted it themselves and INTERNAL when staff did.
Then fetch the kassabon:
GET /partner/{partnerCode}/configurations/{configurationId}{
"id": "config-a",
"projectId": "project-1",
"projectName": "De Waarden fase IV",
"bnr": "12",
"typeId": "type-a",
"typeName": "Type A hoek",
"status": "COMPLETED",
"source": "CUSTOMER",
"confirmed": true,
"favorite": true,
"basePrice": 385000.0,
"totalPrice": 12450.0,
"options": [
{
"code": "03.12.040",
"option": "Keukenopstelling",
"category": "Keuken",
"description": "Uitbouw 1,2 m",
"price": 9800.0,
"base": false
}
]
}code is the option code as Alpha knows it, which is the code imported from 4PS where that
integration is configured (see 4PS integration). That is the
value to map onto the partner's own product list.
base: true marks the standard choice rather than an upgrade; partners normally write back only the
upgrades. basePrice is the house's v.o.n. price where the project carries one, and totalPrice is
the sum of the option lines, excluding it.
The totals come from the same enrichment behind the buyer's own portal page and the PDF receipt, so a partner and a buyer can never see different numbers for one configuration.
Buyers who are not on the partner platform
Buyers of an external builder have no account with the partner, so the hand-off above does not apply to them. They reach the portal through the invite from the sales module instead, which is the route that depends on no other system. See Getting buyers into the portal.