MagicDoor API
A REST API over the properties, units, leases, tenants, owners, vendors, maintenance and accounting behind your portfolio. Everything the MagicDoor company portal can do, your own software can do too.
MagicDoor does not offer this API as a product — it is the interface our own applications use. There is no version number, no compatibility guarantee, and it can change at any time. Read API stability before you build on it.
This API serves the company portal — the property manager's side of MagicDoor. It is the same API the MagicDoor web app calls, so anything your team can do on screen is available here.
The tenant, owner, and vendor portals are separate and are not documented here.
MagicDoor is a set of services rather than one origin, and each has its own base URL. Most sit behind a shared gateway:
https://services.magicdoor.com/leasing
https://services.magicdoor.com/accounting
https://services.magicdoor.com/maintenanceTwo do not. Authentication has its own host, and a large part of the API is still served by the original MagicDoor application, which has its own host as well:
https://auth.magicdoor.com # API keys and access tokens
https://api.portal.magicdoor.com # properties, units, leases, tenants, owners, and moreEvery page in the API reference prints its own base URL at the top, already resolved, next to the method and path. Read it from there rather than assembling one yourself.
MagicDoor is steadily moving endpoints off the original application onto their own services. When an endpoint moves, its page here changes with it — so treat the base URL as something you read per endpoint, not a constant you set once.
These hold across every endpoint. Several are not what you would guess from other APIs, so they are worth reading once.
Every id is a 64-bit number, and it is always sent as a string:
{
"id": "1830472847398404096",
"propertyId": "1830472801245167616"
}Keep ids as strings in your own code. They exceed the range JavaScript numbers can represent exactly, so parsing one as a number would silently round it and you would read and write the wrong record.
Amounts are plain decimal numbers in the company's currency. 1850.00 means $1,850.00. There is no minor-unit encoding anywhere in this API.
| Kind | Looks like | Means |
|---|---|---|
| Date | "2026-03-01" | A calendar day — a lease start, a due date |
| Timestamp | "2026-08-17T21:04:11" | A moment, already converted to your company's timezone |
Timestamps deliberately have no Z and no offset. The server has already converted them to the timezone configured on your company, so what you get back is the wall-clock time your team would see in the app. Do not treat one as UTC.
The same applies in reverse: a timestamp you send is interpreted in your company's timezone.
A field with no value is left out of the response entirely — you will not see "end": null. Check whether the key is present, not whether it is null.
So a lease that has not ended simply has no end property:
{
"id": "1830472847398404096",
"start": "2026-03-01",
"monthToMonth": true
}Fixed value sets are strings, never numbers: "active", "waitingForApproval", "monthToMonth". Every endpoint page lists the values its fields can take.
Property names are camelCase throughout, in both requests and responses.
Requests are limited to 100 per minute per endpoint, counted per calling IP address. A few services are more generous — the accounting service allows 500 per minute, and the authentication service 300.
Going over returns 429 with the number of seconds to wait:
{
"type": "https://httpstatuses.com/429",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Try again in 42 seconds.",
"error": "rate_limit_exceeded",
"retry_after": 42
}A Retry-After header carries the same number. Wait that long before retrying — retrying immediately only consumes the next window.
Stated plainly, so you do not go looking:
- No webhooks. MagicDoor does not call your servers when something changes. Poll the endpoints you care about.
- No
Idempotency-Keyheader. A retriedPOSTcreates a second record, so only retry when you never saw a response at all. There is exactly one exception — paying a company invoice takes anidempotencyKeyin the request body — and it is documented on that endpoint's own page. - No cursor pagination. Paging is by page number — see Pagination.
- No versioning, and no compatibility promise. See API stability.