{
  "openapi": "3.1.0",
  "info": {
    "title": "Jigcar webhooks",
    "description": "Outbound webhooks for vehicle movement lifecycle events, plus the endpoint for reconciling against them.\n\nEvery request is signed. Verify `svix-signature` against the RAW request body before parsing it, and deduplicate on the envelope's `eventId`. Delivery is at-least-once.\n\nSee docs/webhooks.md for the integration guide.",
    "version": "1.0.0",
    "contact": {
      "email": "engineering@jigcar.com",
      "url": "https://jigcar.com/"
    }
  },
  "servers": [
    {
      "url": "https://public-api.jigcar.com/v1"
    }
  ],
  "tags": [
    {
      "name": "Webhooks",
      "description": "Events we send you, and how to reconcile against them."
    }
  ],
  "components": {
    "securitySchemes": {
      "JigcarAPIAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Jigcar-API-Key"
      }
    },
    "schemas": {
      "Movement": {
        "type": "object",
        "title": "Movement",
        "description": "The movement every event carries. Each event adds the fields it alone reports.",
        "required": [
          "id",
          "status",
          "businessArea",
          "transportMethod"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Jigcar's identifier for the movement.",
            "examples": [
              "01JQ8W3H2K4M5N6P7R8S9T0V1W"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "IN_PROGRESS",
              "DELIVERED",
              "CANCELLED",
              "ABORTED"
            ],
            "examples": [
              "DELIVERED"
            ]
          },
          "vin": {
            "type": "string",
            "examples": [
              "2T1BURHE2FC361469"
            ]
          },
          "vrm": {
            "type": "string",
            "description": "UK registration mark. Absent for vehicles without one."
          },
          "make": {
            "type": "string",
            "examples": [
              "Toyota"
            ]
          },
          "model": {
            "type": "string",
            "examples": [
              "Corolla"
            ]
          },
          "modelVariant": {
            "type": "string",
            "examples": [
              "LE"
            ]
          },
          "businessArea": {
            "type": "string",
            "examples": [
              "Retail"
            ]
          },
          "transportMethod": {
            "type": "string",
            "enum": [
              "DRIVEN",
              "TRANSPORTED"
            ]
          },
          "notes": {
            "type": "string",
            "description": "The note supplied when the movement was created."
          },
          "confirmedBy": {
            "type": "string",
            "description": "Who confirmed the vehicle was available."
          },
          "originSiteId": {
            "type": "string",
            "description": "Present when collection is from one of your sites.",
            "examples": [
              "site-orchard-park"
            ]
          },
          "originAddressLine1": {
            "type": "string",
            "description": "Present when collection is from an ad-hoc address."
          },
          "originAddressPostcode": {
            "type": "string"
          },
          "originScheduledOn": {
            "type": "string",
            "format": "date",
            "description": "The collection date requested when the movement was created.",
            "examples": [
              "2026-08-27"
            ]
          },
          "originScheduledTimeslot": {
            "type": "string",
            "enum": [
              "ANYTIME",
              "AM",
              "PM"
            ]
          },
          "originPredictedOn": {
            "type": "string",
            "format": "date",
            "description": "When we currently expect collection to happen.",
            "examples": [
              "2026-08-27"
            ]
          },
          "originPredictedTimeslot": {
            "type": "string",
            "enum": [
              "ANYTIME",
              "AM",
              "PM"
            ]
          },
          "destinationSiteId": {
            "type": "string",
            "examples": [
              "site-lockport"
            ]
          },
          "destinationAddressLine1": {
            "type": "string"
          },
          "destinationAddressPostcode": {
            "type": "string"
          },
          "destinationScheduledOn": {
            "type": "string",
            "format": "date",
            "description": "The delivery deadline requested at creation.",
            "examples": [
              "2026-08-27"
            ]
          },
          "destinationScheduledTimeslot": {
            "type": "string",
            "enum": [
              "ANYTIME",
              "AM",
              "PM"
            ]
          },
          "destinationPredictedOn": {
            "type": "string",
            "format": "date",
            "description": "When we currently expect delivery to happen.",
            "examples": [
              "2026-08-27"
            ]
          },
          "destinationPredictedTimeslot": {
            "type": "string",
            "enum": [
              "ANYTIME",
              "AM",
              "PM"
            ]
          },
          "driverName": {
            "type": "string",
            "description": "The assigned driver. Absent when the movement is carried by a logistics provider.",
            "examples": [
              "John Davidson"
            ]
          }
        }
      },
      "CollectedMovement": {
        "title": "CollectedMovement",
        "allOf": [
          {
            "$ref": "#/components/schemas/Movement"
          },
          {
            "type": "object",
            "required": [
              "originActualOn"
            ],
            "properties": {
              "originActualOn": {
                "type": "string",
                "format": "date",
                "description": "When the vehicle was actually collected.",
                "examples": [
                  "2026-08-27"
                ]
              }
            }
          }
        ]
      },
      "DeliveredMovement": {
        "title": "DeliveredMovement",
        "allOf": [
          {
            "$ref": "#/components/schemas/Movement"
          },
          {
            "type": "object",
            "required": [
              "destinationActualOn"
            ],
            "properties": {
              "destinationActualOn": {
                "type": "string",
                "format": "date",
                "description": "When the vehicle was actually delivered.",
                "examples": [
                  "2026-08-27"
                ]
              }
            }
          }
        ]
      },
      "AbortedMovement": {
        "title": "AbortedMovement",
        "allOf": [
          {
            "$ref": "#/components/schemas/Movement"
          },
          {
            "type": "object",
            "required": [],
            "properties": {
              "abortReason": {
                "type": "string",
                "enum": [
                  "NotOnSite",
                  "NotReady",
                  "NeedsDifferentTransportMethod",
                  "CancelledTooLate",
                  "Other"
                ],
                "description": "Why the movement was aborted. Free-text detail is not published.",
                "examples": [
                  "NotOnSite"
                ]
              }
            }
          }
        ]
      },
      "MovementEvent": {
        "type": "object",
        "title": "MovementEvent",
        "description": "The envelope fields common to every movement webhook.",
        "required": [
          "schemaVersion",
          "eventId",
          "occurredAt",
          "occurredAtLocal"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "description": "Bumped only for a breaking change to this envelope.",
            "examples": [
              "1.0"
            ]
          },
          "eventId": {
            "type": "string",
            "description": "Stable across redeliveries of the same event. Deduplicate on this.",
            "examples": [
              "01JQ8W3H2K4M5N6P7R8S9T0V1W"
            ]
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the transition happened, as an ISO 8601 instant. The only instant in the payload.",
            "examples": [
              "2026-08-27T09:14:02.000Z"
            ]
          },
          "occurredAtLocal": {
            "type": "string",
            "description": "The same instant at the offset of the site the event happened at.",
            "examples": [
              "2026-08-27T05:14:02-04:00"
            ]
          },
          "reference": {
            "type": "string",
            "description": "Your own identifier for the vehicle, as supplied on `vehicle.externalVehicleId`.",
            "examples": [
              "STOCK-12345"
            ]
          }
        }
      },
      "MovementCollectedEvent": {
        "title": "MovementCollectedEvent",
        "allOf": [
          {
            "$ref": "#/components/schemas/MovementEvent"
          },
          {
            "type": "object",
            "required": [
              "eventType",
              "movement"
            ],
            "properties": {
              "eventType": {
                "type": "string",
                "const": "movement.collected"
              },
              "movement": {
                "$ref": "#/components/schemas/CollectedMovement"
              }
            }
          }
        ]
      },
      "MovementDeliveredEvent": {
        "title": "MovementDeliveredEvent",
        "allOf": [
          {
            "$ref": "#/components/schemas/MovementEvent"
          },
          {
            "type": "object",
            "required": [
              "eventType",
              "movement"
            ],
            "properties": {
              "eventType": {
                "type": "string",
                "const": "movement.delivered"
              },
              "movement": {
                "$ref": "#/components/schemas/DeliveredMovement"
              }
            }
          }
        ]
      },
      "MovementCancelledEvent": {
        "title": "MovementCancelledEvent",
        "allOf": [
          {
            "$ref": "#/components/schemas/MovementEvent"
          },
          {
            "type": "object",
            "required": [
              "eventType",
              "movement"
            ],
            "properties": {
              "eventType": {
                "type": "string",
                "const": "movement.cancelled"
              },
              "movement": {
                "$ref": "#/components/schemas/Movement"
              }
            }
          }
        ]
      },
      "MovementAbortedEvent": {
        "title": "MovementAbortedEvent",
        "allOf": [
          {
            "$ref": "#/components/schemas/MovementEvent"
          },
          {
            "type": "object",
            "required": [
              "eventType",
              "movement"
            ],
            "properties": {
              "eventType": {
                "type": "string",
                "const": "movement.aborted"
              },
              "movement": {
                "$ref": "#/components/schemas/AbortedMovement"
              }
            }
          }
        ]
      }
    }
  },
  "security": [
    {
      "JigcarAPIAuth": []
    }
  ],
  "webhooks": {
    "movement.collected": {
      "post": {
        "summary": "Movement collected",
        "description": "The vehicle has been collected from the origin and is in transit.",
        "operationId": "webhookMovementCollected",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovementCollectedEvent"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Return any 2xx to acknowledge. A non-2xx is retried on Svix's schedule; sustained failure disables the endpoint and notifies you."
          }
        }
      }
    },
    "movement.delivered": {
      "post": {
        "summary": "Movement delivered",
        "description": "The vehicle has been delivered to the destination. The normal terminal state.",
        "operationId": "webhookMovementDelivered",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovementDeliveredEvent"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Return any 2xx to acknowledge. A non-2xx is retried on Svix's schedule; sustained failure disables the endpoint and notifies you."
          }
        }
      }
    },
    "movement.cancelled": {
      "post": {
        "summary": "Movement cancelled",
        "description": "The movement was cancelled before it was carried out.",
        "operationId": "webhookMovementCancelled",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovementCancelledEvent"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Return any 2xx to acknowledge. A non-2xx is retried on Svix's schedule; sustained failure disables the endpoint and notifies you."
          }
        }
      }
    },
    "movement.aborted": {
      "post": {
        "summary": "Movement aborted",
        "description": "The movement was abandoned after it had started, for example the vehicle was not on site.",
        "operationId": "webhookMovementAborted",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovementAbortedEvent"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Return any 2xx to acknowledge. A non-2xx is retried on Svix's schedule; sustained failure disables the endpoint and notifies you."
          }
        }
      }
    }
  },
  "paths": {
    "/movement-requests/events": {
      "get": {
        "summary": "List movement events",
        "description": "Replays the webhook envelopes we published for your profile, newest first. Use it to confirm you processed everything: a consumer that acknowledges before it persists (a Power Automate flow answers 202 the moment the request lands) can lose an event without either side seeing a failure, and a retry cannot help once we have had a 2xx.\n\nBounded by message retention, which depends on the Svix plan: 30 days on free, 90 on Pro. An empty page is indistinguishable from an expired one, so reconcile well inside the window.",
        "operationId": "listMovementEvents",
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only events at or after this ISO 8601 instant.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "examples": [
                "2026-08-27T00:00:00Z"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque continuation token from a previous response's `nextCursor`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 250. Defaults to 50.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$",
              "examples": [
                "50"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "The same envelopes a webhook would have delivered.",
                      "items": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/MovementCollectedEvent"
                          },
                          {
                            "$ref": "#/components/schemas/MovementDeliveredEvent"
                          },
                          {
                            "$ref": "#/components/schemas/MovementCancelledEvent"
                          },
                          {
                            "$ref": "#/components/schemas/MovementAbortedEvent"
                          }
                        ],
                        "discriminator": {
                          "propertyName": "eventType",
                          "mapping": {
                            "movement.collected": "#/components/schemas/MovementCollectedEvent",
                            "movement.delivered": "#/components/schemas/MovementDeliveredEvent",
                            "movement.cancelled": "#/components/schemas/MovementCancelledEvent",
                            "movement.aborted": "#/components/schemas/MovementAbortedEvent"
                          }
                        }
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page. Null on the last page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid `since` or `limit`."
          },
          "401": {
            "description": "Missing or invalid API key."
          }
        }
      }
    }
  }
}