# MagicDoor API

Source: https://api-docs.magicdoor.com/introduction

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.

- [Quickstart](https://api-docs.magicdoor.com/quickstart.md) — Exchange an API key for a token and make your first call.
- [Authentication](https://api-docs.magicdoor.com/authentication.md) — How API keys work, and how to scope one down.
- [API reference](https://api-docs.magicdoor.com/api-reference/introduction.md) — Every endpoint, parameter, and response shape.

> **Warning: This API is not versioned**
>
> 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](https://api-docs.magicdoor.com/stability.md) 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.

## Base URLs

MagicDoor is a set of services rather than one origin, and each has its own base URL. Most sit behind a shared gateway:

**Most services**

```bash
https://services.magicdoor.com/leasing
https://services.magicdoor.com/accounting
https://services.magicdoor.com/maintenance
```

Two 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:

**The two exceptions**

```bash
https://auth.magicdoor.com          # API keys and access tokens
https://api.portal.magicdoor.com    # properties, units, leases, tenants, owners, and more
```

> **Tip: Do not hard-code these**
>
> **Every page in the [API reference](https://api-docs.magicdoor.com/api-reference/introduction.md) 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.

## Conventions

These hold across every endpoint. Several are not what you would guess from other APIs, so they are worth reading once.

### Identifiers are strings

Every id is a 64-bit number, and it is always sent as a **string**:

```json
{
  "id": "1830472847398404096",
  "propertyId": "1830472801245167616"
}
```

> **Warning:**
>
> 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.

### Money is a decimal, not cents

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.

### Dates and times carry no timezone

| 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.

### Absent rather than null

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:

```json
{
  "id": "1830472847398404096",
  "start": "2026-03-01",
  "monthToMonth": true
}
```

### Enumerations are camelCase strings

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.

## Rate limits

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:

**429 Too Many Requests**

```json
{
  "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.

## What this API does not have

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-Key` header.** A retried `POST` creates a second record, so only retry when you never saw a response at all. There is exactly one exception — paying a company invoice takes an `idempotencyKey` in the request body — and it is documented on that endpoint's own page.
- **No cursor pagination.** Paging is by page number — see [Pagination](https://api-docs.magicdoor.com/pagination.md).
- **No versioning, and no compatibility promise.** See [API stability](https://api-docs.magicdoor.com/stability.md).

## Next

- [Finding a lease](https://api-docs.magicdoor.com/walkthrough.md) — Walk from a portfolio down to a lease, then read it.
- [Make your first call](https://api-docs.magicdoor.com/quickstart.md) — Key, token, request — about five minutes.
- [Errors](https://api-docs.magicdoor.com/errors.md) — One error shape across every service, and what each status means.
