API Documentation

REST API for automation, integrations, and AI agents

Introduction

The Moj Web API provides programmatic access to manage your businesses, websites, media, calendar, menu items, and inquiries. It is designed for:

  • Automation - Sync availability calendars, update menus automatically
  • Integrations - Connect with booking platforms, POS systems, CRMs
  • AI Agents - Enable AI assistants to manage your web presence

The base URL for all API requests is:

https://moj-web.com

All endpoints documented below are under /api/v1, accept and return JSON unless noted otherwise, and only expose resources accessible to the API key owner.

Authentication

All API requests require authentication using a Bearer token. You can create and manage API keys from your dashboard.

Getting an API Key

  1. Log in to your dashboard
  2. Open Settings > API Keys
  3. Click "Create API Key"
  4. Select the scopes (permissions) you need
  5. Copy and securely store the generated key

Using Your API Key

Include the API key in the Authorization header of every request:

curl -H "Authorization: Bearer mw_live_your_api_key_here" \
  https://moj-web.com/api/v1/businesses

The scheme must be exactly Bearer. Missing, malformed, invalid, or expired keys return 401; a valid key without the required scope returns 403.

Key Prefixes

PrefixEnvironmentDescription
mw_live_LivePrefix used by keys created in the dashboard
mw_test_TestAccepted format for test keys when issued

The prefix identifies the key format; it does not select a separate API base URL or sandbox. Treat every key as a secret. The full value is shown only when the key is created.

Scopes

API keys have scopes that define what operations they can perform. Request only the scopes your application needs for better security.

ScopeDescription
business:readRead business information
business:writeCreate and update businesses
business:deleteDelete businesses
website:readRead website content
website:writeUpdate website content and theme
website:publishPublish and unpublish websites
media:readList media files
media:writeUpload media files
media:deleteDelete media files
calendar:readRead availability calendar
calendar:writeManage blocked dates
menu:readRead menu items
menu:writeCreate and update menu items
menu:deleteDelete menu items
inquiry:readRead inquiries
inquiry:writeCreate inquiries and update inquiry status

The dashboard issues keys with one or more of the listed scopes.

Endpoints Reference

Businesses

GET/api/v1/businesses
business:read

List accessible businesses

Query parameters

  • type — optional: APARTMENT, VILLA, RESTAURANT, CAFE, or ACTIVITY_VENUE
  • limit — default 50, maximum 100
  • offset — default 0

Success

  • 200 — { businesses, pagination: { total, limit, offset, hasMore } }
POST/api/v1/businesses
business:write

Create a business

Request body

  • Required: type (APARTMENT | VILLA | RESTAURANT | CAFE | ACTIVITY_VENUE), name (at least 2 characters)
  • Optional: description, address, city, country (default: Hrvatska), phone, email, amenities (string[])

Success

  • 201 — { business }

Endpoint-specific errors

  • 400 — invalid body
GET/api/v1/businesses/:id
business:read

Get business details and resource counts

Success

  • 200 — { business }

Endpoint-specific errors

  • 404 — business not found or not accessible
PATCH/api/v1/businesses/:id
business:write

Update a business

Request body

  • Any POST business field; only supplied fields are updated

Success

  • 200 — { business }

Endpoint-specific errors

  • 400 — invalid body
  • 404 — business not found or not accessible
DELETE/api/v1/businesses/:id
business:delete

Delete a business and its stored files

Success

  • 200 — { deleted: true }

Endpoint-specific errors

  • 404 — business not found or not accessible

Websites

GET/api/v1/websites
website:read

List accessible websites

Query parameters

  • isPublished — optional: true or false
  • page — default 1, minimum 1
  • limit — default 50, range 1–100

Success

  • 200 — { websites, pagination: { page, limit, totalCount, totalPages } }
GET/api/v1/websites/:id
website:read

Get website content, theme, and business details

Success

  • 200 — { website }

Endpoint-specific errors

  • 404 — website not found or not accessible
PATCH/api/v1/websites/:id
website:write

Update content or theme

Request body

  • At least one of: content (JSON object), themeId (string), themeConfig (JSON object)
  • String values inside content are sanitized before storage

Success

  • 200 — { website }

Endpoint-specific errors

  • 400 — invalid JSON, invalid fields, or no fields to update
  • 404 — website not found or not accessible
POST/api/v1/websites/:id/publish
website:publish

Publish a website

Success

  • 200 — { website: { id, subdomain, isPublished, publishedAt } }

Endpoint-specific errors

  • 400 — website is already published
  • 404 — website not found or not accessible
DELETE/api/v1/websites/:id/publish
website:publish

Unpublish a website

Success

  • 200 — { website: { id, subdomain, isPublished, publishedAt } }

Endpoint-specific errors

  • 400 — website is not published
  • 404 — website not found or not accessible

Media

GET/api/v1/media
media:read

List media using cursor pagination

Query parameters

  • businessId — optional accessible business filter
  • cursor — optional media ID returned as pagination.nextCursor
  • limit — default 50, maximum 100

Success

  • 200 — { media, pagination: { hasMore, nextCursor } }

Endpoint-specific errors

  • 404 — business filter is not accessible
POST/api/v1/media
media:write

Upload and process an image

Request body

  • multipart/form-data: file (required) and businessId (required)
  • Accepted input: JPG, PNG, or WebP; maximum 10 MB. Stored output is WebP.

Success

  • 201 — { media }

Endpoint-specific errors

  • 400 — missing, unsupported, oversized, or unprocessable file
  • 404 — business not found or not accessible
GET/api/v1/media/:id
media:read

Get media details

Success

  • 200 — { media }

Endpoint-specific errors

  • 404 — media not found or not accessible
DELETE/api/v1/media/:id
media:delete

Delete unused media

Success

  • 200 — { deleted: true }

Endpoint-specific errors

  • 404 — media not found or not accessible
  • 409 — media is still referenced; response includes usage details

Calendar

GET/api/v1/calendar
calendar:read

List calendar blocks overlapping a date range

Query parameters

  • One of businessId or unitId is required; businessId returns business-wide blocks only
  • unitId returns blocks for that unit plus business-wide blocks
  • startDate and endDate — optional YYYY-MM-DD boundaries; endDate is exclusive

Success

  • 200 — { blockedDates }

Endpoint-specific errors

  • 400 — missing target, invalid date, or invalid range
  • 404 — business or unit not found
POST/api/v1/calendar
calendar:write

Create a calendar block

Request body

  • Required: businessId or unitId, startDate, endDate (YYYY-MM-DD; endDate is exclusive)
  • Optional: reason, guestName, note (string or null)

Success

  • 201 — { blockedDate }

Endpoint-specific errors

  • 400 — invalid body or date range
  • 404 — business or unit not found
  • 409 — range overlaps a calendar block or reservation
GET/api/v1/calendar/:id
calendar:read

Get a calendar block

Success

  • 200 — { blockedDate }

Endpoint-specific errors

  • 404 — blocked date not found or not accessible
PATCH/api/v1/calendar/:id
calendar:write

Update a calendar block

Request body

  • Any of startDate, endDate (YYYY-MM-DD), reason, guestName, or note; the target business/unit cannot be changed

Success

  • 200 — { blockedDate }

Endpoint-specific errors

  • 400 — invalid JSON or date range
  • 404 — blocked date not found or not accessible
  • 409 — range overlaps a calendar block or reservation
DELETE/api/v1/calendar/:id
calendar:write

Delete a calendar block

Success

  • 200 — { success: true }

Endpoint-specific errors

  • 404 — blocked date not found or not accessible

Inquiries

GET/api/v1/inquiries
inquiry:read

List accessible inquiries

Query parameters

  • websiteId — optional accessible website filter
  • status — optional: NEW, CONTACTED, ANSWERED, or ARCHIVED
  • limit — default 50, maximum 100
  • offset — default 0

Success

  • 200 — { inquiries, pagination: { total, limit, offset, hasMore } }
POST/api/v1/inquiries
inquiry:write

Create an inquiry with optional metadata

Request body

  • Required: websiteId, name, email, message
  • Optional: phone, checkIn, checkOut (ISO 8601 strings), guests (positive integer), metadata (JSON object)

Success

  • 201 — { inquiry }

Endpoint-specific errors

  • 400 — invalid JSON or fields
  • 404 — website not found or not accessible
GET/api/v1/inquiries/:id
inquiry:read

Get inquiry details

Success

  • 200 — { inquiry }

Endpoint-specific errors

  • 404 — inquiry not found or not accessible
PATCH/api/v1/inquiries/:id
inquiry:write

Update inquiry workflow status

Request body

  • Required: status — NEW, CONTACTED, ANSWERED, or ARCHIVED

Success

  • 200 — { inquiry }

Endpoint-specific errors

  • 400 — invalid JSON or status
  • 404 — inquiry not found or not accessible

Example Requests

List your businesses

curl -X GET "https://moj-web.com/api/v1/businesses" \
  -H "Authorization: Bearer mw_live_your_api_key"

Response:

{
  "businesses": [
    {
      "id": "clx1234567890",
      "type": "APARTMENT",
      "name": "Apartments Sunset",
      "description": "Beautiful seaside apartments",
      "address": "Obala 123",
      "city": "Split",
      "country": "Croatia",
      "phone": "+385 21 123 456",
      "website": {
        "id": "clx0987654321",
        "subdomain": "apartments-sunset",
        "isPublished": true
      }
    }
  ],
  "pagination": {
    "total": 1,
    "limit": 50,
    "offset": 0,
    "hasMore": false
  }
}

Create a business

curl -X POST "https://moj-web.com/api/v1/businesses" \
  -H "Authorization: Bearer mw_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "RESTAURANT",
    "name": "Konoba Mare",
    "description": "Traditional Dalmatian cuisine",
    "address": "Riva 45",
    "city": "Dubrovnik",
    "country": "Croatia",
    "phone": "+385 20 321 654"
  }'

List calendar blocks

curl -X GET "https://moj-web.com/api/v1/calendar?unitId=clx_unit_id&startDate=2026-07-01&endDate=2026-09-01" \
  -H "Authorization: Bearer mw_live_your_api_key"

Block dates in calendar

curl -X POST "https://moj-web.com/api/v1/calendar" \
  -H "Authorization: Bearer mw_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "unitId": "clx_unit_id",
    "startDate": "2026-07-15",
    "endDate": "2026-07-22",
    "reason": "BOOKING",
    "guestName": "John Smith"
  }'

Upload media

curl -X POST "https://moj-web.com/api/v1/media" \
  -H "Authorization: Bearer mw_live_your_api_key" \
  -F "businessId=clx1234567890" \
  -F "file=@/path/to/image.jpg"

Add menu item

curl -X POST "https://moj-web.com/api/v1/menu" \
  -H "Authorization: Bearer mw_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "businessId": "clx1234567890",
    "name": "Grilled Sea Bass",
    "description": "Fresh daily catch with seasonal vegetables",
    "price": 25.00,
    "category": "Main Courses",
    "isAvailable": true
  }'

Create inquiry with metadata

curl -X POST "https://moj-web.com/api/v1/inquiries" \
  -H "Authorization: Bearer mw_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "websiteId": "clx_website_id",
    "name": "Vila Sunset",
    "email": "[email protected]",
    "phone": "+385 91 123 4567",
    "message": "Lead from Booking.com scraper",
    "metadata": {
      "source": "booking-scraper",
      "booking_url": "https://booking.com/hotel/...",
      "rating": 9.2,
      "lead_score": 85
    }
  }'

Update inquiry status

Supported statuses: NEW, CONTACTED, ANSWERED, ARCHIVED.

curl -X PATCH "https://moj-web.com/api/v1/inquiries/clx_inquiry_id" \
  -H "Authorization: Bearer mw_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"status": "ANSWERED"}'

Publish a website

curl -X POST "https://moj-web.com/api/v1/websites/clx_website_id/publish" \
  -H "Authorization: Bearer mw_live_your_api_key"

List only published websites

curl -X GET "https://moj-web.com/api/v1/websites?isPublished=true" \
  -H "Authorization: Bearer mw_live_your_api_key"

Error Handling

The API uses standard HTTP status codes to indicate success or failure:

Status CodeMeaning
200Success
201Created - Resource created successfully
400Bad Request - Invalid parameters or request body
401Unauthorized - Missing or invalid API key
403Forbidden - Insufficient scope permissions
404Not Found - Resource does not exist
409Conflict - Date overlap or media is still in use
429Too Many Requests - API key rate limit exceeded
500Internal Server Error - Something went wrong

Error responses include a JSON body with an error field:

{
  "error": "Missing Authorization header"
}
{
  "error": "Insufficient permissions. Required: business:write"
}
{
  "error": "Business not found"
}

Rate Limiting

API requests are rate limited to ensure fair usage and maintain service quality. Rate limits are configurable per API key.

Newly created keys default to 1000 requests per 60-second window. The limit is enforced independently for each API key and may be configured differently for an individual key.

When the limit is exceeded, the API returns 429 Too Many Requests with this body:

{
  "error": "Rate limit exceeded"
}

Rate-limit counters or reset timestamps are not currently included in response headers. Retry later; the API does not currently report the exact reset time.

Need help? Contact support or check our guides in the dashboard.