MovingPro Documents API — Safeway Moving

API reference · version 1.0.0 · Swagger UI · openapi.json

Overview

Read-only access to jobs and the documents attached to them, plus a webhook that announces document changes.

Authentication Send your API key on every request as an x-api-key header. Keys are issued by MovingPro (a 26-character key tied to your company); keep it server-side and never embed it in a browser or mobile app. A missing, invalid or deactivated key returns 401.

Rate limits Sustained ~5 requests/second with short bursts allowed. Over the limit the gateway answers 429 with body {"message":"Too Many Requests"} — back off and retry. Document downloads happen through the temporary direct link returned by the download endpoint and do not count against the limit.

Paging GET /jobs returns up to limit jobs (default 100, max 500) newest-first plus a nextCursor. Pass it back as ?cursor= to get the next page; stop when nextCursor is null. A cursor is bound to the filters it was issued with — changing filters mid-walk returns 400 invalid_cursor; restart the walk. Paging is stable while new jobs are being created.

Timestamps Date-times such as createdAt and addedOn are the CRM's own wall-clock time and carry no time-zone suffix (2026-07-03T14:02:00). Dates (bookingDate, moveDate) are YYYY-MM-DD. Webhook timestamp and download expiresAt are UTC (Z).

Errors Every error body is { "ok": false, "error": "<code>", "message": "<human readable>" } (except the gateway's 429 above).

Base URL

https://safewaymoving.movingpro.net/api

All endpoints below are relative to this base. Example:

curl -H "x-api-key: YOUR_KEY" "https://safewaymoving.movingpro.net/api/jobs?limit=100"

Meta

GET/healthno key required

Liveness probe

200 OK
{
  "ok": true,
  "service": "safewaymovinginc-documents-api"
}
GET/docsno key required

This reference (HTML)

200 HTML page
GET/swaggerno key required

Interactive Swagger UI

200 HTML page
GET/openapi.jsonno key required

This document as JSON

200 OpenAPI document

Jobs

GET/jobs

List jobs

Newest-first, with optional filters and keyset paging. Unknown query parameters are rejected (400) so a typo never silently widens a backfill.

NameInTypeDescription
statusqueryLEAD FOLLOW_UP BOOKED READY_FOR_PICKUP PICKED_UP ON_TRIP IN_STORAGE READY_FOR_DELIVERY DELIVERED CANCELLED DEAD DELETEDFolded lead + invoice status.
orderTypequeryintegerOrder type id (see orderType.id on a job; e.g. 1 Local, 2 Long Distance, 5 Intrastate).
flagqueryintegerFlag id (see flag.id).
branchIdqueryintegerBranch id (see branch.id).
createdFromquerydate-timeJob created on/after (YYYY-MM-DD or YYYY-MM-DDTHH:mm[:ss], CRM local time).
createdToquerydate-timeJob created on/before. A date-only value covers the whole day.
bookedFromquerydate
bookedToquerydate
moveFromquerydate
moveToquerydate
updatedFromquerydate-timeLast CRM activity on/after. Enables the (slower) updatedAt computation.
updatedToquerydate-time
pickupStatequerystringTwo-letter state of the first pickup stop.
deliveryStatequerystring
hasDocumentsquerybooleanOnly jobs with (or without) at least one document.
limitqueryinteger
cursorquerystringOpaque nextCursor from the previous page.
200 A page of jobs
{
  "ok": true,
  "items": [
    {
      "jobId": 20312,
      "referenceNumber": "SW2031200",
      "status": "DELIVERED",
      "orderType": {
        "id": 2,
        "name": "Long Distance Move"
      },
      "flag": null,
      "branch": {
        "id": 9,
        "name": "Safeway Moving"
      },
      "createdAt": "2026-06-14T09:12:41",
      "updatedAt": null,
      "bookingDate": "2026-06-15",
      "moveDate": "2026-07-02",
      "customer": {
        "firstName": "Jane",
        "lastName": "Doe",
        "email": "jane.doe@example.com",
        "phone": "(555) 010-2000"
      },
      "pickup": {
        "city": "Hobart",
        "state": "IN",
        "zip": "46342"
      },
      "delivery": {
        "city": "Austin",
        "state": "TX",
        "zip": "73301"
      },
      "assignedToId": 14,
      "invoiceId": 20313,
      "documentCount": 3
    }
  ],
  "nextCursor": "eyJ2IjoxLCJjZCI6IjIwMjYtMDYtMTRUMDk6MTI6NDEuMDAwWiIsImlkIjoyMDMxMiwiZiI6IjNhOWY1YzBiIn0"
}
400 Bad filter, cursor or limit
{
  "ok": false,
  "error": "invalid_filter",
  "message": "Unknown query parameter \"statuss\"."
}
401 Missing or invalid key
{
  "ok": false,
  "error": "unauthorized",
  "message": "An x-api-key header is required."
}
429 Rate limit exceeded (emitted by the gateway, not this API)
{
  "message": "Too Many Requests"
}
GET/jobs/{jobId}

Get one job

NameInTypeDescription
jobIdpath · requiredintegerThe jobId from the jobs list.
200 The job (with `updatedAt` populated)
{
  "ok": true,
  "job": {
    "jobId": 20312,
    "referenceNumber": "SW2031200",
    "status": "DELIVERED",
    "orderType": {
      "id": 2,
      "name": "Long Distance Move"
    },
    "flag": null,
    "branch": {
      "id": 9,
      "name": "Safeway Moving"
    },
    "createdAt": "2026-06-14T09:12:41",
    "updatedAt": "2026-07-03T15:40:02",
    "bookingDate": "2026-06-15",
    "moveDate": "2026-07-02",
    "customer": {
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane.doe@example.com",
      "phone": "(555) 010-2000"
    },
    "pickup": {
      "city": "Hobart",
      "state": "IN",
      "zip": "46342"
    },
    "delivery": {
      "city": "Austin",
      "state": "TX",
      "zip": "73301"
    },
    "assignedToId": 14,
    "invoiceId": 20313,
    "documentCount": 3
  }
}
401 Missing or invalid key
{
  "ok": false,
  "error": "unauthorized",
  "message": "The API key is invalid or inactive."
}
404 Unknown job
{
  "ok": false,
  "error": "not_found",
  "message": "No job with id 999999."
}
429 Rate limit exceeded (emitted by the gateway, not this API)
{
  "message": "Too Many Requests"
}

Documents

GET/jobs/{jobId}/documents

List documents on a job

Every document attached to the job, newest first. Documents added on the invoice after booking are included; internal mirror copies are not, so each document appears once.

NameInTypeDescription
jobIdpath · requiredintegerThe jobId from the jobs list.
200 Documents
{
  "ok": true,
  "items": [
    {
      "documentId": 79159,
      "name": "Bill of Lading - order 20312.pdf",
      "sizeBytes": 320786,
      "addedBy": "Office User",
      "addedOn": "2026-07-03T14:02:00"
    }
  ]
}
401 Missing or invalid key
{
  "ok": false,
  "error": "unauthorized",
  "message": "The API key is invalid or inactive."
}
404 Unknown job
{
  "ok": false,
  "error": "not_found",
  "message": "No job with id 999999."
}
429 Rate limit exceeded (emitted by the gateway, not this API)
{
  "message": "Too Many Requests"
}
GET/jobs/{jobId}/documents/{documentId}/download

Get a temporary direct download link

Returns a pre-signed URL. Fetch the file from url with a plain GET (no API key) before expiresAt (10 minutes). The download itself does not count against the rate limit. Request a fresh link if it expires.

NameInTypeDescription
jobIdpath · requiredintegerThe jobId from the jobs list.
documentIdpath · requiredintegerThe documentId from the documents list (or a webhook).
200 Download link
{
  "ok": true,
  "documentId": 79159,
  "name": "Bill of Lading - order 20312.pdf",
  "sizeBytes": 320786,
  "contentType": "application/pdf",
  "url": "https://safewaymovinginc.s3.us-east-1.amazonaws.com/mobile-uploads/20312/0f3a…?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=600&…",
  "expiresAt": "2026-07-03T14:12:00.000Z"
}
401 Missing or invalid key
{
  "ok": false,
  "error": "unauthorized",
  "message": "The API key is invalid or inactive."
}
404 Unknown job or document
{
  "ok": false,
  "error": "not_found",
  "message": "Document 1 was not found on job 20312."
}
429 Rate limit exceeded (emitted by the gateway, not this API)
{
  "message": "Too Many Requests"
}
501 Stored where this API cannot serve it
{
  "ok": false,
  "error": "storage_unsupported",
  "message": "This document is stored in a location this API cannot serve."
}

Webhooks

Events MovingPro pushes to the HTTPS URL you register. Names below are the event values.

POST → your URLdocument.created

A document was added to a job

Delivered as an HTTPS POST to the URL you register with MovingPro, within about a minute of the change in the CRM.

Verify every delivery. Each request carries X-MovingPro-Timestamp (unix seconds) and X-MovingPro-Signature (v1=<hex>). Compute HMAC-SHA256(secret, timestamp + "." + rawBody) over the exact request bytes, hex-encode it, and compare (constant-time) with the value after v1=. Reject deliveries whose timestamp is more than 5 minutes off.

Respond 2xx quickly (do the work asynchronously). A 4xx response is treated as a permanent reject and the event is dropped; 5xx or a timeout (10 s) is queued for a later retry. eventId is stable across retries — deduplicate on it. To fetch the file, call GET /jobs/{jobId}/documents/{documentId}/download. Renames are not announced.

NameInTypeDescription
X-MovingPro-Timestampheader · requiredstringUnix seconds when the request was signed.
X-MovingPro-Signatureheader · requiredstringv1= + hex HMAC-SHA256 of timestamp + "." + body.
X-MovingPro-Eventheader · requireddocument.created document.deleted
X-MovingPro-Event-Idheader · requiredstring

Payload

{
  "event": "document.created",
  "eventId": "doc-79159-created",
  "timestamp": "2026-07-03T14:02:31.512Z",
  "data": {
    "jobId": 20312,
    "document": {
      "documentId": 79159,
      "name": "Bill of Lading - order 20312.pdf",
      "sizeBytes": 320786,
      "createdOn": "2026-07-03T14:02:00",
      "createdByName": "Office User",
      "createdById": 14
    }
  }
}
2xx Acknowledged. Any other status is treated as a failed delivery (4xx: dropped; 5xx/timeout: retried later).
POST → your URLdocument.deleted

A document was removed from a job

NameInTypeDescription
X-MovingPro-Timestampheader · requiredstringUnix seconds when the request was signed.
X-MovingPro-Signatureheader · requiredstringv1= + hex HMAC-SHA256 of timestamp + "." + body.
X-MovingPro-Eventheader · requireddocument.created document.deleted
X-MovingPro-Event-Idheader · requiredstring

Payload

{
  "event": "document.deleted",
  "eventId": "doc-79159-deleted",
  "timestamp": "2026-07-03T15:40:02.004Z",
  "data": {
    "jobId": 20312,
    "documentId": 79159,
    "name": "Bill of Lading - order 20312.pdf"
  }
}
2xx Acknowledged.