Get started

Quickstart

Turn an API key into an access token and list your properties. Three requests, about five minutes.

You need an API key — a long-lived credential that looks like magic_1830472847398404096_Xk3nQv8mWp2sT5yR9dL4bN6hJ1kF3gZ7xC0vQ8aE. You create it yourself in MagicDoor, in about a minute.

The key is not sent to the API directly. You trade it for a short-lived access token, and the token goes on every request.

  1. Create an API key in MagicDoor

    Open the profile menu in the top-right corner of MagicDoor and choose Developer — or go straight to portal.magicdoor.com/settings/account/developer.

    Click Create API key, give it a name and an expiration date, then click Create key.

    By default the key can do everything you can. To limit it to a smaller set of permissions, see Authentication.

  2. Copy the key before you close the dialog

    The key is shown once. MagicDoor stores only a hash of it, so if you lose it you have to create a new one — it cannot be recovered or shown again. Treat the whole string as a password.

    export MAGICDOOR_API_KEY='magic_1830472847398404096_Xk3nQv8mWp2sT5yR9dL4bN6hJ1kF3gZ7xC0vQ8aE'
  3. Exchange it for an access token

    This is the one endpoint that takes no Authorization header — the key in the body is the credential. Note the host: token exchange lives on the authentication service, not on the service you are about to call.

    curl --request POST \
      --url 'https://auth.magicdoor.com/api-keys/token' \
      --header 'Content-Type: application/json' \
      --data "{\"key\": \"$MAGICDOOR_API_KEY\"}"
    200 OK
    {
      "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
      "tokenType": "Bearer",
      "expiresIn": 900
    }

    expiresIn is seconds — the token is good for 15 minutes.

  4. Call the API

    Send the token as a bearer token. This lists the first ten properties in your portfolio.

    export MAGICDOOR_TOKEN='eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...'
    
    curl --request GET \
      --url 'https://api.portal.magicdoor.com/company-app/properties?page=1&pageSize=10' \
      --header "Authorization: Bearer $MAGICDOOR_TOKEN"
    200 OK (trimmed)
    {
      "items": [
        {
          "id": "1830472847398404096",
          "name": "Sunset Apartments",
          "displayName": "Sunset Apartments",
          "type": "multiFamily",
          "active": true,
          "unitCount": 24,
          "occupiedUnits": 21,
          "totalRent": 44850.00,
          "address": {
            "streetAddress1": "1420 Sunset Boulevard",
            "city": "Austin",
            "state": "TX",
            "zipCode": "78704",
            "country": "US",
            "isValid": true
          }
        }
      ],
      "currentPage": 1,
      "totalPages": 4,
      "pageSize": 10,
      "totalCount": 37
    }
  5. Exchange again when the token expires

    There is no refresh token. When the 15 minutes are up, post the same API key again and get a new access token. Re-exchange slightly early rather than waiting for a 401.

const AUTH = "https://auth.magicdoor.com";
const PORTAL = "https://api.portal.magicdoor.com";

let token: string | undefined;
let expiresAt = 0;

async function getToken(): Promise<string> {
  // Re-exchange a minute early rather than racing the expiry.
  if (token && Date.now() < expiresAt - 60_000) return token;

  const response = await fetch(`${AUTH}/api-keys/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ key: process.env.MAGICDOOR_API_KEY }),
  });
  if (!response.ok) throw new Error(`Token exchange failed: ${response.status}`);

  const body = await response.json();
  token = body.accessToken;
  expiresAt = Date.now() + body.expiresIn * 1000;
  return token!;
}

const properties = await fetch(`${PORTAL}/company-app/properties?page=1&pageSize=10`, {
  headers: { Authorization: `Bearer ${await getToken()}` },
}).then(response => response.json());

for (const property of properties.items) {
  // Ids are strings on purpose — never parse them as numbers.
  console.log(property.id, property.displayName);
}

Every way a key can fail returns the same 401 with the same message — malformed, unknown, revoked, expired, or belonging to a user who has been deactivated or moved to another company. The real reason is deliberately not in the response.

Check in MagicDoor whether the key is still listed, and not revoked or expired. If it looks fine and still fails, contact support.