# UAEProjects Commercial API v1

## Quick start

Production base URL: `https://api.uaeprojects.com/v1`. Local equivalent: `http://testuaeprojects.com/api/v1`.

Send the one-time API key as `Authorization: Bearer up_live_...` (or `up_test_...`). Never put keys in query strings or browser-side code. Every response includes `X-Request-Id`; provide it when requesting support.

```bash
curl --fail-with-body 'https://api.uaeprojects.com/v1/projects?community=dubai-marina&per_page=25' \
  -H 'Authorization: Bearer up_live_REPLACE_ME' \
  -H 'Accept: application/json'
```

## Response and errors

Successful responses use `{ "data": ..., "meta": { "request_id": ..., "generated_at": ... } }`. Lists add `meta.pagination`. Errors use `{ "error": { "code": ..., "message": ..., "details": ... }, "meta": ... }`; validation failures are HTTP 422, authorization failures 401/403, quota failures 429, and unavailable resources 404.

Rate responses expose `RateLimit-Limit`, `RateLimit-Remaining`, `X-Quota-Daily-*`, and `X-Quota-Monthly-*` where applicable. A 429 includes `Retry-After` when a bounded retry is possible.

## Endpoints and permissions

| Endpoint | Scope | Additional entitlement |
|---|---|---|
| `GET /projects` and `/projects/{id-or-slug}` | `projects:read` | — |
| `GET /projects/changes?updated_since=...` | `projects:sync` | — |
| `GET /developers[/{id-or-slug}]` | `developers:read` | — |
| `GET /communities[/{id-or-slug}]` | `communities:read` | — |
| `GET /catalog/metadata` | `catalog:read` | — |
| `GET /projects/{project}/floor-plans` | `floorplans:read` | `floorplans` |
| `GET /projects/{project}/payment-plans` | `paymentplans:read` | — |
| `GET /projects/{project}/media` | `media:read` | `images` |
| `GET /projects/{project}/brochure` | `brochures:read` | `brochures` |
| `GET /projects/{project}/related` | `projects:read` | — |

Project filters: `q`, `developer`, `community`, `state`, `nature`, `min_price`, `max_price`, `updated_since`, `sort`, `page`, and `per_page` (maximum 100). Sort values are `updated_at`, `starting_from`, or `name`; prefix with `-` for descending. Catalog lists accept `q`, `updated_since`, `sort`, `page`, and `per_page`; communities also accept `state`.

Document endpoints return short-lived signed URLs. They must still be requested with the API key and cannot be converted into permanent origin URLs.

## Enterprise request signing

When enabled for a client, send `X-API-Timestamp` (Unix seconds), a unique 16–128 character `X-API-Nonce`, and `X-API-Signature: v1=<hex>`. Build the canonical value using newline separators:

```text
timestamp
nonce
UPPERCASE_METHOD
raw_path_and_query
sha256_hex_of_body
```

For GET/HEAD, the body is empty. HMAC-SHA256 the canonical value with the API key. Timestamps have a five-minute default window and nonces are single-use.

## Integration examples

### PHP

```php
$ch = curl_init('https://api.uaeprojects.com/v1/projects?per_page=25');
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [
    'Authorization: Bearer '.getenv('UAEPROJECTS_API_KEY'), 'Accept: application/json',
]]);
$payload = json_decode(curl_exec($ch), true, flags: JSON_THROW_ON_ERROR);
```

### Laravel

```php
$response = Http::withToken(config('services.uaeprojects.key'))->acceptJson()
    ->retry(2, 500, throw: false)->get('https://api.uaeprojects.com/v1/projects', ['updated_since' => $cursor]);
$response->throw();
$projects = $response->json('data');
```

### Browser JavaScript

Only call from an approved server-side proxy; do not embed a live key in public JavaScript.

```js
const response = await fetch('/your-secure-backend/uaeprojects?community=downtown-dubai');
const { data, meta } = await response.json();
```

### Node.js

```js
const response = await fetch('https://api.uaeprojects.com/v1/projects?per_page=25', {
  headers: { Authorization: `Bearer ${process.env.UAEPROJECTS_API_KEY}`, Accept: 'application/json' }
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const payload = await response.json();
```

### WordPress

```php
$response = wp_remote_get('https://api.uaeprojects.com/v1/projects?per_page=25', [
    'headers' => ['Authorization' => 'Bearer '.get_option('uaeprojects_api_key'), 'Accept' => 'application/json'],
    'timeout' => 15,
]);
if (is_wp_error($response)) { throw new RuntimeException($response->get_error_message()); }
$payload = json_decode(wp_remote_retrieve_body($response), true, flags: JSON_THROW_ON_ERROR);
```

## Webhooks

Supported events are `project.created`, `project.updated`, `project.status_changed`, `project.deleted`, `developer.updated`, and `community.updated`. Deliveries include event ID, timestamp and `X-UAEProjects-Signature: v1=<hex>`. Verify HMAC-SHA256 over `timestamp.event_id.raw_body` with the one-time webhook secret, reject old timestamps, and store processed event IDs idempotently. Return any 2xx response promptly; failed deliveries retry with exponential intervals and can be manually retried by an administrator.

## Versioning and changelog

Breaking changes receive a new URL version. Additive fields and endpoints may be introduced within v1; integrations must ignore unknown JSON fields. Deprecations will be announced before removal. The machine-readable contract is [`/openapi.yaml`](/openapi.yaml).

- 2026-07-20: v1 foundation, commercial authentication, quotas, published catalogs, premium resources, signed requests, usage reporting, and signed webhooks.

## Enterprise catalogue controls and project PDFs

Each client environment has one authorized normalized domain. Configure the exact host (not a URL path or wildcard) and, where required, an IP allow/deny policy. Server-to-server integrations must send `X-Client-Domain`; this header supplements API-key, IP, and optional request-signature controls and is not a replacement for them.

Restricted catalogue clients receive only their assigned states. A state with no selected community grants that client all published communities in the state; selecting communities narrows that state to those communities. Inaccessible projects, communities, premium project resources, related projects, delta records, and PDF generation return `404` and do not affect pagination totals.

`POST /projects/{project}/pdf` requires `project-pdf:generate` and the `project_pdf` entitlement. It returns a short-lived signed `download_url`; callers must retain their bearer key for the download. New PDF generation is governed by editable PDF minute/day/month limits and returns `429` with `Retry-After` when denied. Reusing an identical cached approved PDF does not consume PDF-generation quota, although it remains a normal authenticated API request. Keys belong only in private server-side configuration; use staging `up_test_` keys outside production and never expose `up_live_` keys in browser code.
