---
title: "Authentication"
description: "API keys, scopes and key rotation for Real Estate applications."
url: "https://real-estate-agency.apim.eu/guides/authentication"
image: "https://real-estate-agency.apim.eu/_og/d/c_Ocean.takumi,title_Authentication,description_~QVBJIGtleXMsIHNjb3BlcyBhbmQga2V5IHJvdGF0aW9uIGZvciBSZWFsIEVzdGF0ZSBhcHBsaWNhdGlvbnMu,props_eyJ0aGVtZSI6eyJtb2RlIjoiZGFyayIsImNvbG9ycyI6eyJwcmltYXJ5IjoiI0Q0QTI0QyJ9fX0,p_Ii9ndWlkZXMvYXV0aGVudGljYXRpb24i,s_BDb3wl84vpX_bIJq.png"
---

## Authentication

How applications identify themselves, what scopes exist, and how to rotate keys without interrupting operations.

## [One Header, Nothing Else](#one-header-nothing-else)

All Real Estate APIs authenticate via an **API key**. It goes along with every call in the `apikey` header:

```bash
curl https://api.realestateagency.example/v1/properties \
  -H "apikey: $REAL_ESTATE_API_KEY"
```

There's no token endpoint, no expiry, and no refresh. That keeps integration simple - but it requires treating the key like a password.

## [Applications and Keys](#applications-and-keys)

Every integration is its own **application**. Each application has one or more keys attached to it.

Create at least two applications: one for the sandbox, one for production. Keeping both under one application means you can't selectively lock one down if there's a leak.

The key is shown **exactly once** at creation. After that it can't be read again, only replaced. Keep it in a secret store - not in version control, not in a ticket, not in a config file baked into an image.

## [Scopes](#scopes)

What a key is allowed to do is attached to it. Grant only what the given process needs: a nightly import doesn't need read access to commissions.

| Scope              | Allows                                            |
| :----------------- | :------------------------------------------------ |
| properties:read    | Query properties and media                        |
| properties:write   | Create, update, archive properties, upload media  |
| listings:read      | Query listings and their status, download the PDF |
| listings:write     | Generate and publish listings                     |
| leads:read         | Query leads, search profiles and inquiries        |
| leads:write        | Maintain leads, score inquiries                   |
| appointments:write | Create, reschedule, cancel viewings               |
| valuations:read    | Query market valuations and comparables           |
| commissions:read   | View settlements and invoices                     |

If a scope is missing, the API responds with `403`:

```json
{
  "code": "scope_missing",
  "message": "The API key is missing a required scope.",
  "details": [
    { "field": "listings:write", "reason": "not_granted" }
  ]
}
```

## [Rotating a Key](#rotating-a-key)

A rotation without downtime works by overlapping - that's why an application allows several active keys:

1.  Generate a second key for the existing application. Both are valid immediately.
2.  Switch the deployment over to the new key.
3.  Confirm no more calls arrive with the old one - the application overview shows `last_used_at` per key.
4.  Revoke the old key.

Rotate on a schedule every 90 days, and immediately if a key ever ends up in a log, a ticket, or a repository. A revoked key stops working within seconds.

## [What Not to Do](#what-not-to-do)

-   **Use the key in the browser.** Anything running on an end-user device exposes it. Call the APIs server-side.
-   **One key for everything.** A key per application and environment is the difference between "lock down one access" and "everything's exposed."
-   **Put the key in the URL.** It belongs in the header; query parameters end up in server logs and browser history.