{
  "openapi": "3.0.3",
  "info": {
    "title": "Mastro Public API",
    "description": "Server-to-server REST API for Mastro registered users (DRIVER and CONTRACTOR roles). Authenticate with `Authorization: Bearer mk_live_<keyId>.<secret>`. Issue and rotate API keys from your Mastro app under Developers.\n\nIssued from the canonical OpenAPI document hosted by the API at `GET https://api.mastro.app/v1/openapi.json`. The version checked into the website is updated per release.",
    "version": "1.0.0"
  },
  "servers": [
    { "url": "https://api.mastro.app", "description": "Production" }
  ],
  "tags": [
    { "name": "Quotes", "description": "Calculate trip prices." },
    { "name": "Trips", "description": "Create, read and cancel trips owned by the authenticated user." }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mk_live_<keyId>.<secret>"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "schema": { "type": "string", "minLength": 8, "maxLength": 200, "pattern": "^[A-Za-z0-9_\\-:.]{8,200}$" },
        "description": "Unique key, recommended to be a UUID, so the request can be safely retried without creating duplicate trips. 24h server-side TTL."
      }
    },
    "schemas": {
      "Address": {
        "type": "object",
        "required": ["address", "lat", "lng"],
        "properties": {
          "address": { "type": "string", "maxLength": 500 },
          "lat": { "type": "number", "minimum": -90, "maximum": 90 },
          "lng": { "type": "number", "minimum": -180, "maximum": 180 }
        }
      },
      "Client": {
        "type": "object",
        "required": ["firstName", "lastName"],
        "properties": {
          "firstName": { "type": "string", "maxLength": 120 },
          "lastName": { "type": "string", "maxLength": 120 },
          "phone": { "type": "string", "minLength": 4, "maxLength": 40 },
          "email": { "type": "string", "format": "email", "maxLength": 200 },
          "companyName": { "type": "string", "maxLength": 200 },
          "type": { "type": "string", "enum": ["PERSONAL", "BUSINESS"] },
          "address": { "type": "string", "maxLength": 200 },
          "city": { "type": "string", "maxLength": 120 },
          "country": { "type": "string", "maxLength": 120 },
          "postalCode": { "type": "string", "maxLength": 20 }
        },
        "description": "Either phone or email must be provided."
      },
      "QuoteRequest": {
        "type": "object",
        "required": ["pickup", "dropoff", "serviceTierId"],
        "properties": {
          "pickup": { "type": "string", "minLength": 2, "maxLength": 500 },
          "dropoff": { "type": "string", "minLength": 2, "maxLength": 500 },
          "serviceTierId": { "type": "string", "format": "mongo-id" },
          "pricingSchemeId": { "type": "string", "format": "mongo-id" },
          "tripType": { "type": "string", "enum": ["TRIP", "AVAILABILITY"], "default": "TRIP" },
          "scheduledAt": { "type": "string", "format": "date-time" }
        }
      },
      "QuoteResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "message": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "pricingSchemeId": { "type": "string" },
              "pricingSchemeName": { "type": "string" },
              "serviceTierId": { "type": "string" },
              "serviceTierName": { "type": "string" },
              "serviceTierMultiplier": { "type": "number" },
              "route": {
                "type": "object",
                "properties": {
                  "distanceKm": { "type": "number" },
                  "durationMinutes": { "type": "number" }
                }
              },
              "price": {
                "type": "object",
                "properties": {
                  "base": { "type": "number" },
                  "distanceCost": { "type": "number" },
                  "timeCost": { "type": "number" },
                  "subtotal": { "type": "number" },
                  "minimum": { "type": "number" },
                  "total": { "type": "number" },
                  "totalCents": { "type": "integer" }
                }
              },
              "vat": {
                "type": "object",
                "properties": {
                  "rate": { "type": "number", "enum": [0, 10, 20] },
                  "amountCents": { "type": "integer" },
                  "amount": { "type": "number" },
                  "totalTTCCents": { "type": "integer" },
                  "totalTTC": { "type": "number" },
                  "isExempt": { "type": "boolean" }
                }
              }
            }
          }
        }
      },
      "CreateTripRequest": {
        "type": "object",
        "required": ["pickup", "dropoff", "scheduledAt", "price"],
        "properties": {
          "pickup": { "$ref": "#/components/schemas/Address" },
          "dropoff": { "$ref": "#/components/schemas/Address" },
          "scheduledAt": { "type": "string", "format": "date-time" },
          "price": { "type": "number", "minimum": 0, "description": "Final TTC price in EUR." },
          "paxNumber": { "type": "integer", "minimum": 1, "maximum": 20, "default": 1 },
          "client": { "$ref": "#/components/schemas/Client" },
          "clientId": { "type": "string", "format": "mongo-id" },
          "vehicleId": { "type": "string", "format": "mongo-id" },
          "serviceTierId": { "type": "string", "format": "mongo-id" },
          "pricingSchemeId": { "type": "string", "format": "mongo-id" },
          "tripType": { "type": "string", "enum": ["TRIP", "AVAILABILITY"] },
          "note": { "type": "string", "maxLength": 2000 },
          "estimatedDistanceKm": { "type": "number", "minimum": 0 },
          "estimatedDurationMinutes": { "type": "number", "minimum": 0 }
        },
        "description": "Either client or clientId is required."
      },
      "TripStatus": {
        "type": "string",
        "enum": ["CONFIRMED", "OTWTP", "OTWTD", "FINISHED", "FINISHED_PAYED", "CANCELLED"]
      },
      "Trip": {
        "type": "object",
        "properties": {
          "_id": { "type": "string" },
          "userId": { "type": "string" },
          "executorId": { "type": "string" },
          "status": { "$ref": "#/components/schemas/TripStatus" },
          "pickupAddress": { "type": "object" },
          "destinationAddress": { "type": "object" },
          "scheduledPickupTime": { "type": "string", "format": "date-time" },
          "price": { "type": "number" },
          "priceHT": { "type": "number" },
          "vatRate": { "type": "number" },
          "vatAmount": { "type": "number" },
          "tripType": { "$ref": "#/components/schemas/TripStatus" },
          "isPaid": { "type": "boolean" }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": { "type": "integer", "minimum": 1 },
          "limit": { "type": "integer", "minimum": 1, "maximum": 100 },
          "total": { "type": "integer", "minimum": 0 },
          "pages": { "type": "integer", "minimum": 1 },
          "hasNext": { "type": "boolean" },
          "hasPrevious": { "type": "boolean" }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "VALIDATION_FAILED" },
          "message": { "oneOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] },
          "statusCode": { "type": "integer" },
          "timestamp": { "type": "string", "format": "date-time" },
          "path": { "type": "string" }
        }
      }
    },
    "responses": {
      "Unauthorized": { "description": "Missing, malformed, expired or revoked API key.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "RateLimited": { "description": "Tier or pre-auth IP rate limit exceeded.", "headers": { "Retry-After": { "schema": { "type": "integer" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
      "ValidationError": { "description": "Body or query validation failed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } }
    }
  },
  "security": [{ "apiKey": [] }],
  "paths": {
    "/v1/quotes": {
      "post": {
        "tags": ["Quotes"],
        "summary": "Calculate a quote (price + ETA + distance) for a trip",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/QuoteRequest" } } }
        },
        "responses": {
          "200": { "description": "Quote calculated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/QuoteResponse" } } } },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/trips": {
      "post": {
        "tags": ["Trips"],
        "summary": "Create a new trip",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey", "required": true }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateTripRequest" } } }
        },
        "responses": {
          "201": {
            "description": "Trip created",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "success": { "type": "boolean" },
                "message": { "type": "string" },
                "data": { "$ref": "#/components/schemas/Trip" }
              }
            } } }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "description": "Idempotency-Key conflict or in-progress.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "get": {
        "tags": ["Trips"],
        "summary": "List trips owned by the authenticated user",
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } },
          { "name": "status", "in": "query", "schema": { "$ref": "#/components/schemas/TripStatus" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date-time" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date-time" } }
        ],
        "responses": {
          "200": {
            "description": "Paginated list",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "success": { "type": "boolean" },
                "data": { "type": "array", "items": { "$ref": "#/components/schemas/Trip" } },
                "pagination": { "$ref": "#/components/schemas/Pagination" }
              }
            } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/trips/{id}": {
      "get": {
        "tags": ["Trips"],
        "summary": "Get a single trip by id",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "mongo-id" } }
        ],
        "responses": {
          "200": { "description": "Trip", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "success": { "type": "boolean" },
              "data": { "$ref": "#/components/schemas/Trip" }
            }
          } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Trip not found or not yours.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/trips/{id}/cancel": {
      "post": {
        "tags": ["Trips"],
        "summary": "Cancel a trip",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "mongo-id" } },
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "content": { "application/json": { "schema": {
            "type": "object",
            "properties": { "reason": { "type": "string", "maxLength": 500 } }
          } } }
        },
        "responses": {
          "200": { "description": "Cancelled trip", "content": { "application/json": { "schema": {
            "type": "object",
            "properties": {
              "success": { "type": "boolean" },
              "data": { "$ref": "#/components/schemas/Trip" }
            }
          } } } },
          "400": { "description": "Trip already cancelled.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorEnvelope" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Trip not found or not yours." },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  }
}
