Public API

Prospects Import API

Push potential-owner leads into the system from external sources (landing pages, CRMs, lead aggregators). Single endpoint, JSON in / JSON out, Bearer-token auth, idempotent — duplicates return the existing record instead of erroring.

Endpoint

POSThttps://aperia.app/api/public/prospects

Content-Type: application/json · CORS: *

Authentication

Pass your API token in the Authorization header using the Bearer scheme. Alternatively send it in the X-Api-Token header.

Authorization: Bearer YOUR_API_TOKEN

The token is issued by the platform administrator. Keep it secret — never expose it in client-side code.

Request body

FieldTypeRequiredDescription
namestring (1–200)requiredContact / property name.
bodystring (≤5000)optionalFree-form description.
addressstring (≤500)optionalPostal address.
countrystring (ISO 3166-1 alpha-2)optionalCountry code, e.g. "HR", "CZ".
languagestring (ISO 639-1)optionalLanguage code, e.g. "en", "cs".
wwwstring (URL)optionalWebsite URL.
emailstring (email)optionalContact email.
phonestring (≤30)optionalPrimary phone.
phone_secstring (≤30)optionalSecondary phone.
whats_appstring (≤30)optionalWhatsApp number.
capacityinteger ≥ 0optionalNumber of guests / sleeping places.
validityinteger (0–5)optionalInfo validity rating (stars, integer 0–5).
categorystring (slug)optionalProperty category slug from the admin-managed list. Default: apartment.
noticestring (≤2000)optionalInternal note.
image_urlstring (URL, ≤1000)optionalURL of the main photo.

Deduplication

Each prospect gets a dedup_key computed in this priority order:

  1. host:<hostname> — derived from www (without protocol / www. prefix)
  2. email:<lowercased email>
  3. name:<lowercased name>|<country>

If a prospect with the same key already exists, the endpoint returns 200 OK with the existing record and deduplicated: true — no duplicate is created. New prospects return 201 Created with deduplicated: false.

Example — cURL

curl -X POST https://aperia.app/api/public/prospects \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Villa Marina",
    "email": "owner@example.com",
    "country": "HR",
    "language": "en",
    "category": "villa-luxury",
    "capacity": 6,
    "validity": 4,
    "www": "https://villa-marina.example",
    "image_url": "https://cdn.example.com/villa-marina/cover.jpg",
    "notice": "Lead from landing page"
  }'

Example — JavaScript (fetch)

await fetch("https://aperia.app/api/public/prospects", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Villa Marina",
    email: "owner@example.com",
    country: "HR",
    language: "en",
    category: "villa-luxury",
    capacity: 6,
    validity: 4,
    image_url: "https://cdn.example.com/villa-marina/cover.jpg",
  }),
});

OpenAPI 3.1 specification (YAML)

Drop the snippet below into Swagger UI, Postman, Insomnia, or any OpenAPI-compatible tool to generate clients, mocks or interactive docs.

openapi: 3.1.0
info:
  title: Prospects Import API
  version: "1.1.1"
  description: Public REST API for importing prospect leads. Idempotent — duplicates return the existing record.
servers:
  - url: https://aperia.app
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
    apiTokenHeader:
      type: apiKey
      in: header
      name: X-Api-Token
  schemas:
    Prospect:
      type: object
      required: [name]
      properties:
        name:       { type: string, minLength: 1, maxLength: 200 }
        body:       { type: string, maxLength: 5000, nullable: true }
        address:    { type: string, maxLength: 500, nullable: true }
        country:    { type: string, minLength: 2, maxLength: 2, description: "ISO 3166-1 alpha-2", nullable: true }
        language:   { type: string, minLength: 2, maxLength: 2, description: "ISO 639-1", nullable: true }
        www:        { type: string, format: uri, maxLength: 500, nullable: true }
        email:      { type: string, format: email, maxLength: 255, nullable: true }
        phone:      { type: string, maxLength: 30, nullable: true }
        phone_sec:  { type: string, maxLength: 30, nullable: true }
        whats_app:  { type: string, maxLength: 30, nullable: true }
        capacity:   { type: integer, minimum: 0, maximum: 100000, nullable: true }
        validity:   { type: integer, minimum: 0, maximum: 5, nullable: true, description: "Info validity rating (stars)" }
        category:
          type: string
          description: "Category slug from the admin-managed list of property categories"
          default: apartment
        notice:     { type: string, maxLength: 2000, nullable: true }
        image_url:  { type: string, format: uri, maxLength: 1000, nullable: true, description: "URL of the main photo" }
    ProspectResult:
      type: object
      required: [id, date_created, deduplicated]
      properties:
        id:           { type: string, format: uuid }
        date_created: { type: string, format: date-time }
        deduplicated: { type: boolean, description: "true = existing record returned, false = newly created" }
    Error:
      type: object
      required: [error]
      properties:
        error:   { type: string }
        details: { type: object, additionalProperties: true }
security:
  - bearerAuth: []
  - apiTokenHeader: []
paths:
  /api/public/prospects:
    post:
      summary: Create or deduplicate a prospect
      operationId: createProspect
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Prospect" }
      responses:
        "200":
          description: Existing prospect returned (deduplicated)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProspectResult" }
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProspectResult" }
        "400":
          description: Validation failed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "401":
          description: Unauthorized
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "500":
          description: Internal error
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }

Responses

201 Created — new prospect inserted

HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "0f9e1c5a-1b2d-4a3e-9c44-7e2d1a3b5c6d",
  "date_created": "2026-05-05T12:34:56.000Z",
  "deduplicated": false
}

200 OK — existing prospect returned (deduplicated)

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "0f9e1c5a-1b2d-4a3e-9c44-7e2d1a3b5c6d",
  "date_created": "2026-05-05T12:34:56.000Z",
  "deduplicated": true
}

400 Bad Request — validation failed

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "validation_failed",
  "details": {
    "fieldErrors": { "email": ["Invalid email"] },
    "formErrors": []
  }
}

401 Unauthorized — missing or invalid token

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{ "error": "unauthorized" }

500 Internal Server Error

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{ "error": "internal_error" }

Allowed category values

Use any active slug from the admin Property categories list:

Loading…

Default if omitted or unknown: apartment.