Coram Ai Reunification API

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.

Operations 2

POST /v1/reunification/imports Import reunification roster data #
GET /v1/reunification/imports/status Get the current reunification import status #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/coram-ai:coram-ai-reunification-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

coram-ai-reunification-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Coram Reunification API
  description: '# Introduction


    The Coram API enables seamless programmatic access to your security camera infrastructure, allowing you to build custom integrations and automate workflows. # Supported Actions


    Manage and interact with your Coram platform resources:


    | Resource | Description |

    | -------- | ----------- |

    | **Camera Groups** | Organize and configure camera collections |

    | **Cameras** | Access camera settings, streams, and metadata |

    | **Locations** | Manage physical site configurations |

    | **NVRs** | Control and monitor network video recorders |

    | **Access Control** | Manage doors, cards, and permissions |


    ## Access Control


    Card and permission changes are saved before they reach controllers. If a request fails, check the saved state before retrying. # Required Headers


    Every API request must include the following headers:


    | Header | Required | Description |

    | ------ | -------- | ----------- |

    | `X-Auth-Token` | Yes | Your API key (from the API key List Page) |


    **Example:**

    ```

    curl -X GET "https://api.coram.ai/developer-api/v1/cameras" \

    -H "X-Auth-Token: xxxxxxxxxxxxxxx"

    ```


    # Response Format


    All API responses follow a consistent structure based on the operation result. ## Success Response


    **List Operations** (e.g., `GET /v1/cameras`)


    Returns a `results` array with pagination support:


    ```json

    {

    "results": [

    { "id": "cam_001", "name": "Front Door", ... },

    { "id": "cam_002", "name": "Lobby", ... }

    ],

    "has_more": true

    }

    ```


    When `has_more` is `true`, additional results are available. Use pagination parameters to fetch more. **Bulk/Batch Operations** (e.g., `POST /v1/cameras`, `PATCH /v1/cameras`)


    Returns 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:


    ```json

    {

    "status": "success",

    "results": [

    { "status": "success", "data": { "id": "cam_001", "mac_address": "..." } },

    { "status": "error", "error": { "code": "VALIDATION_ERROR", "message": "..." } }

    ]

    }

    ```


    | Field | Description |

    | ----- | ----------- |

    | `status` (top-level) | `"success"` if the request was processed |

    | `results[].status` | `"success"` or `"error"` for each operation |

    | `results[].data` | Present when `status` is `"success"` |

    | `results[].error` | Present when `status` is `"error"` |


    ## Error Response


    When an error occurs, the response includes `status` set to `"error"` along with detailed error information:


    ```json

    {

    "status": "error",

    "error": {

    "code": "VALIDATION_ERROR",

    "message": "Invalid MAC address format",

    "field": "mac_address"

    }

    }

    ```


    **Error Fields:**


    | Field | Type | Description |

    | ----- | ---- | ----------- |

    | `code` | string | Machine-readable error code |

    | `message` | string | Human-readable error description |

    | `field` | string | The field that caused the error (if applicable) |


    # HTTP Status Codes


    The API uses standard HTTP status codes to indicate the success or failure of requests:


    ## Success Codes


    | Code | Description |

    | ---- | ----------- |

    | `200 OK` | Request succeeded |

    | `201 Created` | Resource successfully created |

    | `204 No Content` | Request succeeded with no response body |


    ## Client Error Codes


    | Code | Description |

    | ---- | ----------- |

    | `400 Bad Request` | Invalid request syntax or parameters |

    | `401 Unauthorized` | Missing or invalid API key |

    | `403 Forbidden` | Valid API key but insufficient permissions |

    | `404 Not Found` | Requested resource does not exist |

    | `422 Unprocessable Entity` | Validation error in request body |

    | `429 Too Many Requests` | Rate limit exceeded |


    ## Server Error Codes


    | Code | Description |

    | ---- | ----------- |

    | `500 Internal Server Error` | Unexpected server error (rare) |

    | `502 Bad Gateway` | Upstream service unavailable |

    | `503 Service Unavailable` | Service temporarily unavailable |


    # Error Handling Best Practices


    1. **Always check the `status` field** — Verify if the response indicates `"success"` or `"error"`


    2.'
  version: 1.0.0
servers:
- url: https://developer.coram.ai
  description: Production
security:
- X-Auth-Token: []
tags:
- name: reunification
  description: Import your reunification roster of schools, students with their guardians, teachers, staff and class sections. This is the API equivalent of the in-app CSV upload. Every import replaces the roster rows that came from this API or the CSV upload, so anyone you leave out is removed. A roster synced from Clever is not touched. Imports run in the background, so poll the status endpoint for progress.
paths:
  /v1/reunification/imports:
    post:
      operationId: create_reunification_import
      summary: Import reunification roster data
      description: 'Imports a reunification roster as JSON. This is the API

        equivalent of the in-app CSV upload and writes to the same roster.


        **Every call replaces the whole roster.** Records you send are created

        or updated, and anything you leave out is deleted. Only records that

        came from this API or the in-app CSV upload are replaced.


        Deleting someone revokes the Coram access an earlier import gave them.

        Access granted by hand survives. Revocation is also skipped when a previous

        import has not finished creating accounts yet, and when more than half

        of a provisioned roster of 10 or more people would lose access at once,

        which stops a truncated upload locking out a school.


        Send every key on every call. The one you may omit is `staff`, and

        omitting it deletes every staff member.


        The import aborts and leaves the old roster intact if two schools map to

        the same Coram location, if a location id does not exist, or if a

        student or teacher repeats a school id in `school_ids` after the first

        one. Repeating a `student_id`, `teacher_id` or `staff_id` returns `400`

        before any work starts.


        The order of the top level arrays does not matter.


        The import runs in the background and is cancelled after one hour. The

        `202` carries the initial status. Poll

        `GET /v1/reunification/imports/status`. One import runs per organization

        at a time and the in-app CSV upload counts as one, so a second request

        returns `409`.


        Requires `write:*` scope and an `admin` creator role. The status

        endpoint needs `read:*`, so a write-only key can start an import but

        never see whether it worked. Requests are limited to an average of 5

        per second with a short burst allowance, and the body to 32 MiB.'
      tags:
      - reunification
      security:
      - X-Auth-Token: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReunificationImportInput'
            example:
              schools:
              - school_id: SCH-01
                name: Lincoln High School
              students:
              - student_id: STU-1001
                name: Ada Reyes
                email: ada.reyes@example.edu
                grade: '9'
                school_ids:
                - SCH-01
                guardians:
                - name: Marta Reyes
                  email: marta.reyes@example.com
                  phone: '+14155550101'
                  relationship: Mother
              teachers:
              - teacher_id: TCH-200
                name: Sam Okafor
                email: sam.okafor@example.edu
                school_ids:
                - SCH-01
              staff:
              - staff_id: STF-300
                name: Dana Whitfield
                email: dana.whitfield@example.edu
                title: Front Office
                school_ids:
                - SCH-01
              sections:
              - section_id: SEC-9A
                teacher_ids:
                - TCH-200
                student_ids:
                - STU-1001
              school_location_mappings:
                SCH-01: 42
      responses:
        '202':
          description: Import accepted and processing started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReunificationImportStatus'
        '400':
          description: Body is not a JSON object, omits or nulls any of schools/students/teachers/sections, has an empty school_location_mappings, repeats a student/teacher/staff id, or exceeds 32 MiB
          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
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthErrorResponse'
        '409':
          description: An import is already running for this organization, including one started by the in-app CSV upload. Poll the existing one instead. The lock clears after one hour at the latest
          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 your organization''s most recent reunification

        import. Poll this after starting one. Requires `read:*` scope and a

        `regular` creator role.


        Status is kept for one hour after its last update and then this returns

        `404`. Store anything you need to keep.


        `completed` means the roster was written and account provisioning was

        handed to a background worker, not that every account exists.

        `succeeded_users` and `failed_users` keep rising afterwards, which can

        take several minutes on a large roster, so poll until both stop moving.


        The `synced_*` and `total_*` pairs are a progress bar rather than a

        success ratio. They end up equal whenever the roster is written, so they

        cannot tell you a record was rejected.


        A `failed` status does not mean the roster is untouched. If the failure

        came after the roster was committed the new roster is live, and account

        provisioning may not have started or may already be under way.'
      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
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthErrorResponse'
        '404':
          description: No import found for this organization, or the status expired. Status is kept for one hour after its last update
          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:
    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.

            '
    ReunificationSection:
      type: object
      required:
      - section_id
      description: A class that pairs students with teachers. Sections build those pairings and are not stored as records you can look up later.
      properties:
        section_id:
          type: string
          description: Must not be blank or the section is dropped. Never stored, so nothing can look it up.
        school_id:
          type: string
          description: Accepted and ignored. Sections take their schools from the people in them.
        teacher_ids:
          type: array
          description: A teacher id that does not resolve drops that teacher's pairings in this section without error.
          items:
            type: string
        student_ids:
          type: array
          description: A student id that does not resolve drops that student's pairings in this section without error.
          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. A blank value drops the school silently, and with it every reference to it. Do not send the same school_id twice.
        name:
          type: string
          description: Not validated. A blank name is accepted and shows as a nameless school in Coram.
        sis_id:
          type: string
          description: Stored and displayed only. Nothing matches or de-duplicates on it.
        address:
          type: string
        city:
          type: string
        state:
          type: string
        zip:
          type: string
    ReunificationGuardian:
      type: object
      required:
      - name
      description: Guardians are contacts only and never receive a Coram account. Two guardians sharing an email address, or a phone number when there is no email, are merged into one guardian across the whole payload.
      properties:
        name:
          type: string
          description: A guardian with a blank name is discarded along with their contact details.
        email:
          type: string
        phone:
          type: string
        relationship:
          type: string
          description: Free text. Stored as "Other" when omitted or blank.
    ReunificationStudent:
      type: object
      required:
      - student_id
      - name
      - school_ids
      properties:
        student_id:
          type: string
          description: Your identifier for the student. A blank value drops the student silently. Stored as the student's card number and visible in Coram, so do not put anything sensitive here.
        name:
          type: string
          description: Not validated. A blank name is accepted and stored empty.
        sis_id:
          type: string
          description: Stored and displayed only. Nothing matches or de-duplicates on it.
        email:
          type: string
          description: The identity Coram uses for this person. They are still imported and still counted without a valid one, but they get no Coram account, appear in neither user counter, and lose any access an earlier import gave them. Two people sharing an email collapse into one Coram account.
        phone_number:
          type: string
        grade:
          type: string
        school_ids:
          type: array
          description: Schools the student belongs to, in order. The first one that resolves becomes the primary school, so a typo in the first position silently promotes the next. Repeating that first id is ignored, but repeating any other aborts the whole import. A student whose school_ids all fail to resolve is imported with no location access.
          items:
            type: string
        guardians:
          type: array
          items:
            $ref: '#/components/schemas/ReunificationGuardian'
    ReunificationImportInput:
      type: object
      required:
      - schools
      - students
      - teachers
      - sections
      - school_location_mappings
      description: '`schools`, `students`, `teachers` and `sections` must be present, and may

        be empty arrays. Sending one as `null` is rejected the same way as leaving

        it out, because both would wipe everything that key covers. Only `staff`

        may be left out entirely.


        Phone numbers are normalized to E.164 against the deployment''s own

        region, currently the United States. A number that cannot be parsed is

        stored as null rather than rejected, so include a country code.


        Unrecognized keys are dropped without error, so a misspelled field name

        fails silently.

        '
      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: An empty array removes every student to teacher pairing on the roster.
          items:
            $ref: '#/components/schemas/ReunificationSection'
        school_location_mappings:
          type: object
          additionalProperties:
            type: integer
            format: int32
          description: Maps your school ids to Coram location IDs. Must be present and non-empty. Give every school an entry, because a school without one is imported with no location and everyone attached only to it loses access. Two schools may not point at the same location, and a location that does not exist in your organization aborts the import after it has already returned 202. The location id in the example is illustrative, so look up your real ones with `GET /v1/locations` before your first call.
    ReunificationTeacher:
      type: object
      required:
      - teacher_id
      - name
      - school_ids
      properties:
        teacher_id:
          type: string
          description: A blank value drops the teacher silently.
        name:
          type: string
        sis_id:
          type: string
        email:
          type: string
          description: Without a valid one the teacher is still imported but gets no Coram account and loses any access an earlier import gave them.
        phone_number:
          type: string
        school_ids:
          type: array
          description: The first one that resolves becomes the primary school. Repeating that first id is ignored, but repeating any other aborts the whole import.
          items:
            type: string
    ReunificationStaff:
      type: object
      required:
      - staff_id
      - name
      - school_ids
      description: Omitting the whole staff array deletes every staff member on the roster.
      properties:
        staff_id:
          type: string
          description: A blank value drops the staff member silently.
        name:
          type: string
        sis_id:
          type: string
        email:
          type: string
          description: Without a valid one the staff member is still imported but gets no Coram account and loses any access an earlier import gave them.
        phone_number:
          type: string
        title:
          type: string
          description: Stored and displayed only.
        department:
          type: string
          description: Stored and displayed only.
        roles:
          type: array
          description: Free text, not a fixed set. Displayed in Coram and used for nothing else.
          items:
            type: string
        school_ids:
          type: array
          description: Unlike students and teachers, repeated ids here are removed rather than aborting the import. Staff have no primary school.
          items:
            type: string
    ReunificationImportStatus:
      type: object
      required:
      - id
      - status
      description: Progress report for your most recent import. Counters that are zero are left out of the response, so treat an absent field as zero. Every counter resets when a new import starts.
      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
          description: Counted after the merge described on the guardian object, so it is usually lower than the number of guardian entries you sent.
        succeeded_users:
          type: integer
          format: int32
          default: 0
          description: Coram accounts created or updated. Rises in the background after status becomes completed. Guardians are never included, because they get no account.
        failed_users:
          type: integer
          format: int32
          default: 0
          description: Accounts that could not be created, usually a seat limit or an Auth0 rejection. Zero does not mean everyone succeeded, since anyone without a valid email is absent from both figures.
        error_message:
          type: string
        started_at:
          type: string
          format: date-time
          description: Carries a real time only while the import is in progress. Once it reaches completed or failed the field is still returned but reset to 0001-01-01T00:00:00Z, so record the start time yourself if you need it.
        completed_at:
          type: string
          format: date-time
          description: Returned as 0001-01-01T00:00:00Z until the import finishes.
  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
  - credentials
  - events
  - permissions
- name: Emergency Management System
  tags:
  - reunification
x-provenance:
  method: harvested
  publisher: Coram Ai
  source: https://developer.coram.ai/openapi.json
  note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
    stamped by stamp-openapi-provenance.py (roadmap#793)