{
 "openapi": "3.0.3",
 "info": {
  "title": "ResponseOne Dispatch API",
  "version": "1.0.0",
  "description": "Connect any CAD / dispatch system to ResponseOne. Send a call to a unit and it opens in that crew's ResponseOne app as a new trip with the route ready; read where your on-duty units are; get signed webhooks when a unit's status changes (en route, on scene, transporting, available).\n\nStaging environment. Each company gets its own API keys (Admin page > Dispatch API). Keys only see their own company's units.",
  "contact": {
   "name": "ResponseOne",
   "email": "mordyspira@gmail.com"
  }
 },
 "servers": [
  {
   "url": "https://responseone-test-api.baishoroa.workers.dev/v1",
   "description": "Staging"
  }
 ],
 "security": [
  {
   "bearerAuth": []
  }
 ],
 "tags": [
  {
   "name": "Units"
  },
  {
   "name": "Assignments"
  },
  {
   "name": "Webhooks"
  },
  {
   "name": "Account"
  }
 ],
 "paths": {
  "/me": {
   "get": {
    "tags": [
     "Account"
    ],
    "summary": "Check your key",
    "operationId": "getMe",
    "responses": {
     "200": {
      "description": "The company and key this request used",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "company": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "name": {
             "type": "string"
            }
           }
          },
          "key": {
           "type": "object",
           "properties": {
            "id": {
             "type": "string"
            },
            "name": {
             "type": "string"
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/units": {
   "get": {
    "tags": [
     "Units"
    ],
    "summary": "List on-duty units",
    "description": "Only units that are ON DUTY are listed. Off-duty units share no location, by design.",
    "operationId": "listUnits",
    "responses": {
     "200": {
      "description": "On-duty units",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "units": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Unit"
           }
          },
          "onDutyOnly": {
           "type": "boolean"
          },
          "at": {
           "type": "string",
           "format": "date-time"
          }
         }
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/units/{unitId}": {
   "get": {
    "tags": [
     "Units"
    ],
    "summary": "Get one unit",
    "operationId": "getUnit",
    "parameters": [
     {
      "name": "unitId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The unit (status off_duty with no position when it is off duty)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "unit": {
           "$ref": "#/components/schemas/Unit"
          }
         }
        }
       }
      }
     },
     "404": {
      "$ref": "#/components/responses/NotFound"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/assignments": {
   "post": {
    "tags": [
     "Assignments"
    ],
    "summary": "Send a call to a unit",
    "description": "Creates an assignment. Within about 10 seconds the unit's app shows the call with the route ready (one tap to start). A newer assignment to the same unit replaces an open one (the old one becomes superseded). Send an Idempotency-Key so a retry never creates a second call.",
    "operationId": "createAssignment",
    "parameters": [
     {
      "name": "Idempotency-Key",
      "in": "header",
      "required": false,
      "schema": {
       "type": "string",
       "maxLength": 100,
       "pattern": "^[A-Za-z0-9._:-]{1,100}$"
      },
      "description": "Your unique id for this request, e.g. the CAD incident + unit. Retrying with the same key returns the original assignment (header Idempotent-Replayed: true)."
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/AssignmentCreate"
       },
       "example": {
        "unitId": "u_123",
        "externalId": "CAD-2026-114233",
        "address": "1124 Avenue L, Brooklyn, NY",
        "priority": "emergency",
        "notes": "Side entrance, 2nd floor. Elderly male, difficulty breathing."
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Created",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "assignment": {
           "$ref": "#/components/schemas/Assignment"
          },
          "warning": {
           "type": "string",
           "description": "Present when the unit is off duty: it gets the call when it goes on duty."
          }
         }
        }
       }
      }
     },
     "200": {
      "description": "Replay of an earlier request with the same Idempotency-Key",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "assignment": {
           "$ref": "#/components/schemas/Assignment"
          }
         }
        }
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/BadRequest"
     },
     "404": {
      "$ref": "#/components/responses/NotFound"
     },
     "409": {
      "description": "Idempotency-Key reused with a different body, or unit login turned off",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   },
   "get": {
    "tags": [
     "Assignments"
    ],
    "summary": "List assignments",
    "operationId": "listAssignments",
    "parameters": [
     {
      "name": "status",
      "in": "query",
      "schema": {
       "type": "string",
       "enum": [
        "open",
        "all"
       ],
       "default": "open"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 100,
       "default": 25
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Newest first",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "assignments": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Assignment"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/assignments/{assignmentId}": {
   "get": {
    "tags": [
     "Assignments"
    ],
    "summary": "Get an assignment",
    "operationId": "getAssignment",
    "parameters": [
     {
      "name": "assignmentId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The assignment",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "assignment": {
           "$ref": "#/components/schemas/Assignment"
          }
         }
        }
       }
      }
     },
     "404": {
      "$ref": "#/components/responses/NotFound"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   },
   "delete": {
    "tags": [
     "Assignments"
    ],
    "summary": "Cancel an assignment",
    "operationId": "cancelAssignment",
    "parameters": [
     {
      "name": "assignmentId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Cancelled (or already closed)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "assignment": {
           "$ref": "#/components/schemas/Assignment"
          }
         }
        }
       }
      }
     },
     "404": {
      "$ref": "#/components/responses/NotFound"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/webhooks": {
   "get": {
    "tags": [
     "Webhooks"
    ],
    "summary": "List webhooks",
    "operationId": "listWebhooks",
    "responses": {
     "200": {
      "description": "Active webhooks",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "webhooks": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Webhook"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   },
   "post": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Add a webhook",
    "description": "ResponseOne POSTs events to this https URL, signed with the secret returned here (shown once).",
    "operationId": "createWebhook",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "url"
        ],
        "properties": {
         "url": {
          "type": "string",
          "format": "uri",
          "example": "https://cad.example.com/hooks/responseone"
         },
         "events": {
          "type": "array",
          "items": {
           "type": "string",
           "enum": [
            "unit.status_changed",
            "assignment.updated"
           ]
          }
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Created; save the secret",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "webhook": {
           "allOf": [
            {
             "$ref": "#/components/schemas/Webhook"
            },
            {
             "type": "object",
             "properties": {
              "secret": {
               "type": "string",
               "example": "whsec_\u2026"
              }
             }
            }
           ]
          }
         }
        }
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/BadRequest"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/webhooks/{webhookId}": {
   "delete": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Remove a webhook",
    "operationId": "deleteWebhook",
    "parameters": [
     {
      "name": "webhookId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Removed"
     },
     "404": {
      "$ref": "#/components/responses/NotFound"
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/webhooks/{webhookId}/test": {
   "post": {
    "tags": [
     "Webhooks"
    ],
    "summary": "Send a test event",
    "operationId": "testWebhook",
    "parameters": [
     {
      "name": "webhookId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Delivery result",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "delivered": {
           "type": "boolean"
          },
          "status": {
           "type": "integer",
           "description": "HTTP status your endpoint answered (0 = no answer)"
          },
          "eventId": {
           "type": "string"
          }
         }
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  },
  "/closures": {
   "get": {
    "tags": [
     "Units"
    ],
    "summary": "Streets your crews blocked",
    "description": "Active road closures reported by your company's crews (shared to every unit; each expires after its hours, max 12).",
    "operationId": "listClosures",
    "responses": {
     "200": {
      "description": "Active closures",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "closures": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "id": {
              "type": "string"
             },
             "street": {
              "type": "string"
             },
             "from": {
              "type": "string",
              "nullable": true
             },
             "to": {
              "type": "string",
              "nullable": true
             },
             "point": {
              "type": "object",
              "properties": {
               "lat": {
                "type": "number"
               },
               "lng": {
                "type": "number"
               }
              }
             },
             "line": {
              "type": "array",
              "items": {
               "type": "object",
               "properties": {
                "lat": {
                 "type": "number"
                },
                "lng": {
                 "type": "number"
                }
               }
              }
             },
             "reportedBy": {
              "type": "string",
              "nullable": true
             },
             "createdAt": {
              "type": "string",
              "format": "date-time"
             },
             "expiresAt": {
              "type": "string",
              "format": "date-time"
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/Unauthorized"
     },
     "429": {
      "$ref": "#/components/responses/RateLimited"
     }
    }
   }
  }
 },
 "webhooks": {
  "unit.status_changed": {
   "post": {
    "summary": "A unit's status changed",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/Event"
       },
       "example": {
        "id": "evt_9f2c41d0a3b8e771",
        "type": "unit.status_changed",
        "createdAt": "2026-11-04T03:10:00.000Z",
        "data": {
         "unitId": "u_123",
         "unitName": "Medic 12",
         "status": "on_scene",
         "previousStatus": "en_route",
         "assignmentId": "asg_1a2b3c4d5e6f7a8b",
         "externalId": "CAD-2026-114233",
         "position": {
          "lat": 40.6262,
          "lng": -73.9442
         },
         "source": "auto"
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Return any 2xx quickly"
     }
    }
   }
  },
  "assignment.updated": {
   "post": {
    "summary": "An assignment changed (pending, delivered, accepted, completed, cancelled, superseded)",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/Event"
       },
       "example": {
        "id": "evt_77aa01bc2d3e4f50",
        "type": "assignment.updated",
        "createdAt": "2026-11-04T03:09:12.000Z",
        "data": {
         "assignmentId": "asg_1a2b3c4d5e6f7a8b",
         "externalId": "CAD-2026-114233",
         "unitId": "u_123",
         "unitName": "Medic 12",
         "status": "delivered"
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Return any 2xx quickly"
     }
    }
   }
  }
 },
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "bearerFormat": "r1k_<id>_<secret>",
    "description": "Company API key from Admin > Dispatch API. Shown once; stored only as a hash."
   }
  },
  "schemas": {
   "Unit": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "name": {
      "type": "string",
      "example": "Medic 12"
     },
     "status": {
      "type": "string",
      "enum": [
       "available",
       "en_route",
       "on_scene",
       "transporting",
       "off_duty"
      ]
     },
     "onDutySince": {
      "type": "string",
      "format": "date-time",
      "nullable": true
     },
     "position": {
      "type": "object",
      "nullable": true,
      "properties": {
       "lat": {
        "type": "number"
       },
       "lng": {
        "type": "number"
       },
       "heading": {
        "type": "number",
        "nullable": true
       },
       "speedMps": {
        "type": "number",
        "nullable": true
       },
       "updatedAt": {
        "type": "string",
        "format": "date-time"
       },
       "stale": {
        "type": "boolean",
        "description": "true when the last fix is over 10 minutes old (no signal)"
       }
      }
     },
     "destination": {
      "type": "object",
      "nullable": true,
      "properties": {
       "label": {
        "type": "string"
       },
       "lat": {
        "type": "number",
        "nullable": true
       },
       "lng": {
        "type": "number",
        "nullable": true
       }
      }
     },
     "etaAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
     },
     "assignmentId": {
      "type": "string",
      "nullable": true
     }
    }
   },
   "AssignmentCreate": {
    "type": "object",
    "required": [
     "unitId"
    ],
    "description": "Send address, or lat+lng, or both (both is best: the pin is exact and the crew sees the address).",
    "properties": {
     "unitId": {
      "type": "string"
     },
     "externalId": {
      "type": "string",
      "maxLength": 64,
      "description": "Your CAD incident number; echoed in every webhook"
     },
     "address": {
      "type": "string",
      "maxLength": 200
     },
     "lat": {
      "type": "number",
      "minimum": -90,
      "maximum": 90
     },
     "lng": {
      "type": "number",
      "minimum": -180,
      "maximum": 180
     },
     "priority": {
      "type": "string",
      "enum": [
       "emergency",
       "urgent",
       "routine"
      ],
      "default": "emergency"
     },
     "notes": {
      "type": "string",
      "maxLength": 500,
      "description": "Shown to the crew (entrance, floor, complaint)"
     }
    }
   },
   "Assignment": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "example": "asg_1a2b3c4d5e6f7a8b"
     },
     "unitId": {
      "type": "string"
     },
     "unitName": {
      "type": "string"
     },
     "externalId": {
      "type": "string",
      "nullable": true
     },
     "status": {
      "type": "string",
      "enum": [
       "pending",
       "delivered",
       "accepted",
       "completed",
       "cancelled",
       "superseded"
      ],
      "description": "pending = waiting for the app; delivered = on the crew's screen; accepted = crew started the route; completed = unit back available"
     },
     "priority": {
      "type": "string"
     },
     "address": {
      "type": "string",
      "nullable": true
     },
     "location": {
      "type": "object",
      "nullable": true,
      "properties": {
       "lat": {
        "type": "number"
       },
       "lng": {
        "type": "number"
       }
      }
     },
     "notes": {
      "type": "string",
      "nullable": true
     },
     "createdAt": {
      "type": "string",
      "format": "date-time"
     },
     "deliveredAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
     },
     "acceptedAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
     },
     "closedAt": {
      "type": "string",
      "format": "date-time",
      "nullable": true
     },
     "updatedAt": {
      "type": "string",
      "format": "date-time"
     }
    }
   },
   "Webhook": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "url": {
      "type": "string"
     },
     "events": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "createdAt": {
      "type": "string",
      "format": "date-time"
     },
     "lastStatus": {
      "type": "integer",
      "nullable": true
     },
     "lastDeliveryAt": {
      "type": "string",
      "nullable": true
     },
     "failuresInARow": {
      "type": "integer"
     }
    }
   },
   "Event": {
    "type": "object",
    "description": "Headers: X-ResponseOne-Event (type), X-ResponseOne-Delivery (event id), X-ResponseOne-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your webhook secret>. Reject if t is more than 5 minutes old. One retry after a 5xx or no answer.",
    "properties": {
     "id": {
      "type": "string"
     },
     "type": {
      "type": "string"
     },
     "createdAt": {
      "type": "string",
      "format": "date-time"
     },
     "test": {
      "type": "boolean"
     },
     "data": {
      "type": "object"
     }
    }
   },
   "Error": {
    "type": "object",
    "properties": {
     "error": {
      "type": "object",
      "properties": {
       "code": {
        "type": "string"
       },
       "message": {
        "type": "string"
       }
      }
     }
    }
   }
  },
  "responses": {
   "Unauthorized": {
    "description": "Missing, wrong or revoked key",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "RateLimited": {
    "description": "120 requests/minute per key, 1,000 assignments per company per 24 h",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    },
    "headers": {
     "Retry-After": {
      "schema": {
       "type": "integer"
      }
     }
    }
   },
   "NotFound": {
    "description": "Not found in your company",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "BadRequest": {
    "description": "Invalid request",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   }
  }
 }
}