openapi: 3.0.0 info: title: Coram alerts reunification API description: "# Introduction\n\nThe Coram API enables seamless programmatic access to your security camera infrastructure, allowing you to build custom integrations and automate workflows.\n\n# Supported Actions\n\nManage and interact with your Coram platform resources:\n\n| Resource | Description |\n| -------- | ----------- |\n| **Camera Groups** | Organize and configure camera collections |\n| **Cameras** | Access camera settings, streams, and metadata |\n| **Locations** | Manage physical site configurations |\n| **NVRs** | Control and monitor network video recorders |\n| **Access Control** | List access control doors and trigger remote unlocks |\n\n# Required Headers\n\nEvery API request must include the following headers:\n\n| Header | Required | Description |\n| ------ | -------- | ----------- |\n| `X-Auth-Token` | Yes | Your API key (from the API key List Page) |\n\n**Example:**\n```\ncurl -X GET \"https://api.coram.ai/developer-api/v1/cameras\" \\\n -H \"X-Auth-Token: xxxxxxxxxxxxxxx\"\n```\n\n# Response Format\n\nAll API responses follow a consistent structure based on the operation result.\n\n## Success Response\n\n**List Operations** (e.g., `GET /v1/cameras`)\n\nReturns a `results` array with pagination support:\n\n```json\n{\n \"results\": [\n { \"id\": \"cam_001\", \"name\": \"Front Door\", ... },\n { \"id\": \"cam_002\", \"name\": \"Lobby\", ... }\n ],\n \"has_more\": true\n}\n```\n\nWhen `has_more` is `true`, additional results are available. Use pagination parameters to fetch more.\n\n**Bulk/Batch Operations** (e.g., `POST /v1/cameras`, `PATCH /v1/cameras`)\n\nReturns a top-level `status` field indicating whether the API request was processed, and a `results` array where each item reflects the outcome of an individual operation:\n\n```json\n{\n \"status\": \"success\",\n \"results\": [\n { \"status\": \"success\", \"data\": { \"id\": \"cam_001\", \"mac_address\": \"...\" } },\n { \"status\": \"error\", \"error\": { \"code\": \"VALIDATION_ERROR\", \"message\": \"...\" } }\n ]\n}\n```\n\n| Field | Description |\n| ----- | ----------- |\n| `status` (top-level) | `\"success\"` if the request was processed |\n| `results[].status` | `\"success\"` or `\"error\"` for each operation |\n| `results[].data` | Present when `status` is `\"success\"` |\n| `results[].error` | Present when `status` is `\"error\"` |\n\n## Error Response\n\nWhen an error occurs, the response includes `status` set to `\"error\"` along with detailed error information:\n\n```json\n{\n \"status\": \"error\",\n \"error\": {\n \"code\": \"VALIDATION_ERROR\",\n \"message\": \"Invalid MAC address format\",\n \"field\": \"mac_address\"\n }\n}\n```\n\n**Error Fields:**\n\n| Field | Type | Description |\n| ----- | ---- | ----------- |\n| `code` | string | Machine-readable error code |\n| `message` | string | Human-readable error description |\n| `field` | string | The field that caused the error (if applicable) |\n\n\n# HTTP Status Codes\n\nThe API uses standard HTTP status codes to indicate the success or failure of requests:\n\n## Success Codes\n\n| Code | Description |\n| ---- | ----------- |\n| `200 OK` | Request succeeded |\n| `201 Created` | Resource successfully created |\n| `204 No Content` | Request succeeded with no response body |\n\n## Client Error Codes\n\n| Code | Description |\n| ---- | ----------- |\n| `400 Bad Request` | Invalid request syntax or parameters |\n| `401 Unauthorized` | Missing or invalid API key |\n| `403 Forbidden` | Valid API key but insufficient permissions |\n| `404 Not Found` | Requested resource does not exist |\n| `422 Unprocessable Entity` | Validation error in request body |\n| `429 Too Many Requests` | Rate limit exceeded |\n\n## Server Error Codes\n\n| Code | Description |\n| ---- | ----------- |\n| `500 Internal Server Error` | Unexpected server error (rare) |\n| `502 Bad Gateway` | Upstream service unavailable |\n| `503 Service Unavailable` | Service temporarily unavailable |\n\n\n# Error Handling Best Practices\n\n1. **Always check the `status` field** — Verify if the response indicates `\"success\"` or `\"error\"`\n\n2. **Handle specific error codes** — Use the `error.code` field to handle different error types programmatically\n\n3. **Implement retry logic** — For `429` and `5xx` errors, implement exponential backoff\n\n4. **Log error details** — Capture `error.code`, `error.message`, and `error.field` for debugging\n\n**Example error handling:**\n```python\nresponse = api.create_camera(data)\n\nif response[\"status\"] == \"error\":\n error = response[\"error\"]\n if error[\"code\"] == \"VALIDATION_ERROR\":\n print(f\"Invalid {error['field']}: {error['message']}\")\n elif error[\"code\"] == \"DUPLICATE_RESOURCE\":\n print(\"Camera already exists\")\n else:\n print(f\"Error: {error['message']}\")\n```\n\n---\n\n# Getting Started\n\n1. **Generate an API key** from your Coram app settings\n2. **Explore the endpoints** in the navigation to begin integrating\n\nNeed help? Contact [support@coram.ai](mailto:support@coram.ai)\n" version: 1.0.0 servers: - url: https://developer.coram.ai description: Production security: - X-Auth-Token: [] tags: - name: reunification description: Bulk-import your school reunification roster — schools, students (with guardians), teachers, staff, and class sections — programmatically, the API equivalent of the in-app CSV upload. Each import is a full roster replace; poll the status endpoint for completion and partial-success counts. paths: /v1/reunification/imports: post: operationId: create_reunification_import summary: Import reunification roster data description: 'Bulk-imports reunification roster data — schools, students (with their guardians), teachers, and staff — as JSON. The programmatic equivalent of the in-app CSV upload. **This is a full roster replace, not a partial update.** Each call replaces the organization''s entire reunification roster: every person and school you send is inserted or updated, and **anything you omit is removed**. To keep someone, include them in every import; to remove someone, send a roster without them. Always send the complete roster — sending only `students`, for example, would delete all schools, teachers, and sections. To guard against that, the `schools`, `students`, `teachers`, and `sections` keys are **required to be present** (they may be empty arrays); `staff` is optional. This mirrors the in-app CSV upload, which requires every file. List each student, teacher, and staff member **once**. To place a person in multiple schools, put every school id in `school_ids` on that single object — a repeated `student_id`/`teacher_id`/`staff_id` is rejected (`400`), not merged. Cross-references are resolved against the ids in this same payload, so list an entity before you reference it. A `school_id`, `student_id`, or `teacher_id` that doesn''t resolve to something in the payload is silently skipped — the surrounding record is still imported, just without that link — so a mistyped id quietly drops a school membership or section rather than failing the import. Processing runs asynchronously and respects the same Auth0 rate limits as the in-app flow; only one import per organization runs at a time — a request made while one is already running returns `409`. The `202` response is the initial in-progress status — poll `GET /reunification/imports/status` for completion and partial-success counts. `school_location_mappings` maps each `school_id` in the payload to a Coram location ID. Requires `write:*` scope and an `admin` creator role; the feature must be enabled for the organization. ' tags: - reunification security: - X-Auth-Token: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReunificationImportInput' responses: '202': description: Import accepted and processing started content: application/json: schema: $ref: '#/components/schemas/ReunificationImportStatus' '400': description: Empty payload, missing school_location_mappings, or invalid data content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '401': description: Missing or invalid API key content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '403': description: API key lacks the required scope/role, or the feature is not enabled for the organization content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '409': description: An import is already running for this organization; poll the existing one instead content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '500': description: Unexpected server error content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' x-public-api: true x-public-api-version: v1 parameters: [] /v1/reunification/imports/status: get: operationId: get_reunification_import_status summary: Get the current reunification import status description: 'Returns the status of the organization''s most recent reunification import, including per-entity synced/total counts and the `succeeded_users` / `failed_users` provisioning figures. Poll this after starting an import. Requires `read:*` scope and a `regular` creator role. Note: `succeeded_users` and `failed_users` are eventually consistent. Guest/staff accounts are provisioned in the background after the roster is imported, so these counts keep climbing for a few seconds after `status` becomes `completed`. Treat `completed` as "roster imported" and keep polling the user counts until they stop changing. `completed` means the roster was imported and account provisioning was dispatched — it does **not** by itself mean every account succeeded. Check `failed_users`: a non-zero value on a `completed` import is a partial success (e.g. seat limits or Auth0 rejections for some users). ' tags: - reunification security: - X-Auth-Token: [] responses: '200': description: Successful Response content: application/json: schema: $ref: '#/components/schemas/ReunificationImportStatus' '401': description: Missing or invalid API key content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '403': description: API key lacks the required scope/role, or the feature is not enabled for the organization content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '404': description: No import found for this organization content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' '500': description: Unexpected server error content: application/json: schema: $ref: '#/components/schemas/AuthErrorResponse' x-public-api: true x-public-api-version: v1 parameters: [] components: schemas: ReunificationSection: type: object required: - section_id description: A class/section linking students to teachers (drives student↔teacher relationships). properties: section_id: type: string school_id: type: string teacher_ids: type: array items: type: string student_ids: type: array items: type: string ReunificationSchool: type: object required: - school_id - name properties: school_id: type: string description: Your identifier for the school; referenced by students/teachers/staff and school_location_mappings. name: type: string sis_id: type: string address: type: string city: type: string state: type: string zip: type: string ReunificationTeacher: type: object required: - teacher_id - name - school_ids properties: teacher_id: type: string name: type: string sis_id: type: string email: type: string phone_number: type: string school_ids: type: array items: type: string ReunificationImportInput: type: object required: - schools - students - teachers - sections - school_location_mappings description: 'Full-replace roster payload. `schools`, `students`, `teachers`, and `sections` must be present (may be empty arrays) so an omitted key can''t silently delete that entity type; `staff` is optional. Phone numbers are normalized to E.164; a number that can''t be parsed for the region is dropped (stored as null), not rejected — so a malformed guardian contact number is silently lost rather than failing the import. ' properties: schools: type: array items: $ref: '#/components/schemas/ReunificationSchool' students: type: array items: $ref: '#/components/schemas/ReunificationStudent' teachers: type: array items: $ref: '#/components/schemas/ReunificationTeacher' staff: type: array items: $ref: '#/components/schemas/ReunificationStaff' sections: type: array description: Class/section memberships that establish student↔teacher relationships. items: $ref: '#/components/schemas/ReunificationSection' school_location_mappings: type: object additionalProperties: type: integer format: int32 description: Maps each school_id in the payload to a Coram location ID. ReunificationStaff: type: object required: - staff_id - name - school_ids properties: staff_id: type: string name: type: string sis_id: type: string email: type: string phone_number: type: string title: type: string department: type: string roles: type: array items: type: string school_ids: type: array items: type: string ReunificationStudent: type: object required: - student_id - name - school_ids properties: student_id: type: string name: type: string sis_id: type: string email: type: string phone_number: type: string grade: type: string school_ids: type: array description: School IDs the student belongs to. The first is the primary school; the rest are secondary. items: type: string guardians: type: array items: $ref: '#/components/schemas/ReunificationGuardian' AuthErrorResponse: type: object required: - detail properties: detail: type: string description: 'Human-readable error description. Matches the shape FastAPI uses for `HTTPException` responses, so existing code that parses the Python developer-api''s auth errors works unchanged. ' ReunificationGuardian: type: object required: - name properties: name: type: string email: type: string phone: type: string relationship: type: string ReunificationImportStatus: type: object required: - id - status description: 'Progress and partial-success report for a reunification import. Counts are cumulative for the current/most-recent import. ' properties: id: type: string status: type: string enum: - in_progress - completed - failed synced_schools: type: integer format: int32 default: 0 total_schools: type: integer format: int32 default: 0 synced_students: type: integer format: int32 default: 0 total_students: type: integer format: int32 default: 0 synced_teachers: type: integer format: int32 default: 0 total_teachers: type: integer format: int32 default: 0 synced_staff: type: integer format: int32 default: 0 total_staff: type: integer format: int32 default: 0 synced_guardians: type: integer format: int32 default: 0 total_guardians: type: integer format: int32 default: 0 succeeded_users: type: integer format: int32 default: 0 failed_users: type: integer format: int32 default: 0 error_message: type: string started_at: type: string format: date-time completed_at: type: string format: date-time securitySchemes: X-Auth-Token: type: apiKey in: header name: X-Auth-Token x-tagGroups: - name: Surveillance tags: - cameras - camera-groups - locations - nvrs - alerts - name: Access Control tags: - doors - events - name: Emergency Management System tags: - reunification