{
  "openapi": "3.1.0",
  "info": {
    "title": "Fleetkeep API",
    "version": "0.5.0",
    "summary": "UK fleet compliance vehicles API",
    "description": "Read and write end-user vehicle records on Fleetkeep. Authorisation is OAuth 2.1 with PKCE; the resource owner is a Fleetkeep end user who has granted a partner client access to their vehicles. All dates are ISO 8601 calendar dates (YYYY-MM-DD). All endpoints require a Bearer access token. Per-token rate limit: 60 requests per minute. See /.well-known/oauth-authorization-server for OAuth metadata and /.well-known/oauth-protected-resource for the resource server descriptor.",
    "contact": {
      "name": "Fleetkeep partner integration",
      "url": "https://fleetkeep.co.uk/contact",
      "email": "hello@fleetkeep.co.uk"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://fleetkeep.co.uk/legal/terms"
    }
  },
  "servers": [
    {
      "url": "https://fleetkeep.co.uk",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Vehicles",
      "description": "Manage the authorised user's vehicles."
    }
  ],
  "security": [
    {
      "oauth2": ["vehicles:read"]
    }
  ],
  "paths": {
    "/api/v1/vehicles": {
      "get": {
        "tags": ["Vehicles"],
        "summary": "List the authorised user's vehicles",
        "description": "Returns every vehicle on the user's account, ordered by createdAt descending.",
        "operationId": "listVehicles",
        "security": [{ "oauth2": ["vehicles:read"] }],
        "responses": {
          "200": {
            "description": "Vehicle list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["vehicles"],
                  "properties": {
                    "vehicles": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Vehicle" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Vehicles"],
        "summary": "Add a vehicle by registration",
        "description": "Adds a vehicle to the authorised user's account. Server triggers a DVLA Vehicle Enquiry Service lookup to populate make, model, year, fuel, MOT due date and tax due date. Insurance, last service and service interval are taken from the request body if supplied.",
        "operationId": "addVehicle",
        "security": [{ "oauth2": ["vehicles:write"] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AddVehicleInput" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Vehicle created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["id"],
                  "properties": {
                    "id": { "type": "string", "format": "uuid" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": {
            "description": "A vehicle with this registration already exists on the user's account.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "502": {
            "description": "DVLA upstream lookup failed.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
            }
          }
        }
      }
    },
    "/api/v1/vehicles/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": { "type": "string", "format": "uuid" },
          "description": "Vehicle UUID."
        }
      ],
      "get": {
        "tags": ["Vehicles"],
        "summary": "Get a single vehicle",
        "operationId": "getVehicle",
        "security": [{ "oauth2": ["vehicles:read"] }],
        "responses": {
          "200": {
            "description": "Vehicle detail",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["vehicle"],
                  "properties": {
                    "vehicle": { "$ref": "#/components/schemas/Vehicle" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "patch": {
        "tags": ["Vehicles"],
        "summary": "Update mutable vehicle fields",
        "description": "Patch semantics: only fields present in the body are touched. `null` explicitly clears a value; missing keys leave the existing value untouched. Only label, insuranceDueDate, lastServiceDate, lastServiceMileage and serviceIntervalMonths are mutable. DVLA-sourced fields cannot be patched and refresh from DVLA on a server-side schedule.",
        "operationId": "updateVehicle",
        "security": [{ "oauth2": ["vehicles:write"] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/UpdateVehiclePatch" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Patch applied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["ok"],
                  "properties": { "ok": { "type": "boolean", "const": true } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "tags": ["Vehicles"],
        "summary": "Remove a vehicle",
        "operationId": "deleteVehicle",
        "security": [{ "oauth2": ["vehicles:write"] }],
        "responses": {
          "204": { "description": "Vehicle deleted." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/vehicles/bulk": {
      "post": {
        "tags": ["Vehicles"],
        "summary": "Bulk add vehicles by registration",
        "description": "Add up to 50 vehicles in one call. Each registration is processed sequentially; partial success is reported via separate `created` and `errors` arrays.",
        "operationId": "bulkAddVehicles",
        "security": [{ "oauth2": ["vehicles:write"] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["registrations"],
                "properties": {
                  "registrations": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 50,
                    "items": { "type": "string", "example": "BD15 KFG" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk add result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["created", "errors"],
                  "properties": {
                    "created": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": ["id", "registration"],
                        "properties": {
                          "id": { "type": "string", "format": "uuid" },
                          "registration": { "type": "string" }
                        }
                      }
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": ["reg", "reason"],
                        "properties": {
                          "reg": { "type": "string" },
                          "reason": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorisation-code flow with PKCE (S256). End-user delegates per-vehicle access to a partner client. Access tokens expire after 1 hour; refresh tokens (granted with the offline_access scope) live for 30 days.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://fleetkeep.co.uk/auth/oauth2/authorize",
            "tokenUrl": "https://fleetkeep.co.uk/auth/oauth2/token",
            "refreshUrl": "https://fleetkeep.co.uk/auth/oauth2/token",
            "scopes": {
              "vehicles:read": "Read the authorised user's vehicles and compliance dates.",
              "vehicles:write": "Add, update, delete and bulk-add vehicles on the authorised user's behalf.",
              "offline_access": "Issue a refresh token so the partner can refresh access without re-prompting the user."
            }
          }
        }
      }
    },
    "schemas": {
      "Vehicle": {
        "type": "object",
        "description": "A single vehicle on the authorised user's account. DVLA-sourced fields (make, model, yearOfManufacture, fuelType, motDueDate, taxDueDate) are populated from the DVLA Vehicle Enquiry Service. Insurance and service fields are user-supplied. serviceDueDate is computed server-side from lastServiceDate plus serviceIntervalMonths.",
        "required": [
          "id", "registration", "label", "make", "model",
          "yearOfManufacture", "fuelType",
          "motDueDate", "taxDueDate", "insuranceDueDate",
          "lastServiceDate", "lastServiceMileage",
          "serviceIntervalMonths", "serviceDueDate"
        ],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "registration": { "type": "string", "example": "BD15 KFG" },
          "label": { "type": ["string", "null"], "maxLength": 80, "example": "Transit white" },
          "make": { "type": ["string", "null"], "example": "FORD" },
          "model": { "type": ["string", "null"], "example": "TRANSIT CUSTOM" },
          "yearOfManufacture": { "type": ["integer", "null"], "example": 2018 },
          "fuelType": { "type": ["string", "null"], "example": "DIESEL" },
          "motDueDate": { "type": ["string", "null"], "format": "date" },
          "taxDueDate": { "type": ["string", "null"], "format": "date" },
          "insuranceDueDate": { "type": ["string", "null"], "format": "date" },
          "lastServiceDate": { "type": ["string", "null"], "format": "date" },
          "lastServiceMileage": { "type": ["integer", "null"], "minimum": 0, "maximum": 1500000 },
          "serviceIntervalMonths": { "type": ["integer", "null"], "minimum": 1, "maximum": 120 },
          "serviceDueDate": { "type": ["string", "null"], "format": "date", "description": "Computed: lastServiceDate plus serviceIntervalMonths. Null if either input is missing." }
        }
      },
      "AddVehicleInput": {
        "type": "object",
        "required": ["registration"],
        "properties": {
          "registration": { "type": "string", "example": "BD15 KFG" },
          "label": { "type": ["string", "null"], "maxLength": 80 },
          "insuranceDueDate": { "type": ["string", "null"], "format": "date" },
          "lastServiceDate": { "type": ["string", "null"], "format": "date" },
          "lastServiceMileage": { "type": ["integer", "null"], "minimum": 0, "maximum": 1500000 },
          "serviceIntervalMonths": { "type": ["integer", "null"], "minimum": 1, "maximum": 120 }
        }
      },
      "UpdateVehiclePatch": {
        "type": "object",
        "description": "All fields are optional. Provide a value to set, `null` to clear, or omit to leave unchanged.",
        "properties": {
          "label": { "type": ["string", "null"], "maxLength": 80 },
          "insuranceDueDate": { "type": ["string", "null"], "format": "date" },
          "lastServiceDate": { "type": ["string", "null"], "format": "date" },
          "lastServiceMileage": { "type": ["integer", "null"], "minimum": 0, "maximum": 1500000 },
          "serviceIntervalMonths": { "type": ["integer", "null"], "minimum": 1, "maximum": 120 }
        },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "registration_required" }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request body or query was invalid.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid Bearer access token.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "Forbidden": {
        "description": "Token does not include the required scope.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "NotFound": {
        "description": "The requested vehicle does not exist on the authorised user's account.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      },
      "RateLimited": {
        "description": "Per-token rate limit exceeded (60 requests per minute).",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
        }
      }
    }
  }
}
