Search for "Ejar API integration" and you will find articles listing tidy endpoints: POST /contracts/register, GET /contracts/{ref}/payments, POST /disputes/file. They read like documentation. They cite no source, because there is no public source to cite.
Ejar (شبكة إيجار), the electronic network that regulates every rental contract in Saudi Arabia, does not publish a developer portal. There is no signup page, no sandbox key, no OpenAPI spec you can download this afternoon. The platform's own X account answers the question directly: for API linkage between Ejar and another platform, write to keyaccounts@nhc.sa. That is the entire published route.
This matters more than it sounds. If you are building or buying a property management system for the Saudi market, the access model shapes the architecture — not the other way around. Teams that assume "we'll just call the API" build a system whose core assumption fails at contract review, six months in.
The Volume That Makes This a Real Market
Ejar has registered more than 10 million rental contracts since launch, running at roughly 19,000 new contracts per day. The split is 8.3 million residential (82.3%) against 1.7 million commercial (17.6%). Since the start of 2024 alone, over 1.5 million contracts have been documented — 1.2 million residential and 283,000 commercial.
Those are the numbers a property management vendor is selling against. A 500-unit portfolio generates something on the order of 40 registrations and 30 renewals per month, each one a multi-step portal session that somebody performs by hand while the same data already sits in the company's own system. The work is real. The question is only how you wire it.
Ejar Has No Public API — Design for That
The official integration path is a program, not a signup. Ejar runs a digital integration track with real estate platforms and property management systems, announced publicly in 2022 with an initial cohort of twelve platforms and expanded since. Vendors like Simaat announce their technical linkage agreement as a milestone, which tells you what kind of thing it is: a commercial and regulatory arrangement, not a self-serve credential.
Three consequences follow, and each one is an architecture decision:
You cannot prototype against production. Whatever integration testing exists is granted, not discovered. Your build plan needs a phase where the Ejar adapter is a stub with a hand-verified fixture set, and you must be honest with stakeholders that the fixtures are your assumption, not the contract.
The endpoint surface is not yours to publish. Anyone showing you exact Ejar request bodies in a public blog post either has an agreement they are breaching or invented them. Treat those articles as marketing, not reference. When you get the real integration pack, expect it to differ from every guess you made.
Eligibility comes before code. The integration is for licensed real estate activity. The broker or brokerage entity needs a valid REGA registration, a valid commercial register, and Nafath-authenticated accounts behind it. This is the same access model as Etimad, Muqeem and SIMAH — approval gates the credentials, and the credentials gate the code.
The honest planning stance: assume 8–16 weeks between first contact and working credentials, and build the system so it delivers value with a human in the loop for that whole period.
Design Rule: Ejar Is the System of Record
The single most expensive mistake in Saudi property management software is treating your own database as the source of truth for a lease.
It is not. An unregistered contract is not an enforceable instrument — it cannot be taken directly to the execution court, and it is not accepted for Citizen Account or the developed social insurance benefits. The legal object is the Ejar contract, identified by the Ejar contract number. Your database holds operational state around it: maintenance tickets, collection status, tenant communication, accounting entries.
That distinction should be visible in the schema. A lease row without an ejar_contract_number is a draft, not a lease, and your code should refuse to invoice against it:
type LeaseState =
| { kind: "draft"; localId: string }
| { kind: "submitted"; localId: string; submittedAt: Date }
| { kind: "awaiting_parties"; localId: string; ejarRef: string }
| { kind: "registered"; localId: string; ejarRef: string; startsOn: Date; endsOn: Date }
| { kind: "terminated"; localId: string; ejarRef: string; terminatedOn: Date };
function canIssueRentInvoice(lease: LeaseState): boolean {
// Only a registered Ejar contract is a billable legal instrument.
return lease.kind === "registered";
}The awaiting_parties state is the one teams forget. Registration is not a single atomic call — the contract is entered, then both landlord and tenant receive SMS notifications and must authenticate it electronically, and only then does documentation complete. Your state machine has to survive a contract sitting in limbo for days because a tenant has not opened a text message. Model it explicitly, expose it in the UI, and give operations a nudge action. Do not hide it behind a spinner.
The 2026 Rules That Break Your Renewal Logic
Most property management systems ship a renewal module that assumes two things: leases expire, and rent goes up. Both assumptions are now wrong in Saudi Arabia, and a system that quietly gets this wrong exposes its customer to fines.
Leases renew automatically. Kingdom-wide, a lease renews unless a party gives written notice of non-renewal at least 60 days before expiry. A renewal workflow that starts at expiry minus 30 days is already too late to be useful. The notice window is the deadline your scheduler should fire on, not the expiry date.
Rent in Riyadh is frozen. A five-year freeze on residential and commercial rental prices in Riyadh's urban area took effect on 25 September 2025 and runs through 24 September 2030. The rent in effect on that date is binding for both new and existing leases. Escalation clauses that existed in contracts as of 25 September 2025 remain enforceable; contracts entered after that date cannot contain escalation increases during the freeze. Sub-lease rent cannot exceed the head lease rent. REGA's board can extend the measures to other cities with approval from the Council of Economic and Development Affairs — so hardcoding "Riyadh" as the special case is a maintenance trap. Make it a policy table keyed by region with an effective date range.
Non-renewal grounds are enumerated. In Riyadh, a landlord may refuse renewal only on specific grounds: tenant default, structural safety defects supported by technical reports, the landlord's own residential use or that of a first-degree relative, or other cases REGA determines. If your product offers a "do not renew" button, it needs a reason code, and that reason code needs to be stored. It is your customer's evidence.
The penalties are not nominal. Violations of the rent-freeze and renewal rules carry fines of up to twelve months' rent for the property, plus cure obligations and compensation to affected parties, with a 30-day appeal window. Separately, a brokerage that fails to register contracts faces penalties under the real estate offices regulations reaching SAR 25,000, office closure for up to a year, or licence revocation. And there is a whistleblower incentive: informants can receive up to 20% of collected fines. Your customer's disgruntled ex-tenant has a financial reason to report a bad renewal.
Here is the shape of the rule the scheduler actually needs:
interface RenewalPolicy {
region: string; // "riyadh_urban", "makkah", ...
effectiveFrom: Date;
effectiveTo: Date | null;
escalationAllowed: boolean; // false inside a rent-freeze window
noticeDays: number; // 60 kingdom-wide today
}
function renewalNoticeDeadline(endsOn: Date, policy: RenewalPolicy): Date {
const d = new Date(endsOn);
d.setDate(d.getDate() - policy.noticeDays);
return d; // alert operations here, not on the expiry date
}Note what is not in that function: a hardcoded 60, and a hardcoded 5%. Regulation in this sector has changed twice in eighteen months. Anything you inline today becomes a code change and a release when it moves.
Three Ledgers, One Rent Payment
This is where the integration work actually lives, and it is the part vendor demos skip.
A single month of rent on a single unit exists in three separate systems that do not talk to each other:
- Ejar holds the contract: parties, term, rent amount, the legal identity of the tenancy.
- ZATCA holds the tax invoice. Commercial rent is a VAT-bearing supply, and the invoice must be cleared or reported through Fatoora with the correct buyer details and a valid national address. Our ZATCA e-invoicing guide covers that pipeline in depth.
- The bank holds the money — a SADAD payment, a transfer, a mada collection — with a reference that resembles neither of the other two.
Nothing joins these automatically. The join key is one your system has to manufacture and defend. When they drift, the symptoms are familiar to anyone who has audited a Saudi real estate company: a contract registered in Ejar with no matching invoice, an invoice cleared by ZATCA against a lease that was terminated last month, cash received with no invoice to apply it to.
A nightly reconciliation is not a nice-to-have here. It is the product:
type Drift =
| { code: "contract_without_invoice"; ejarRef: string; period: string }
| { code: "invoice_without_contract"; invoiceId: string }
| { code: "invoice_on_terminated_lease"; invoiceId: string; ejarRef: string }
| { code: "cash_unapplied"; paymentRef: string; amountHalalas: number }
| { code: "amount_mismatch"; ejarRef: string; contractHalalas: number; invoicedHalalas: number };
function reconcile(
contracts: RegisteredContract[],
invoices: TaxInvoice[],
payments: BankPayment[]
): Drift[] {
const byRef = new Map(contracts.map((c) => [c.ejarRef, c]));
const drifts: Drift[] = [];
for (const inv of invoices) {
const contract = inv.ejarRef ? byRef.get(inv.ejarRef) : undefined;
if (!contract) {
drifts.push({ code: "invoice_without_contract", invoiceId: inv.id });
continue;
}
if (contract.terminatedOn && inv.issuedOn > contract.terminatedOn) {
drifts.push({ code: "invoice_on_terminated_lease", invoiceId: inv.id, ejarRef: contract.ejarRef });
}
// Compare in halalas. Never in floats.
if (inv.totalExVatHalalas !== contract.periodRentHalalas) {
drifts.push({
code: "amount_mismatch",
ejarRef: contract.ejarRef,
contractHalalas: contract.periodRentHalalas,
invoicedHalalas: inv.totalExVatHalalas,
});
}
}
const applied = new Set(payments.filter((p) => p.invoiceId).map((p) => p.paymentRef));
for (const p of payments) {
if (!applied.has(p.paymentRef)) {
drifts.push({ code: "cash_unapplied", paymentRef: p.paymentRef, amountHalalas: p.amountHalalas });
}
}
return drifts;
}Two details worth stealing. Every amount is an integer of halalas — the 100× rounding trap in Saudi payment integrations is real, and rent contracts are large enough that a floating-point cent becomes a visible discrepancy on an annual statement. And the function returns drift records rather than throwing: reconciliation should produce a work queue for a human, not fail a batch job at 03:00.
Registration and documentation fees are a related trap. They are charged per contract and payable by the landlord, and the published amounts have changed. Do not hardcode them into your billing logic — model them as a dated fee schedule you can update without a deploy, and verify the current values against Ejar's own fee pages before each cycle.
What to Build Before You Have Credentials
The waiting period is not dead time. Everything below delivers value with a human performing the portal steps, and none of it is thrown away when the API arrives:
- The state machine. Draft, submitted, awaiting parties, registered, terminated — with timestamps and an append-only event log. This is the part the API will plug into, unchanged.
- The operator queue. Instead of an API call, emit a task with the exact payload a human needs to paste into the portal, and require them to record the returned Ejar contract number. You are collecting the fixture set for your future adapter for free.
- Drift detection. Reconciliation against contracts exported from Ejar works today, by CSV, with no API at all. It is also the single most compelling thing you can show a prospect, because it finds money.
- The adapter interface. Define
EjarGatewayas an interface with a manual implementation now and a network implementation later. The rest of the system never learns which one it is talking to. - Identity plumbing. Nafath authentication and identity verification are prerequisites across the whole Saudi government stack — see our guide to Yakeen, Nafath and Wathq. Build it once, use it for every platform you touch afterwards.
That sequence has a useful property: if the integration agreement takes longer than expected, or is declined, the product still works. If you build API-first and the credentials do not arrive, you have nothing.
The Real Risk Is Silent Non-Compliance
Nobody in this market gets fined for a slow renewal screen. They get fined for a contract that was never registered, a rent increase applied inside a freeze window, a renewal refused without a valid ground, or a tax invoice that does not match the registered contract. Every one of those is a data-consistency failure that a reporting layer catches and a CRUD app does not.
That is the shift worth making in how you scope the project. Ejar integration is usually specified as a data-entry saving. Its actual value is as a compliance control — the thing that tells your customer, every morning, which of their 500 leases is currently out of step with the registry, the tax authority, or the bank. Automating the typing is the cheap half. Proving the books agree is the half that gets renewed.
Building or auditing a property management system for the Saudi market? We work on exactly this layer — the integration and reporting surface above Ejar, ZATCA and banking, including reconciliation for systems already in production. If you want a straight assessment of where your lease data is currently drifting, get in touch and we will walk through your contract, invoice and collection flows with you.
Sources
- SPA — Ejar network registers over 10 million rental contracts
- REGA — Ejar platform
- Ejar — Digital integration with real estate marketing platforms
- King & Spalding — Saudi Arabia introduces rent controls and automatic lease renewal
- Shwra — Documenting electronic lease contracts via Ejar and the penalty for non-documentation
- National Platform — Digital integration with real estate platforms