Skip to main content
REST · JSON · OpenAPI 3.1

Carzle Dealer API

Sync your showroom inventory from any external system - dealer website, CRM, DMS, or a custom script. One HTTPS endpoint, one Bearer token, JSON in and out.

Quick start

  1. 1

    Create a key

    Go to Dashboard → API Access, name your integration, pick the scopes you need (start with listings:read + listings:write), and copy the key. The plaintext is shown once - if you lose it, revoke and create a new one.

  2. 2

    Call /me to verify

    curl https://carzle.ae/api/v1/me \
      -H "Authorization: Bearer cz_live_..."
  3. 3

    Load reference data

    Enum values, makes + models, feature tags. Cache on your side - changes rarely.

    curl https://carzle.ae/api/v1/metadata \
      -H "Authorization: Bearer cz_live_..."
  4. 4

    Create a listing

    curl -X POST https://carzle.ae/api/v1/listings \
      -H "Authorization: Bearer cz_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "externalRef": "your-cms-id-123",
        "status": "active",
        "make": "Toyota",
        "model": "Land Cruiser",
        "year": 2024,
        "title": "2024 Toyota Land Cruiser GR Sport",
        "price": 385000,
        "kilometers": 12000,
        "imageUrls": [
          { "url": "https://your-site.com/photos/1.jpg", "isHero": true },
          { "url": "https://your-site.com/photos/2.jpg" }
        ]
      }'
  5. 5

    Sync incrementally

    Use updatedSince to pull only what changed since your last sync. Update by id or by your own external reference.

    curl "https://carzle.ae/api/v1/listings?updatedSince=2026-04-01T00:00:00Z" \
      -H "Authorization: Bearer cz_live_..."
    
    # Update by your own ref (no need to remember Carzle's id)
    curl -X PATCH https://carzle.ae/api/v1/listings/by-ref/your-cms-id-123 \
      -H "Authorization: Bearer cz_live_..." \
      -H "Content-Type: application/json" \
      -d '{ "price": 379000 }'

Safe by default

Bearer auth with scoped keys, IP allowlists, auto-pause on anomalous traffic, and SSRF-hardened image ingestion. A leaked key can't move money or take destructive action outside its scopes.

Fast incremental sync

updatedSince + pagination give you cheap deltas. Most dealers sync with < 5 requests per run.

OpenAPI first

Import the spec into Postman, Insomnia, or any codegen tool and get typed clients in minutes.

Endpoints

MethodPathScopePurpose
GET/meanyAuth check + quota snapshot
GET/metadataanyEnums, makes+models, features
GET/listingslistings:readList your listings (filter + paginate)
POST/listingslistings:writeCreate a listing
GET/listings/{id}listings:readGet one listing
PATCH/listings/{id}listings:writeUpdate one listing
DELETE/listings/{id}listings:deleteSoft-delete
GET/listings/by-ref/{ref}listings:readGet by your own ref
PATCH/listings/by-ref/{ref}listings:writeUpdate by your own ref

Rate limits

  • 60 read requests / minute / key
  • 30 write requests / minute / key
  • Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • 429 response on breach - back off and retry after the reset window.

Errors

Every error response has the same envelope. Include requestId when contacting support.

{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "fields": {
      "price": "must be less than 50000000",
      "imageUrls.0": "Source download failed: HTTP 404"
    },
    "requestId": "req_01H..."
  }
}

FAQ for plugin / integration devs

Real questions from dealer-side devs building their first Carzle integration. If your question isn't here, call or WhatsApp +971 58 587 9282.

The dealer already has listings on Carzle (created via the dashboard). Should they delete all and start fresh via API?+

No - adopt them. Deleting loses public URLs, SEO, view counts, and lead history. Every existing listing has an empty externalRef. To take it over:

  1. GET /listings to fetch all existing Carzle listings with their UUIDs.
  2. Match each to a record in your source system (by VIN, or by title + year + price). One-time, usually in a spreadsheet.
  3. For each match: PATCH /listings/{uuid} with { "externalRef": "your-id" }.
  4. From then on, sync by-ref: PATCH /listings/by-ref/{ref}.
What should externalRef be - WordPress post ID, VIN/chassis, SKU, something else?+

Dealer's choice. externalRef is an opaque string up to 255 chars, unique per showroom. In priority order:

  1. SKU / stock number - cleanest, survives site rebuilds.
  2. WordPress post ID - always present, typo-proof.
  3. Chassis / VIN - last resort. Risk of typos; and if a sold car comes back with a new post, using VIN would PATCH the old sold listing back to active.

Prefix it so it's self-documenting (e.g. p1m_sku_1234). Pick one scheme and stick to it - don't mix SKU for some and post-ID for others.

You can also send the chassis number separately via chassisNumber - it's stored for display/audit but not used for sync.

What status values does the API accept?+

Six dealer-settable statuses: active, inactive, sold, coming_soon, reserved, draft.

How should WordPress post status map to Carzle status?+

Recommended mapping:

WordPressAPI callCarzle status
publishPOST / PATCHactive
draftPATCH by-refinactive
trashDELETE by-ref(soft-deleted)

Don't send draft from a "car removed from site" flow - on Carzle, draft means "never published yet." Use inactive for "was live, now hidden."

To expose sold, reserved, or coming_soon, add a custom carzle_status ACF dropdown on the car post type and send its value when post_status is publish.

What's the base URL? Is there a sandbox / staging?+

Base URL: https://carzle.ae/api/v1

We'll move to api.carzle.ae when traffic justifies it - make the base URL configurable in your plugin so it becomes a one-line change, not a release.

No sandbox today - single production environment. Safe dev pattern: create a 7-day API key, test the full create / patch / delete cycle against one real car, then revoke and create a long-lived key for prod. Need an isolated test env? Ask support - we can stand one up if the integration scale justifies it.

What sync triggers should a WordPress plugin use?+

Three-trigger model works well:

  1. Auto-sync on save_post - filter to the car post type, skip autosaves + revisions. Debounce with Action Scheduler or wp_schedule_single_event at +10-15 seconds so multiple saves collapse into one API call. Don't use a raw sleep().
  2. Daily reconciliation cron - NOT a brute-force push-everything. Call GET /listings?updatedSince=<last_sync>, compare to WP, re-push anything missing / stale. This catches sync failures from live traffic.
  3. Manual "Sync Now" - per-car button on the edit screen + bulk action on the admin list. Queue each via Action Scheduler to respect the 30 writes / min limit.

Idempotency: store the returned listing UUID in post meta (_carzle_listing_id). If meta exists → PATCH /listings/by-ref/{ref}. If not → POST /listings, save the returned UUID. On 409 Conflict from POST, retry as PATCH - handles race where two syncs fire at once.

Retries: on 429, read X-RateLimit-Reset and reschedule. On 500, exponential backoff (30s, 2m, 10m) max 3 attempts.

Trash, not unpublish: WP post moved to trash DELETE. Unpublish (draft) → PATCH to inactive.

Which fields are required vs. optional?+

Required on create: make, model, year, kilometers, price, title.

Strongly recommended for listing quality: description, trim, transmission, fuelType, bodyType, regionalSpecs (GCC / American / European - UAE buyers filter heavily on this), exteriorColor, interiorColor, imageUrls.

Nice to have: chassisNumber, warranty, serviceHistory, accidentHistory, financeAvailable, various flag booleans (hasCleanTitle, isFirstOwner, etc.), spec strings (horsepower, cylinders, etc.).

Rule: if a source field doesn't exist, omit the key from the payload - don't send empty strings or nulls. Empty strings fail enum validation.

Fetch GET /metadata once at plugin boot and cache for 1 hour - it returns every enum allowlist and the full makes/models catalog so you can validate on your side before POST-ing.

How should the features array be structured?+

Features are structured - each feature has a name and one of four categories: safety, entertainment, comfort, exterior.

"features": [
  { "name": "Panoramic sunroof",       "category": "comfort" },
  { "name": "Adaptive cruise control", "category": "safety" },
  { "name": "Harman Kardon sound",     "category": "entertainment" },
  { "name": "22-inch alloys",          "category": "exterior" }
]

If your source stores features as a flat list, either add a category sub-field going forward, or use a small keyword classifier (airbag → safety, sunroof → comfort, alloys → exterior, sound/nav → entertainment) with a fallback to comfort.

Do I need to send engine / top-speed / 0-100 specs, or does Carzle fill them in?+

Carzle auto-enriches on create. When you POST a listing, we look up our vehicle spec database by make + model + trim and fill in any missing spec fields (engine capacity, horsepower, cylinders, top speed, 0-100, body type, fuel type, transmission, doors, seating).

Dealer-sent values always win. If you send horsepower: "620 HP", we keep your value even if the VDB has a different one. Enrichment only fills fields you didn't send.

Enrichment runs on POST only, not on PATCH — so a dealer's manual edit is never silently overwritten by a later sync. If the VDB doesn't have the make+model (rare brands, brand-new models), those fields simply stay null; you can still send them yourself.

Minimum fields to trigger useful enrichment: make, model, and ideally trim (we fall back to trimless match if no trim-level data exists).

How do I assign a salesperson to each listing?+

Each Carzle showroom can have multiple "contacts" (salespeople) - each with their own name, phone, and WhatsApp. The contact assigned to a listing is what buyers see on the public listing page.

One-time setup: the dealer adds their salespeople in the Carzle dashboard (Showroom → Contacts). Each gets a UUID.

Then in your plugin:

  1. Call GET /api/v1/contacts once, cache the result.
  2. Build a map in your code from your own salesperson identifier (WP user email, CRM agent id, etc.) to Carzle's contact.id.
  3. When syncing a listing, include contactId in the POST / PATCH body.

Send contactId: null to clear an assignment (listing falls back to the default contact). Sending an id that doesn't belong to your showroom returns 422.

What happens to images? Do you store them or just link to ours?+

We ingest them. For each URL you send, we fetch it, re-encode to WebP (82 quality, EXIF stripped, max 40 megapixels), and upload to our CDN. Your listing shows our URL, not yours - so if your source site goes down, the Carzle listing is unaffected.

Source URLs must be HTTPS and publicly fetchable. SSRF-hardened: private IPs (10.x, 169.254.x, etc.) and redirect chains to them are rejected.

Per-image failures return a 422 with field-level detail (e.g. imageUrls.2 = "Source download failed"). No listing is created when any image fails - fix + retry.

Need help? Call or WhatsApp +971 58 587 9282 with the requestId from the failing response.
Carzle Dealer API - Docs for Developers