Skip to the article
Aniq-UI

VelraAPI reference

API reference

Every route the API serves, who may call it, and how sign-in, permissions, errors, lists and limits work.

For the Full Stack package

Base URL and response format

Every route is served under the /api prefix. Locally the base URL is http://localhost:8000/api. On a server it is your API's address followed by /api, the same value both frontends read from NEXT_PUBLIC_API_BASE_URL (Environment variables). Photos are served outside the prefix, at /media/….

The website's routes are public; the dashboard's need an admin's bearer token, and most also a permission. Request bodies are JSON, except file uploads. Every answer comes in the same envelope:

Envelope
{
  "success": true,
  "data": { },
  "message": ""
}

data holds the result. message is a short sentence some writes fill in, in the request's language, and empty otherwise. Prices are decimal numbers in the property's currency.

The health check needs no token:

Terminal
curl http://localhost:8000/api/health
Response
{"success":true,"data":{"status":"ok","database":"up"},"message":""}

When the database cannot be read it still answers 200, with "status":"degraded" and "database":"down". The Docker image's health check reads that field.

Errors

A failure keeps the envelope, with success: false and the HTTP status:

Error
{
  "success": false,
  "data": null,
  "message": "…",
  "errors": { "email": ["…"] }
}
  • message is a sentence in the request's language.
  • errors appears on a validation failure, with the problems per field, keyed by the path the client sent (such as title.en). An unknown field in a body is refused.
  • A guarded route with no token, or an expired one, answers 401; a token without the permission answers 403.
  • A request over a limit answers 429.

Lists and paging

A list
{ "data": [ ], "page": 1, "limit": 12, "total": 15, "totalPages": 2 }
  • Properties, inquiries and viewings page with page and page_size: 12 a page by default on the website (at most 24), 10 in the dashboard (at most 50). The dashboard's lists also return their counts per status.
  • Admins, roles and settings page with page and page_count.
  • Unless the route has its own order, rows come newest-updated first, with the id as a tiebreaker, so paging is stable.
  • The website's property filters are validated: an unknown value is refused with 400 rather than ignored, while a key the API does not read is ignored, so a shared link with extra parameters still works.

Language

Send Accept-Language: ar for Arabic messages; anything else answers in English. Properties and neighborhoods carry both languages as { "en": "…", "ar": "…" } whatever the header says, and the client picks one.

Signing in

  1. Sign in as an admin

    Terminal
    curl -X POST http://localhost:8000/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{"email":"admin@velra.example","password":"Admin@123"}'

    data.access_token is the token, with token_type Bearer and the admin with their roles and permissions. The token lasts JWT_EXPIRATION, 7d by default, and expires_in reports that value as written. GET /api/auth/me returns the signed-in admin with their roles and permissions.

  2. Call a protected route

    Terminal
    curl http://localhost:8000/api/leads -H "Authorization: Bearer YOUR_TOKEN"
  • An unknown email and a wrong password both answer 401 with the same message.
  • Ten failed sign-ins from one address within 15 minutes answer 429 until the oldest failure is 15 minutes old (RATE_LIMIT_LOGIN). Successful sign-ins are not counted.
  • There is no sign-up and no sign-out route: accounts come from the seed or from Admin management, and a client signs out by forgetting its token.
MethodPathWho can call itWhat it does
POST/api/auth/loginAnyoneAdmin sign-in: email, password
GET/api/auth/meAny signed-in adminThe signed-in admin, their roles and permissions

Permissions

Dashboard routes check a permission named <area>.<action>. An admin holds every permission of every role they have, and a route that names several accepts any one of them. The 31 permissions and the two seeded roles are listed in Admins, roles and permissions.

Properties on the website

MethodPathWho can call itWhat it does
GET/api/propertiesAnyonePublished homes. purpose (sale, rent), type (apartment, villa, townhouse), neighborhood (slug), min_price, max_price, bedrooms (0 for studios, otherwise a minimum), amenities (comma separated slugs, all required), q (title or reference), sort (newest, price_asc, price_desc), page, page_size
GET/api/properties/featuredAnyoneThe home page's featured homes: the references in the home_featured_references setting, in order, or published homes marked featured when none of those is published
GET/api/properties/facetsAnyonePublished homes counted by purpose, by type and by neighborhood, for the filters and the home page
GET/api/properties/lookupAnyonePublished homes by id, for saved homes and compare: ?ids=1,2,3, at most 12
GET/api/properties/:slug/similarAnyoneUp to three similar published homes
GET/api/properties/:slugAnyoneOne published home in full, with its gallery, its neighborhood and its public staged room

Neighborhoods and the agency's settings

MethodPathWho can call itWhat it does
GET/api/neighborhoodsAnyoneThe neighborhoods marked visible, in their sort order
GET/api/settings/publicAnyoneThe settings the website renders: agency_name, the taglines and descriptions in both languages, contact_email, agency_city, currency, timezone, home_featured_references and home_staging_property_slug

The contact form and viewing requests

MethodPathWho can call itWhat it does
POST/api/leadsAnyoneThe contact form: name, email, intent (buy, rent, other), client_token (a UUID v4), and optionally phone, preferred_areas (slugs), budget, message (1000 characters), property_id. Answers 201 with { id, received_at }
POST/api/viewingsAnyoneA viewing request: property_id (a published home), name, email, preferred_date (YYYY-MM-DD), preferred_time (HH:MM, agency time), client_token, and optionally phone, message. Answers 201 with { reference, status, preferred_at, timezone }
  • A repeated client_token returns the first submission's receipt instead of storing a second one.
  • A filled company field is treated as a bot: the answer looks the same, and nothing is stored.
  • A viewing must start at least an hour ahead, in Asia/Dubai time; sooner answers 400 with preferred_time_too_soon on preferred_at.
  • Each address may send five of each in ten minutes; the next answers 429.

Managing properties

MethodPathWho can call itWhat it does
GET/api/properties/manageAdmins with properties.viewEvery property, drafts included. purpose, type, status, q, page, page_size; returns counts of all, published and drafts
GET/api/properties/manage/:idAdmins with properties.viewOne property in full, with its gallery and staging pairs
POST/api/properties/manageAdmins with properties.createCreate a draft. Required: title (en, ar), purpose, type, price, bedrooms, bathrooms, area_sqm
PUT/api/properties/manage/:idAdmins with properties.editChange any field, the gallery included (media, up to 40 items)
PATCH/api/properties/manage/:id/statusAdmins with properties.publishSet status: draft, published, sold, rented or archived
PUT/api/properties/manage/:id/mediaAdmins with properties.editReplace the gallery, in order, with one item marked is_cover
DELETE/api/properties/manage/:idAdmins with properties.deleteDelete the property

Inquiries and viewings

MethodPathWho can call itWhat it does
GET/api/leadsAdmins with leads.viewInquiries. status, q, received_within_days, page, page_size; with counts per status
GET/api/leads/:idAdmins with leads.viewOne inquiry with its property, notes and viewing
PATCH/api/leads/:idAdmins with leads.editSet status (new, contacted, qualified, closed) and notes
GET/api/viewingsAdmins with viewings.viewViewings. status, q, scheduled_from, scheduled_to, page, page_size; with counts per status
GET/api/viewings/:idAdmins with viewings.viewOne viewing with its inquiry and property
PATCH/api/viewings/:idAdmins with viewings.editaction (confirm, complete, cancel), confirmed_at (ISO 8601) and admin_notes. An action from the wrong status answers 400
GET/api/overviewAdmins with overview.viewThe overview: published counts, new inquiries, viewings awaiting confirmation and coming up, recent rows, featured homes and the map's points

Neighborhoods and settings

MethodPathWho can call itWhat it does
GET/api/neighborhoods/manageAdmins with neighborhoods.viewEvery neighborhood, hidden ones included
POST/api/neighborhoods/manageAdmins with neighborhoods.createAdd one: name (en, ar), and optionally slug, short_description, city, is_featured, sort_order, is_active, cover_url, cover_alt
PUT/api/neighborhoods/manage/:idAdmins with neighborhoods.editChange one
DELETE/api/neighborhoods/manage/:idAdmins with neighborhoods.deleteDelete one; its homes keep existing without a neighborhood
GET/api/settingsAdmins with settings.viewEvery setting. search, category, page, page_count
GET/api/settings/:keyAdmins with settings.viewOne setting by key
PATCH/api/settings/:keyAdmins with settings.editChange one setting's value
DELETE/api/settings/:keyAdmins with settings.editDelete one setting

Admins and roles

MethodPathWho can call itWhat it does
GET/api/adminsAdmins with admins.viewAdmins. email, name, phone, page, page_count
GET/api/admins/statisticsAdmins with admins.viewCounts for the admin list
GET/api/admins/:idAdmins with admins.viewOne admin, by id or username
POST/api/adminsAdmins with admins.createAdd an admin: first_name, last_name, email, password and password_confirmation (6 characters or more), optionally phone
PATCH/api/admins/:idAdmins with admins.editChange an admin
PATCH/api/admins/:id/rolesAdmins with admins.assign_rolesSet an admin's roles: role_ids
DELETE/api/admins/:idAdmins with admins.deleteDelete an admin
PATCH/api/admins/profileAny signed-in adminChange the signed-in admin's own first_name, last_name, email, phone, profile_picture or country_id. A password or role_ids in the body answers 400: the password has its own route
PATCH/api/admins/profile/passwordAny signed-in adminChange the signed-in admin's own password: current_password, password, password_confirmation
GET/api/rolesAdmins with roles.viewRoles. name, guard_name, created_from, created_to, page, page_count
GET/api/roles/statisticsAdmins with roles.viewCounts for the roles list
GET/api/roles/selectAdmins with roles.viewEvery role's id and name, for a picker
GET/api/roles/permissionsAdmins with roles.viewEvery permission, grouped by area
GET/api/roles/:idAdmins with roles.viewOne role with its permissions
POST/api/rolesAdmins with roles.createAdd a role
PUT/api/roles/:idAdmins with roles.editRename or describe a role
POST/api/roles/:id/permissionsAdmins with roles.assign_permissionsSet a role's permissions
DELETE/api/roles/:idAdmins with roles.deleteDelete a role

Notifications and uploads

MethodPathWho can call itWhat it does
GET/api/notificationsAny signed-in adminThe signed-in admin's newest 30 notifications
PATCH/api/notifications/read-allAny signed-in adminMark them all read
PATCH/api/notifications/:id/readAny signed-in adminMark one read
DELETE/api/notifications/:idAny signed-in adminRemove one
POST/api/helpers/uploadAny signed-in adminStore a file (multipart file, with path for the folder, for for the size preset such as property, and type). Photos are re-encoded, at most 1920 pixels wide; videos are accepted as MP4, WebM, MOV or M4V; 150 MB at most. Answers the stored file's address

An upload has no permission of its own: what the file becomes is checked by the route that saves it. Notifications are kept for 30 days.

Limits per address

RouteLimitVariable
POST /api/auth/login10 failed sign-ins per 15 minutesRATE_LIMIT_LOGIN
POST /api/leads5 per 10 minutesNone
POST /api/viewings5 per 10 minutesNone

A request over a limit answers 429. The counts live in the API's memory, so a restart clears them. Behind a reverse proxy, set TRUST_PROXY so each visitor is counted on their own address.

AI assistant and its conversations

Included with your purchase. Sign in to read, or open it in your download.

The assistant's streaming route, its model list and the saved conversations.

Staging studio

Included with your purchase. Sign in to read, or open it in your download.

The studio's routes: generations, saving a staged pair and approving it.

MCP server

Included with your purchase. Sign in to read, or open it in your download.

The MCP endpoint for coding agents and how it is authorised.

Live updates over WebSocket

Included with your purchase. Sign in to read, or open it in your download.

The Socket.IO namespaces the dashboard listens on, their events and how a connection is authorised.

Demo mode route

Included with your purchase. Sign in to read, or open it in your download.

The route that exists only on a public demo build.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.