{
  "openapi": "3.0.3",
  "info": {
    "title": "EfiRoute API",
    "version": "1.0.0",
    "description": "REST API for [EfiRoute](https://efiroute.com) — multi-courier route optimization.\n\n## Authentication\n\nEvery request carries a company API key:\n\n```\nAuthorization: Bearer efr_live_xxxxxxxx…\n```\n\nCreate and revoke keys in the dashboard under **Settings → API**. The raw key is shown\n**once**; EfiRoute stores only a hash of it. A key belongs to a company, not a user, and\ncarries any of three scopes: `read`, `write`, `optimize`.\n\nAPI access requires the **Pro** or **Business** plan. On the Free plan every call returns\n`402 API_REQUIRES_PAID_PLAN`.\n\n## Response shape\n\nSuccess is always `{ \"success\": true, \"data\": { … } }`. Errors are always\n`{ \"success\": false, \"message\": \"…\", \"code\": \"…\", \"details\": { … } }` — branch on `code`,\nnever on the message text.\n\n## Billing\n\nOptimization costs **$1 per courier per run**, taken from\nmonthly subscription credit first and the prepaid wallet after. A run that fails, or that\nproduces no routes, is not charged. Reading data is free. Check `GET /account` before a run\nto see the balance and the remaining daily quota.\n\n## Rate limits\n\n120 requests per minute per key. Optimization is additionally capped per day by your plan.\n\n## Typical integration\n\n1. `POST /projects` — create the day's plan.\n2. `POST /projects/{projectId}/stops` — upload delivery points (up to 1000 per call).\n3. `POST /projects/{projectId}/stops/geocode` — resolve addresses to coordinates.\n   **Required**: the optimizer skips stops without coordinates.\n4. `POST /couriers` then `POST /projects/{projectId}/couriers` — the second call carries\n   the shift hours, depots and capacity the optimizer actually uses.\n5. `POST /projects/{projectId}/optimize` — returns routes directly, or `202` with a\n   `jobId` for large plans.\n6. `GET /projects/{projectId}/routes` — read the result; or subscribe to the\n   `optimization.completed` webhook instead of polling.\n\n## Webhooks\n\nRegister endpoints via `/webhooks`. Each delivery is signed:\n\n```\nX-EfiRoute-Timestamp: 1759000000\nX-EfiRoute-Signature: sha256=<hex>\n```\n\nwhere the signature is `HMAC-SHA256(secret, \"<timestamp>.<raw body>\")`. Verify against the\n**raw** body, compare in constant time, and reject timestamps older than 5 minutes. Failed\ndeliveries retry 6 times with backoff (1 m, 5 m, 15 m, 1 h, 3 h); an\nendpoint that fails 20 times in a row is disabled\nautomatically. Respond 2xx quickly and do the work asynchronously.",
    "contact": {
      "name": "EfiRoute Support",
      "email": "info@efiroute.com",
      "url": "https://efiroute.com/developers"
    },
    "termsOfService": "https://efiroute.com/terms-of-use"
  },
  "servers": [
    {
      "url": "https://api.efiroute.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Plan, credit balance, limits and usage."
    },
    {
      "name": "Projects",
      "description": "A project is one day's delivery plan."
    },
    {
      "name": "Delivery points",
      "description": "The stops to visit, with time windows and service time."
    },
    {
      "name": "Couriers",
      "description": "Drivers, and their per-project shift/capacity settings."
    },
    {
      "name": "Optimization",
      "description": "Trigger a run and read the resulting routes. Billed."
    },
    {
      "name": "Webhooks",
      "description": "Get notified instead of polling."
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "This specification",
        "description": "Public — no API key needed. Fetch it to generate a client or to brief an AI agent.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Plan, credit balance, limits and usage",
        "description": "One call to answer \"can I afford this run?\" — remaining subscription credit, wallet balance, plan limits and how many projects you have used this month.",
        "responses": {
          "200": {
            "description": "Account snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "account": {
                          "type": "object",
                          "properties": {
                            "companyId": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "plan": {
                              "type": "string",
                              "enum": [
                                "free",
                                "pro",
                                "business"
                              ]
                            },
                            "subscriptionStatus": {
                              "type": "string",
                              "nullable": true
                            },
                            "subscriptionPlan": {
                              "type": "string",
                              "nullable": true
                            },
                            "subscriptionPeriodEnd": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            }
                          }
                        },
                        "credit": {
                          "type": "object",
                          "description": "Optimization is charged against subscription credit first, then the prepaid wallet.",
                          "properties": {
                            "subscriptionCreditsRemainingUsd": {
                              "type": "number"
                            },
                            "subscriptionMonthlyCreditsUsd": {
                              "type": "number"
                            },
                            "walletBalanceUsd": {
                              "type": "number"
                            },
                            "totalAvailableUsd": {
                              "type": "number"
                            },
                            "optimizationCostPerCourierUsd": {
                              "type": "number",
                              "example": 1
                            }
                          }
                        },
                        "limits": {
                          "type": "object",
                          "properties": {
                            "projectsPerMonth": {
                              "type": "integer"
                            },
                            "couriersPerProject": {
                              "type": "integer"
                            },
                            "deliveryPointsPerProject": {
                              "type": "integer"
                            },
                            "optimizationsPerDay": {
                              "type": "integer"
                            }
                          }
                        },
                        "usage": {
                          "type": "object",
                          "properties": {
                            "projectsThisMonth": {
                              "type": "integer"
                            },
                            "projectsThisMonthLimit": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by project status."
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Match against the name."
          }
        ],
        "responses": {
          "200": {
            "description": "Projects.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "projects": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Project"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Create a project",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "defaultStartDate": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "defaultStartTime": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$"
                  },
                  "defaultStartAddress": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "defaultStartLatitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "defaultStartLongitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "project": {
                          "$ref": "#/components/schemas/Project"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get a project",
        "responses": {
          "200": {
            "description": "Project.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "project": {
                          "$ref": "#/components/schemas/Project"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Projects"
        ],
        "summary": "Update a project",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "defaultStartDate": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "defaultStartTime": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$"
                  },
                  "defaultStartAddress": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "defaultStartLatitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "defaultStartLongitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "active",
                      "editing",
                      "on_hold",
                      "postponed",
                      "optimized",
                      "in_progress",
                      "completed",
                      "cancelled"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "project": {
                          "$ref": "#/components/schemas/Project"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project (draft only)",
        "description": "Only a project still in the `draft` status can be deleted — once it has been optimized its routes and billing records are kept. To retire an optimized project, `PATCH` its `status` to `cancelled` (or back to `draft` if you really want it gone). Deleting anything else returns `400 PROJECT_NOT_DRAFT`.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/stops": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Delivery points"
        ],
        "summary": "List delivery points",
        "responses": {
          "200": {
            "description": "Delivery points.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "stops": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Stop"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Delivery points"
        ],
        "summary": "Create one or many delivery points",
        "description": "Send a single object, or `{ \"stops\": [ … ] }` with up to 1000 items. `serviceMinutes` accepts a number of minutes or human text such as \"1:30\" or \"2 hours\".",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 255
                      },
                      "address": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "phone": {
                        "type": "string",
                        "maxLength": 50
                      },
                      "notes": {
                        "type": "string",
                        "maxLength": 2000
                      },
                      "products": {
                        "type": "string",
                        "maxLength": 2000
                      },
                      "serviceMinutes": {
                        "anyOf": [
                          {
                            "type": "number",
                            "exclusiveMinimum": true,
                            "minimum": 0
                          },
                          {
                            "type": "string"
                          }
                        ],
                        "nullable": true
                      },
                      "priority": {
                        "anyOf": [
                          {
                            "type": "integer"
                          },
                          {
                            "type": "string"
                          }
                        ]
                      },
                      "pointType": {
                        "type": "string",
                        "enum": [
                          "delivery",
                          "stock_loading"
                        ]
                      },
                      "paymentMethod": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "deliveryDeadline": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      },
                      "timeWindows": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "startTime": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            },
                            "endTime": {
                              "type": "string",
                              "format": "date-time"
                            }
                          },
                          "required": [
                            "endTime"
                          ]
                        },
                        "maxItems": 10
                      },
                      "latitude": {
                        "type": "number",
                        "minimum": -90,
                        "maximum": 90
                      },
                      "longitude": {
                        "type": "number",
                        "minimum": -180,
                        "maximum": 180
                      },
                      "allowedCourierIds": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    },
                    "required": [
                      "name",
                      "address"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "stops": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 255
                            },
                            "address": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 500
                            },
                            "phone": {
                              "type": "string",
                              "maxLength": 50
                            },
                            "notes": {
                              "type": "string",
                              "maxLength": 2000
                            },
                            "products": {
                              "type": "string",
                              "maxLength": 2000
                            },
                            "serviceMinutes": {
                              "anyOf": [
                                {
                                  "type": "number",
                                  "exclusiveMinimum": true,
                                  "minimum": 0
                                },
                                {
                                  "type": "string"
                                }
                              ],
                              "nullable": true
                            },
                            "priority": {
                              "anyOf": [
                                {
                                  "type": "integer"
                                },
                                {
                                  "type": "string"
                                }
                              ]
                            },
                            "pointType": {
                              "type": "string",
                              "enum": [
                                "delivery",
                                "stock_loading"
                              ]
                            },
                            "paymentMethod": {
                              "type": "string",
                              "maxLength": 40
                            },
                            "deliveryDeadline": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            },
                            "timeWindows": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "startTime": {
                                    "type": "string",
                                    "format": "date-time",
                                    "nullable": true
                                  },
                                  "endTime": {
                                    "type": "string",
                                    "format": "date-time"
                                  }
                                },
                                "required": [
                                  "endTime"
                                ]
                              },
                              "maxItems": 10
                            },
                            "latitude": {
                              "type": "number",
                              "minimum": -90,
                              "maximum": 90
                            },
                            "longitude": {
                              "type": "number",
                              "minimum": -180,
                              "maximum": 180
                            },
                            "allowedCourierIds": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "format": "uuid"
                              }
                            }
                          },
                          "required": [
                            "name",
                            "address"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 1000
                      }
                    },
                    "required": [
                      "stops"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. A batch returns `stops` + `created`; a single object returns `stop`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "stop": {
                          "$ref": "#/components/schemas/Stop"
                        },
                        "stops": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Stop"
                          }
                        },
                        "created": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/stops/{stopId}": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "stopId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "tags": [
          "Delivery points"
        ],
        "summary": "Update a delivery point",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "address": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 50
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "products": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "serviceMinutes": {
                    "anyOf": [
                      {
                        "type": "number",
                        "exclusiveMinimum": true,
                        "minimum": 0
                      },
                      {
                        "type": "string"
                      }
                    ],
                    "nullable": true
                  },
                  "priority": {
                    "anyOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "pointType": {
                    "type": "string",
                    "enum": [
                      "delivery",
                      "stock_loading"
                    ]
                  },
                  "paymentMethod": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "deliveryDeadline": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true
                  },
                  "timeWindows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "startTime": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "endTime": {
                          "type": "string",
                          "format": "date-time"
                        }
                      },
                      "required": [
                        "endTime"
                      ]
                    },
                    "maxItems": 10
                  },
                  "latitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "longitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  },
                  "allowedCourierIds": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "stop": {
                          "$ref": "#/components/schemas/Stop"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Delivery points"
        ],
        "summary": "Delete a delivery point",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/stops/geocode": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Delivery points"
        ],
        "summary": "Geocode every address in the project",
        "description": "Resolves addresses that have no coordinates yet. Call this once after a bulk upload — the optimizer ignores stops without coordinates. Already-geocoded stops are left alone.",
        "responses": {
          "200": {
            "description": "Geocoding summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "result": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "geocoded": {
                              "type": "integer"
                            },
                            "failed": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/couriers": {
      "get": {
        "tags": [
          "Couriers"
        ],
        "summary": "List the company driver pool",
        "responses": {
          "200": {
            "description": "Couriers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "couriers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Courier"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Couriers"
        ],
        "summary": "Add a courier to the company",
        "description": "Creates the driver record only. To make the courier plannable, assign them to a project with `POST /projects/{projectId}/couriers`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "lastName": {
                    "type": "string",
                    "maxLength": 100,
                    "default": ""
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 50
                  }
                },
                "required": [
                  "firstName",
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "courier": {
                          "$ref": "#/components/schemas/Courier"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/couriers": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Couriers"
        ],
        "summary": "List courier assignments on a project",
        "responses": {
          "200": {
            "description": "Assignments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "couriers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CourierAssignment"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Couriers"
        ],
        "summary": "Assign a courier to the project",
        "description": "Shift hours are enforced as a hard limit unless `softTimeWindowMode` is true. `stopDurationMinutes` is the default per stop; a stop with its own `serviceMinutes` wins.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "courierId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "startAddress": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "endAddress": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "workStartTime": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$"
                  },
                  "workEndTime": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$"
                  },
                  "stopDurationMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1440
                  },
                  "softTimeWindowMode": {
                    "type": "boolean"
                  },
                  "breakDurationMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 600,
                    "nullable": true
                  },
                  "breakFrequencyMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1440,
                    "nullable": true
                  },
                  "loadLimits": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number"
                    },
                    "nullable": true
                  }
                },
                "required": [
                  "courierId",
                  "startAddress",
                  "endAddress"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Assigned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "courier": {
                          "$ref": "#/components/schemas/CourierAssignment"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/couriers/{assignmentId}": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "assignmentId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "tags": [
          "Couriers"
        ],
        "summary": "Update a courier assignment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "startAddress": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "endAddress": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "workStartTime": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$"
                  },
                  "workEndTime": {
                    "type": "string",
                    "pattern": "^\\d{2}:\\d{2}$"
                  },
                  "stopDurationMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1440
                  },
                  "softTimeWindowMode": {
                    "type": "boolean"
                  },
                  "breakDurationMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 600,
                    "nullable": true
                  },
                  "breakFrequencyMinutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1440,
                    "nullable": true
                  },
                  "loadLimits": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "number"
                    },
                    "nullable": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "courier": {
                          "$ref": "#/components/schemas/CourierAssignment"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Couriers"
        ],
        "summary": "Remove a courier from the project",
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "removed": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/optimize": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Optimization"
        ],
        "summary": "Optimize the project (billed)",
        "description": "Costs $1 per courier, charged to subscription credit then wallet. Nothing is charged if the run fails or produces no routes. Requires the `optimize` scope.\n\nSmall plans are solved inline and return `200` with the routes. Large plans return `202` with a `jobId` — poll `GET /optimization-jobs/{jobId}` or subscribe to the `optimization.completed` webhook.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "onlyAssignedPoints": {
                    "type": "boolean"
                  },
                  "freezeMode": {
                    "type": "string",
                    "enum": [
                      "none",
                      "lock_in_transit",
                      "lock_all_active",
                      "lock_courier_routes"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Solved inline.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "async": {
                          "type": "boolean",
                          "example": false
                        },
                        "routes": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Route"
                          }
                        },
                        "unassignedStops": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          },
                          "description": "Delivery points the optimizer could not fit into any route."
                        },
                        "courierCount": {
                          "type": "integer",
                          "description": "Couriers billed for this run."
                        },
                        "deliveryPointCount": {
                          "type": "integer"
                        },
                        "chargedUsd": {
                          "type": "number",
                          "description": "0 when nothing was charged (no routes produced)."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Accepted; solving in the background.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "async": {
                          "type": "boolean",
                          "example": true
                        },
                        "jobId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "courierCount": {
                          "type": "integer"
                        },
                        "deliveryPointCount": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/projects/{projectId}/routes": {
      "parameters": [
        {
          "name": "projectId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Optimization"
        ],
        "summary": "Read the current routes",
        "responses": {
          "200": {
            "description": "Routes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "routes": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Route"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/optimization-jobs/{jobId}": {
      "parameters": [
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Optimization"
        ],
        "summary": "Poll an async optimization job",
        "responses": {
          "200": {
            "description": "Job status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "job": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending",
                                "running",
                                "done",
                                "failed"
                              ]
                            },
                            "projectId": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "error": {
                              "type": "string",
                              "nullable": true
                            },
                            "createdAt": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints",
        "responses": {
          "200": {
            "description": "Endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "webhooks": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Webhook"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a webhook endpoint",
        "description": "The URL must be public https. The signing `secret` is returned **once** in this response — store it now; afterwards only the last 4 characters are visible. At most 5 endpoints per company.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "optimization.completed",
                        "optimization.failed"
                      ]
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created — save the secret.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "webhook": {
                          "$ref": "#/components/schemas/Webhook"
                        },
                        "secret": {
                          "type": "string",
                          "example": "whsec_…"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{webhookId}": {
      "parameters": [
        {
          "name": "webhookId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Update an endpoint",
        "description": "Setting `isActive: true` on an auto-disabled endpoint also clears its failure counter.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "optimization.completed",
                        "optimization.failed"
                      ]
                    },
                    "minItems": 1
                  },
                  "isActive": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "webhook": {
                          "$ref": "#/components/schemas/Webhook"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete an endpoint",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "deleted": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{webhookId}/test": {
      "parameters": [
        {
          "name": "webhookId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Queues a sample `optimization.completed` delivery so you can verify your signature check.",
        "responses": {
          "200": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "queued": {
                          "type": "boolean"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (see `code` and `details.issues`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, revoked or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Paid plan, plan limit or credit balance problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API key lacks the required scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found, or it belongs to another company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (120 requests/minute per key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Company API key, e.g. `efr_live_…`. Created under Settings → API."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "message": {
            "type": "string",
            "description": "Human-readable English text. Do not branch on it."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable code — branch on this.",
            "enum": [
              "API_KEY_MISSING",
              "API_KEY_INVALID",
              "API_KEY_REVOKED",
              "API_KEY_EXPIRED",
              "API_KEY_SCOPE_MISSING",
              "API_REQUIRES_PAID_PLAN",
              "INVALID_BODY",
              "INVALID_QUERY",
              "INVALID_PATH_PARAMS",
              "PROJECT_NOT_FOUND",
              "PROJECT_NOT_DRAFT",
              "STOP_NOT_FOUND",
              "PROJECT_COURIER_NOT_FOUND",
              "COMPANY_NOT_FOUND",
              "OPTIMIZATION_NO_STOPS",
              "OPTIMIZATION_NO_COURIERS",
              "JOB_NOT_FOUND",
              "WEBHOOK_NOT_FOUND",
              "WEBHOOK_URL_INVALID",
              "WEBHOOK_LIMIT_REACHED",
              "PROJECT_MONTHLY_LIMIT",
              "COURIER_PER_PROJECT_LIMIT",
              "DELIVERY_POINTS_PER_PROJECT_LIMIT",
              "DAILY_OPTIMIZATION_LIMIT_EXCEEDED",
              "OPTIMIZATION_PREREQUISITES_MISSING",
              "NO_ASSIGNED_POINTS",
              "GEOCODE_NO_RESULT",
              "INSUFFICIENT_BALANCE",
              "PROJECT_COURIER_INVALID_COURIER",
              "FOREIGN_KEY_CONSTRAINT",
              "BAD_REQUEST",
              "UNAUTHORIZED",
              "PAYMENT_REQUIRED",
              "FORBIDDEN",
              "NOT_FOUND",
              "CONFLICT",
              "GONE",
              "RATE_LIMITED",
              "INTERNAL_ERROR"
            ]
          },
          "details": {
            "type": "object",
            "description": "Extra context, e.g. `issues`, `limit`, `address`."
          }
        },
        "required": [
          "success",
          "message"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "example": "active"
          },
          "defaultStartDate": {
            "type": "string",
            "nullable": true,
            "example": "2026-10-01"
          },
          "defaultStartTime": {
            "type": "string",
            "nullable": true,
            "example": "08:00"
          },
          "defaultStartAddress": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Stop": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "products": {
            "type": "string",
            "nullable": true
          },
          "serviceMinutes": {
            "type": "number",
            "nullable": true,
            "description": "Normalised to minutes. Accepts \"90\", \"1:30\" or \"1 h 30 min\" on input."
          },
          "priority": {
            "type": "integer",
            "example": 0
          },
          "pointType": {
            "type": "string",
            "enum": [
              "delivery",
              "stock_loading"
            ]
          },
          "paymentMethod": {
            "type": "string",
            "nullable": true
          },
          "deliveryDeadline": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "timeWindows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "startTime": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "endTime": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "latitude": {
            "type": "number",
            "nullable": true
          },
          "longitude": {
            "type": "number",
            "nullable": true
          },
          "allowedCourierIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "sequence": {
            "type": "integer"
          }
        }
      },
      "Courier": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "isActive": {
            "type": "boolean"
          }
        }
      },
      "CourierAssignment": {
        "type": "object",
        "description": "A courier assigned to one project. Shift hours, depots, capacity and breaks live here — the optimizer reads this record, not the company-level courier.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Assignment id — used as `projectCourierId` on routes."
          },
          "courierId": {
            "type": "string",
            "format": "uuid"
          },
          "startAddress": {
            "type": "string",
            "nullable": true
          },
          "endAddress": {
            "type": "string",
            "nullable": true
          },
          "workStartTime": {
            "type": "string",
            "nullable": true,
            "example": "08:00:00",
            "description": "Returned as `HH:MM:SS`. Send it as `HH:MM`."
          },
          "workEndTime": {
            "type": "string",
            "nullable": true,
            "example": "17:00:00",
            "description": "Returned as `HH:MM:SS`. Send it as `HH:MM`."
          },
          "stopDurationMinutes": {
            "type": "integer",
            "nullable": true
          },
          "softTimeWindowMode": {
            "type": "boolean"
          },
          "loadLimits": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "nullable": true
          },
          "courier": {
            "$ref": "#/components/schemas/Courier"
          }
        }
      },
      "Route": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "projectCourierId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "totalStops": {
            "type": "integer"
          },
          "totalDistanceKm": {
            "type": "number",
            "nullable": true
          },
          "totalDurationMin": {
            "type": "number",
            "nullable": true
          },
          "totalServiceTimeMin": {
            "type": "number",
            "nullable": true
          },
          "optimizedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "stops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RouteStop"
            }
          }
        }
      },
      "RouteStop": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sequence": {
            "type": "integer",
            "description": "Visit order, starting at 1."
          },
          "customerId": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The delivery point this stop came from."
          },
          "customerName": {
            "type": "string",
            "nullable": true
          },
          "address": {
            "type": "string",
            "nullable": true
          },
          "latitude": {
            "type": "number",
            "nullable": true
          },
          "longitude": {
            "type": "number",
            "nullable": true
          },
          "serviceMinutes": {
            "type": "number",
            "nullable": true
          },
          "plannedArrivalAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_transit",
              "delivered",
              "skipped",
              "failed"
            ]
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "optimization.completed",
                "optimization.failed"
              ]
            }
          },
          "isActive": {
            "type": "boolean"
          },
          "secretHint": {
            "type": "string",
            "nullable": true,
            "example": "whsec_…eAYE",
            "description": "Last 4 characters only. The full secret is returned once, when the endpoint is created."
          },
          "consecutiveFailures": {
            "type": "integer"
          },
          "lastDeliveryAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastStatusCode": {
            "type": "integer",
            "nullable": true
          },
          "lastError": {
            "type": "string",
            "nullable": true
          },
          "disabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Set when EfiRoute auto-disabled the endpoint after too many consecutive failures."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEventPayload": {
        "type": "object",
        "description": "The JSON body POSTed to your endpoint.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Delivery id — use it to make handling idempotent."
          },
          "event": {
            "type": "string",
            "enum": [
              "optimization.completed",
              "optimization.failed"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "properties": {
              "projectId": {
                "type": "string",
                "format": "uuid"
              },
              "routeCount": {
                "type": "integer"
              },
              "deliveryPointCount": {
                "type": "integer"
              },
              "courierCount": {
                "type": "integer"
              },
              "chargedUsd": {
                "type": "number"
              },
              "jobId": {
                "type": "string",
                "format": "uuid",
                "nullable": true
              },
              "error": {
                "type": "string",
                "nullable": true,
                "description": "Present on `optimization.failed`."
              }
            }
          }
        }
      }
    }
  },
  "x-webhooks": {
    "optimization.completed": {
      "post": {
        "summary": "optimization.completed",
        "description": "Sent when a run finished and routes are ready.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEventPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx to acknowledge."
          }
        }
      }
    },
    "optimization.failed": {
      "post": {
        "summary": "optimization.failed",
        "description": "Sent when a background run ended with an error. Nothing was charged.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEventPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any 2xx to acknowledge."
          }
        }
      }
    }
  },
  "x-error-codes": [
    {
      "code": "API_KEY_MISSING",
      "status": 401,
      "meaning": "No `Authorization: Bearer` header."
    },
    {
      "code": "API_KEY_INVALID",
      "status": 401,
      "meaning": "Unknown key, or wrong prefix."
    },
    {
      "code": "API_KEY_REVOKED",
      "status": 401,
      "meaning": "The key was revoked in the dashboard."
    },
    {
      "code": "API_KEY_EXPIRED",
      "status": 401,
      "meaning": "The key passed its expiry date."
    },
    {
      "code": "API_KEY_SCOPE_MISSING",
      "status": 403,
      "meaning": "Key lacks `read`, `write` or `optimize`."
    },
    {
      "code": "API_REQUIRES_PAID_PLAN",
      "status": 402,
      "meaning": "API access needs the Pro or Business plan."
    },
    {
      "code": "INVALID_BODY",
      "status": 400,
      "meaning": "Body failed validation; `details.issues` lists each field."
    },
    {
      "code": "INVALID_QUERY",
      "status": 400,
      "meaning": "Query string failed validation."
    },
    {
      "code": "INVALID_PATH_PARAMS",
      "status": 400,
      "meaning": "A path id is not a UUID."
    },
    {
      "code": "PROJECT_NOT_FOUND",
      "status": 404,
      "meaning": "No such project for this company."
    },
    {
      "code": "PROJECT_NOT_DRAFT",
      "status": 400,
      "meaning": "Only a draft project can be deleted; PATCH its status to `cancelled` instead."
    },
    {
      "code": "STOP_NOT_FOUND",
      "status": 404,
      "meaning": "No such delivery point in this project."
    },
    {
      "code": "PROJECT_COURIER_NOT_FOUND",
      "status": 404,
      "meaning": "No such courier assignment on this project."
    },
    {
      "code": "COMPANY_NOT_FOUND",
      "status": 404,
      "meaning": "The company behind the key no longer exists."
    },
    {
      "code": "OPTIMIZATION_NO_STOPS",
      "status": 400,
      "meaning": "The project has no delivery points."
    },
    {
      "code": "OPTIMIZATION_NO_COURIERS",
      "status": 400,
      "meaning": "No courier is assigned to the project."
    },
    {
      "code": "JOB_NOT_FOUND",
      "status": 404,
      "meaning": "No such optimization job."
    },
    {
      "code": "WEBHOOK_NOT_FOUND",
      "status": 404,
      "meaning": "No such webhook endpoint."
    },
    {
      "code": "WEBHOOK_URL_INVALID",
      "status": 400,
      "meaning": "URL must be public https (no localhost/private ranges)."
    },
    {
      "code": "WEBHOOK_LIMIT_REACHED",
      "status": 400,
      "meaning": "At most 5 endpoints per company."
    },
    {
      "code": "PROJECT_MONTHLY_LIMIT",
      "status": 402,
      "meaning": "Monthly project quota for your plan is used up."
    },
    {
      "code": "COURIER_PER_PROJECT_LIMIT",
      "status": 402,
      "meaning": "Too many couriers on this project for your plan."
    },
    {
      "code": "DELIVERY_POINTS_PER_PROJECT_LIMIT",
      "status": 402,
      "meaning": "Too many delivery points for your plan."
    },
    {
      "code": "DAILY_OPTIMIZATION_LIMIT_EXCEEDED",
      "status": 402,
      "meaning": "Daily optimization cap reached."
    },
    {
      "code": "OPTIMIZATION_PREREQUISITES_MISSING",
      "status": 400,
      "meaning": "Project has no couriers, or no geocoded stops."
    },
    {
      "code": "NO_ASSIGNED_POINTS",
      "status": 400,
      "meaning": "`onlyAssignedPoints` was set but nothing is assigned."
    },
    {
      "code": "GEOCODE_NO_RESULT",
      "status": 400,
      "meaning": "The address could not be resolved; `details.address` says which."
    },
    {
      "code": "INSUFFICIENT_BALANCE",
      "status": 402,
      "meaning": "Not enough subscription credit + wallet balance."
    },
    {
      "code": "PROJECT_COURIER_INVALID_COURIER",
      "status": 400,
      "meaning": "The `courierId` you sent no longer exists or is inactive."
    },
    {
      "code": "FOREIGN_KEY_CONSTRAINT",
      "status": 400,
      "meaning": "An id in the request refers to a missing record."
    },
    {
      "code": "BAD_REQUEST",
      "status": 400,
      "meaning": "Generic validation/state failure with no more specific code."
    },
    {
      "code": "UNAUTHORIZED",
      "status": 401,
      "meaning": "Generic authentication failure."
    },
    {
      "code": "PAYMENT_REQUIRED",
      "status": 402,
      "meaning": "Generic plan/credit failure."
    },
    {
      "code": "FORBIDDEN",
      "status": 403,
      "meaning": "Generic authorization failure."
    },
    {
      "code": "NOT_FOUND",
      "status": 404,
      "meaning": "Generic missing resource."
    },
    {
      "code": "CONFLICT",
      "status": 409,
      "meaning": "The request conflicts with the current state."
    },
    {
      "code": "GONE",
      "status": 410,
      "meaning": "The resource is no longer available."
    },
    {
      "code": "RATE_LIMITED",
      "status": 429,
      "meaning": "Generic rate-limit rejection."
    },
    {
      "code": "INTERNAL_ERROR",
      "status": 500,
      "meaning": "Unexpected server error — safe to retry once."
    }
  ]
}