API Guide

    Idempotency

    Safe retries for POST, PATCH, and DELETE using Idempotency-Key and If-Match.

    Idempotency-Key — required on every write

    Idempotency-Key is required on every POST, PATCH, and DELETE. A write without one is rejected with 400 idempotency_key_required before anything executes. This is what makes a retry after a timeout safe — it can never create a duplicate.

    curl -X POST https://api.moveitpro.com/v1/leads \
      -H "Authorization: Bearer mip_live_..." \
      -H "Idempotency-Key: 6f6c2c1e-8f4b-4f1a-9c33-6a3e0d7a2b91" \
      -H "Content-Type: application/json" \
      -d '{"first_name":"Dana","last_name":"Reyes","email":"dana@example.com"}'

    The key is 1–255 visible ASCII characters. A UUIDv4 is recommended but not required; anything outside that shape is 400 invalid_idempotency_key.

    Generate one key per logical operation and reuse it for every retry of that operation.

    • Replaying the same key with the same request returns the original result instead of writing again, marked with an Idempotency-Replayed: true response header.
    • Replaying the same key with a different payload is an error: 422 idempotency_key_reuse.
    • A same-key retry that arrives while the first attempt is still executing is 409 request_in_flight with a Retry-After header — wait and retry the same key.

    Concurrency: If-Match

    Updates and deletes use optimistic concurrency and require an If-Match header (428 precondition_required without one). The version token is the record's updated_at value, exactly as a GET returns it — an opaque timestamp, not a version number. Quotes around it are optional.

    curl -X PATCH https://api.moveitpro.com/v1/customers/{id} \
      -H "Authorization: Bearer mip_live_..." \
      -H 'If-Match: "2026-08-01T14:22:05.123456+00:00"' \
      -H "Idempotency-Key: …" \
      -H "Content-Type: application/json" \
      -d '{"email":"new@example.com"}'

    Read the resource, take data.updated_at, send it back. On success the response body carries the new updated_at for your next write. Pass the timestamp through untouched — comparison is strict equality — and do not send an ETag here: the read ETag is a different value.

    • 428 precondition_required — the route needs If-Match and none was sent.
    • 400 invalid_precondition — the header could not be used: weak validators (W/"…"), *, comma-lists, and non-timestamp values are all rejected.
    • 409 version_conflict — someone else changed the record. The error body includes current_version, the record's current updated_at — re-read (or use current_version directly after re-checking the record), re-apply, retry.
    • 409 request_in_flight — an identical idempotent request is still executing. Wait and retry the same key.
    1. Generate one Idempotency-Key per logical write and send it on every attempt.
    2. Retry on 429 (honor Retry-After), 500, 503, network timeouts, and request_in_flight — always with the same key.
    3. On 502 write_indeterminate, do not retry with the same key: the indeterminate result is stored against it and will simply replay. GET the record first; if the write didn't land, retry with a new key.
    4. On 409 version_conflict, re-read, re-apply, and retry with a fresh If-Match.
    5. Never retry 400, 401, 403, 404, or 422.