Reporting API
A scoped API for pulling your office's EvaCore data into Power BI, Excel or your ERP. Every key belongs to one office, reads only that office's data, and only within the scopes the owner chose.
1. Create a key
Only the office owner creates keys: Integrations → Reporting API → New key. Choose a name, the scopes and an expiry. The key (it starts with evak_) is shown once. Store it like a password. Revoke it any time and it stops at once.
| Scope | Permission | Endpoints |
|---|---|---|
read | Read all resources (including messages) | /api/v1/leads, /api/v1/deals, /api/v1/properties, /api/v1/projects, /api/v1/units, /api/v1/reservations, /api/v1/activities, /api/v1/tasks, /api/v1/agents, /api/v1/teams, /api/v1/conversations, /api/v1/messages |
leads:write | Create, update and assign leads | /api/v1/leads |
properties:write | Create and update properties and status | /api/v1/properties |
leads:read | Leads | /api/v1/leads |
deals:read | Deals | /api/v1/deals |
properties:read | Properties | /api/v1/properties |
projects:read | Projects, units and reservations | /api/v1/projects, /api/v1/units, /api/v1/reservations |
activities:read | Activities and tasks | /api/v1/activities, /api/v1/tasks |
agents:read | Agents and teams | /api/v1/agents, /api/v1/teams |
conversations:read | Conversations (metadata only) | /api/v1/conversations |
messages:read | Message text | /api/v1/messages |
2. Send the key
In the Authorization header, either way. Never put the key in the URL.
curl -H "Authorization: Bearer evak_YOUR_KEY" \
"https://app.evacore.io/api/v1/leads?limit=100"
# Basic auth. Any user name, the key as the password (what Power BI uses):
curl -u "api:evak_YOUR_KEY" "https://app.evacore.io/api/v1/deals?updated_since=2026-10-01T00:00:00Z"
# CSV for Excel / Power BI. Every matching row in one response:
curl -u "api:evak_YOUR_KEY" "https://app.evacore.io/api/v1/properties?format=csv&fields=title,city,price,status" -o properties.csv3. The response
UTF-8 JSON with Arabic text as-is. Times are UTC ISO 8601; SAR amounts are strings with two decimals ("1500000.00") so no precision is lost. An empty field is null.
{
"object": "list",
"resource": "leads",
"data": [
{
"id": "0e7b6a52-3c55-4a8e-b0a4-7c4d1f2e9a31",
"name": "محمد العتيبي",
"phone": "+966501234567",
"status": "جديد",
"budget": "1500000.00",
"created_at": "2026-10-01T09:15:01.902Z",
"updated_at": "2026-10-03T12:40:22.118Z"
}
],
"has_more": true,
"next_cursor": "WyIyMDI2LTEwLTAzVDEyOjQwOjIyLjExODQ1MVoiLCIwZTdi…",
"next": "/api/v1/leads?limit=100&cursor=WyIyMDI2…"
}A page has 100 rows by default and up to 1000 with limit. Pick columns with fields (comma-separated; id is always included). format=csv returns every row in one response, field names on the first line.
4. Incremental sync
- Rows come oldest change first. Fetch the first page, then pass next_cursor as cursor until has_more is false.
- Keep the last next_cursor. Next sync, send it and you get only what was created or changed after it. Every row comes whole, so upsert by id.
- Or use updated_since with your last sync time minus 5 minutes (to cover changes that commit late) and upsert by id.
- Deleted rows are not reported incrementally: do a full pull now and then (weekly, say).
5. Connect Power BI
- Power BI Desktop → Get data → Web.
- Paste a table's CSV URL, e.g. https://app.evacore.io/api/v1/leads?format=csv
- In the sign-in dialog choose Basic: any user name (e.g. api) and the key as the password. Apply it to https://app.evacore.io so every table uses the key.
- If the Arabic looks garbled: in the preview set File origin = 65001: Unicode (UTF-8).
- Repeat Web for each table you need (deals, properties, projects, units, reservations, activities, tasks, agents, teams) and relate them on the *_id fields.
- After publishing to the Power BI Service: Settings → Data source credentials → Basic with the same key, then turn on Scheduled refresh.
Excel works the same way: Data → From Web → the same URL → Basic.
Writes and retries
Use POST /api/v1/leads or POST /api/v1/properties to create; PATCH the same path with /{id} to update. Requires leads:write or properties:write; read never grants writes. Update a lead's status or assign a sales user in the same office, or update a property's details and status. See OpenAPI for allowed fields and values.
Send JSON with a unique Idempotency-Key per operation (1 to 128 printable ASCII characters). Retrying the same request and key returns the original response without repeating the mutation. A changed body or path returns 409. Retry 503 with the same key. Project-linked property status uses the reservation workflow; resolve active shared lead assignments in the office first.
Owners see daily counts per key for the last 30 Riyadh days on the API keys page. Every authenticated read, write, replay or refusal counts once. All share a 120/minute budget; CSV costs 10 budget units and counts as one request.
6. Limits and errors
| Status | Meaning |
|---|---|
| 400 | Invalid parameter (unknown field, date, cursor or limit). |
| 401 | Missing, unknown, revoked or expired key. |
| 403 | The key lacks this table's scope. |
| 404 | Unknown table. |
| 429 | Over 120 requests a minute for this key (a CSV export counts as 10). Wait the seconds in Retry-After. |
Every error has the shape: { "error": { "code": "…", "message": "…" } }. Writes require a separate scope and an Idempotency-Key.
7. Tables and fields
The full OpenAPI 3.1 specification: https://app.evacore.io/api/v1/openapi.json
GET /api/v1/leads
Leads (customers) of the office, every channel. Scope: leads:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Lead id. |
name | string | Name. |
phone | string | Phone, international format when known. |
email | string | Email. |
status | string | Pipeline status, as shown in the dashboard. |
source | string | Where the lead came from (whatsapp, bayut, website…). |
channel | string | Conversation channel. |
intent | string | Buy / rent intent. |
score | string | Lead score. |
property_type | string | Wanted property type. |
city | string | Wanted city. |
district | string | Wanted district. |
budget | SAR amount (string, 2 decimals) | Budget, SAR. |
budget_min | SAR amount (string, 2 decimals) | Budget lower bound, SAR. |
budget_max | SAR amount (string, 2 decimals) | Budget upper bound, SAR. |
bedrooms | integer | Wanted bedrooms. |
timeline | string | Purchase timeline. |
financing | string | Financing (cash / bank). |
tags | list of strings | Tags. |
agent_id | string | Assigned agent (agents.id). |
team_id | id (UUID) | Team (teams.id). |
project_id | id (UUID) | Project (projects.id). |
merged_into_lead_id | id (UUID) | Set when this lead was merged into another. |
opted_out | boolean | Asked not to be contacted. |
follow_up_at | UTC date-time (ISO 8601) | Next follow-up (UTC). |
first_contact_at | UTC date-time (ISO 8601) | First contact sent (UTC). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/deals
Deals (sale / rent opportunities) on leads. Scope: deals:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Deal id. |
lead_id | id (UUID) | Lead (leads.id). |
kind | string | Deal kind. |
title | string | Title. |
stage | string | Stage. |
value | SAR amount (string, 2 decimals) | Value, SAR. |
property_id | id (UUID) | Property (properties.id). |
unit_id | id (UUID) | Project unit (units.id). |
agent_id | string | Agent (agents.id). |
expected_close_date | date (YYYY-MM-DD) | Expected close date. |
won_at | UTC date-time (ISO 8601) | Won (UTC). |
lost_at | UTC date-time (ISO 8601) | Lost (UTC). |
lost_reason | string | Lost reason. |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/properties
Listings of the office. Scope: properties:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Property id. |
title | string | Title. |
description | string | Public description. |
type | string | Type (فيلا، شقة، أرض…). |
purpose | string | للبيع / للإيجار. |
status | string | Status (متاح، مباع، مؤجر، مؤرشف…). |
city | string | City. |
district | string | District. |
price | SAR amount (string, 2 decimals) | Price, SAR. |
rent_monthly | SAR amount (string, 2 decimals) | Monthly rent, SAR. |
area_m2 | number | Area, m². |
built_up_area_m2 | number | Built-up area, m². |
bedrooms | integer | Bedrooms. |
bathrooms | integer | Bathrooms. |
age_years | integer | Age, years. |
project_id | id (UUID) | Project (projects.id). |
agent_ids | list of strings | Agents (agents.id). |
ad_license_number | string | REGA ad licence number. |
external_source | string | Imported from (aqar, bayut…). |
external_ref | string | Id in that source. |
public_link | string | Public page. |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/projects
Off-plan / development projects. Scope: projects:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Project id. |
name | string | Name. |
developer | string | Developer. |
city | string | City. |
district | string | District. |
status | string | Status. |
delivery_date | date (YYYY-MM-DD) | Delivery date. |
wafi_number | string | Wafi licence. |
price_min | SAR amount (string, 2 decimals) | Lowest price, SAR. |
price_max | SAR amount (string, 2 decimals) | Highest price, SAR. |
description | string | Description. |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/units
Units of every project. Scope: projects:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Unit id. |
project_id | id (UUID) | Project (projects.id). |
property_id | id (UUID) | Linked listing (properties.id). |
code | string | Unit code. |
building | string | Building. |
floor | integer | Floor. |
model | string | Model. |
area_m2 | number | Area, m². |
built_up_area_m2 | number | Built-up area, m². |
bedrooms | integer | Bedrooms. |
bathrooms | integer | Bathrooms. |
price | SAR amount (string, 2 decimals) | Price, SAR. |
status | string | Status (available, held, reserved, sold…). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/reservations
Unit reservations and deposits. Scope: projects:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Reservation id. |
project_id | id (UUID) | Project (projects.id). |
unit_id | id (UUID) | Unit (units.id). |
lead_id | id (UUID) | Lead (leads.id). |
status | string | Status (held, paid, sold, released, expired, cancelled). |
deposit | SAR amount (string, 2 decimals) | Deposit, SAR. |
payment_mode | string | Payment mode. |
hold_expires_at | UTC date-time (ISO 8601) | Hold deadline (UTC). |
paid_at | UTC date-time (ISO 8601) | Deposit confirmed (UTC). |
created_by | string | Created by (agents.id). |
closed_by | string | Closed by (agents.id). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/activities
Calls / call-centre outcomes and lead status changes. Scope: activities:read. updated_since: rows created since (rows never change) the given time.
| Field | Type | Meaning |
|---|---|---|
id | string | Activity id (call:… or status:…). |
type | string | call or status_change. |
lead_id | id (UUID) | Lead (leads.id). |
user_id | string | Who did it (agents.id). |
action | string | Action. |
outcome | string | Call outcome. |
from_status | string | Previous status. |
to_status | string | New status. |
follow_up_at | UTC date-time (ISO 8601) | Follow-up set (UTC). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
GET /api/v1/tasks
Office tasks. Scope: activities:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Task id. |
title | string | Title. |
status | string | Status. |
due_at | UTC date-time (ISO 8601) | Due (UTC). |
lead_id | id (UUID) | Lead (leads.id). |
assignee_id | string | Assignee (agents.id). |
created_by | string | Created by (agents.id). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/agents
People of the office. Scope: agents:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | string | Agent id: what agent_id fields refer to. |
name | string | Name. |
email | string | Email. |
role | string | Role (org:admin = owner, org:member…). |
team_ids | list of strings | Teams (teams.id). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/teams
Sales teams. Scope: agents:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | id (UUID) | Team id. |
name | string | Name. |
region_id | id (UUID) | Region. |
leader_id | string | Leader (agents.id). |
member_ids | list of strings | Members (agents.id). |
created_at | UTC date-time (ISO 8601) | When it was created (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |
GET /api/v1/conversations
WhatsApp, Instagram and TikTok conversations. Counts and times only, never message text. Scope: conversations:read. updated_since: rows with a message since the given time.
| Field | Type | Meaning |
|---|---|---|
id | string | Conversation id (channel:contact). |
channel | string | whatsapp, instagram or tiktok. |
contact | string | Customer phone or handle. |
contact_name | string | Customer name as shown. |
lead_id | string | Lead (leads.id). |
message_count | integer | Messages. |
inbound_count | integer | From the customer. |
outbound_count | integer | From the office or Eva. |
first_message_at | UTC date-time (ISO 8601) | First message (UTC). |
last_message_at | UTC date-time (ISO 8601) | Last message (UTC): what updated_since compares. |
GET /api/v1/messages
Message text of every conversation. A deleted message keeps its row with text null. Scope: messages:read. updated_since: rows created or changed since the given time.
| Field | Type | Meaning |
|---|---|---|
id | string | Message id. |
conversation_id | string | Conversation (conversations.id). |
channel | string | whatsapp, instagram or tiktok. |
direction | string | inbound (customer) or outbound. |
sender | string | Who sent it (customer, eva, agent…). |
sent_by_user_id | string | Agent who sent it (agents.id). |
text | string | Message text, Arabic intact. |
media_type | string | Media type, if any. |
lead_id | string | Lead (leads.id). |
created_at | UTC date-time (ISO 8601) | Sent (UTC). |
updated_at | UTC date-time (ISO 8601) | When it last changed (UTC). What updated_since compares. |