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