ebixcoi Public REST API (1.0.0-draft)

Download OpenAPI specification:

DRAFT specification for the ebixcoi Public REST API (Phase 1A surface). This contract remains DRAFT until the Phase 1A freeze; later PRs implement to it. Source documents: docs/Swift-Public-REST-API-Spec-v0.2.docx, docs/architecture/public-api-1a-contracts.md, docs/architecture/public-api-ground-truth.md.

Conventions:

  • All endpoints are tenant-scoped to the authenticated credential. The tenant is derived exclusively from the credential, never from the request body, path, or query string.
  • Errors use the RFC 7807 problem+json envelope with stable string codes.
  • Pagination is cursor-based (keyset on created_at DESC, id DESC) with an opaque base64url cursor. Default limit 50, max 500 (clamped, not errored).
  • Every response carries an X-Request-Id header. Authenticated responses carry X-RateLimit-Limit-Hour, X-RateLimit-Remaining-Hour and X-RateLimit-Reset headers. 429 responses carry Retry-After.
  • Write endpoints accept an Idempotency-Key header (UUID). Replays within 24 hours return the original response; a different request body under the same key returns 409 idempotency_conflict. Exception: the multipart COI document upload (POST /v1/insureds/{id}/coi-documents) does NOT honor Idempotency-Key; see that operation for details.
  • The COI document lifecycle is asynchronous: uploads return 201 immediately and progress is delivered via webhook stage events.
  • No browser CORS for API-key or client-credentials callers. The API is server-to-server only in Phase 1A.

Onboarding workflow (order of operations):

  1. GET /v1/risk-profiles to discover the active risk (insurance) profiles. Risk profiles are read-only here; onboarding configures them, so capture the UUID of the profile to associate.
  2. POST /v1/business-units to create the org-structure unit and read its UUID from the response.
  3. POST /v1/insureds with insurance_profile_id and business_unit_id set to those UUIDs.
  4. POST /v1/insureds/{id}/coi-documents to upload the COI; compliance then evaluates asynchronously and is read back via GET /v1/insureds/{id}/compliance and /v1/insureds/{id}/policies.

Insureds

Tenant-scoped vendors. Archive only; no destructive delete.

List insureds

Cursor-paginated list of insureds for the authenticated tenant, ordered by created_at DESC, id DESC. The default page excludes archived rows unless filter[lifecycle_status] is supplied. Requires the read scope.

Authorizations:
apiKeyoauthClientCredentials
query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

sort
string

Comma-separated sort fields; prefix a field with - for descending, for example sort=name,-created_at. Sortable fields are documented per resource. Default sort is created_at DESC, id DESC.

filter[status]
string

Filter by insured status.

filter[lifecycle_status]
string
Enum: "active" "inactive" "archived"

Filter by lifecycle status. When omitted the default list excludes archived rows; pass this to include or target a specific lifecycle status.

filter[business_unit_id]
string <uuid>

Filter by business unit id.

filter[insurance_profile_id]
string <uuid>

Filter by risk (insurance) profile id.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": "eyJjYSI6IjIwMjYtMDUtMjhUMTE6MTI6MDBaIiwiaWQiOiJiOGYwZDJjNC02ZTFhLTRiM2MtOWQ1ZS03ZjhhOWIwYzFkMmUifQ"
}

Create an insured

Creates a single insured. Server-owned fields (email_alias_full, email_alias_local_part, email_alias_hash, alias_frozen, portal_token) are generated by database triggers and are never accepted on input. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
name
required
string non-empty
email
string or null <email>
contact_name
string or null
phone
string or null
address_line1
string or null
address_line2
string or null
city
string or null
state
string or null
zip
string or null
country
string or null
tax_id
string or null
external_id
string or null
status
string

Optional initial status; server default applies when omitted.

business_unit_id
string or null <uuid>
insurance_profile_id
string or null <uuid>
trade_type_id
string or null <uuid>

Responses

Request samples

Content type
application/json
{
  • "name": "Acme Mechanical LLC",
  • "email": "coi@acmemech.example",
  • "contact_name": "Dana Whitfield",
  • "phone": "312-555-0144",
  • "address_line1": "4821 W Fulton St",
  • "address_line2": "Suite 210",
  • "city": "Chicago",
  • "state": "IL",
  • "zip": "60624",
  • "country": "US",
  • "tax_id": "36-4821907",
  • "external_id": "VEND-88421",
  • "business_unit_id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "insurance_profile_id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d"
}

Response samples

Content type
application/json
{
  • "id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "name": "Acme Mechanical LLC",
  • "email": "coi@acmemech.example",
  • "contact_name": "Dana Whitfield",
  • "phone": "312-555-0144",
  • "address_line1": "4821 W Fulton St",
  • "address_line2": "Suite 210",
  • "city": "Chicago",
  • "state": "IL",
  • "zip": "60624",
  • "country": "US",
  • "tax_id": "36-4821907",
  • "external_id": "VEND-88421",
  • "status": "active",
  • "lifecycle_status": "active",
  • "business_unit_id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "insurance_profile_id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  • "trade_type_id": null,
  • "created_at": "2026-06-01T14:30:00Z",
  • "updated_at": "2026-06-01T14:30:00Z"
}

Get an insured

Returns one insured by id within the authenticated tenant. A cross-tenant id returns 404 with no existence leak. Requires the read scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "name": "Acme Mechanical LLC",
  • "email": "coi@acmemech.example",
  • "contact_name": "Dana Whitfield",
  • "phone": "312-555-0144",
  • "address_line1": "4821 W Fulton St",
  • "address_line2": "Suite 210",
  • "city": "Chicago",
  • "state": "IL",
  • "zip": "60624",
  • "country": "US",
  • "tax_id": "36-4821907",
  • "external_id": "VEND-88421",
  • "status": "active",
  • "lifecycle_status": "active",
  • "business_unit_id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "insurance_profile_id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  • "trade_type_id": null,
  • "created_at": "2026-06-01T14:30:00Z",
  • "updated_at": "2026-06-01T14:30:00Z"
}

Update an insured

Partial update. Server-owned alias and portal fields are never writable. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
name
string non-empty
email
string or null <email>
contact_name
string or null
phone
string or null
address_line1
string or null
address_line2
string or null
city
string or null
state
string or null
zip
string or null
country
string or null
tax_id
string or null
external_id
string or null
status
string
business_unit_id
string or null <uuid>
insurance_profile_id
string or null <uuid>
trade_type_id
string or null <uuid>

Responses

Request samples

Content type
application/json
{
  • "phone": "312-555-0167",
  • "contact_name": "Dana Whitfield-Cole"
}

Response samples

Content type
application/json
{
  • "id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "name": "Acme Mechanical LLC",
  • "email": "coi@acmemech.example",
  • "contact_name": "Dana Whitfield-Cole",
  • "phone": "312-555-0167",
  • "address_line1": "4821 W Fulton St",
  • "address_line2": "Suite 210",
  • "city": "Chicago",
  • "state": "IL",
  • "zip": "60624",
  • "country": "US",
  • "tax_id": "36-4821907",
  • "external_id": "VEND-88421",
  • "status": "active",
  • "lifecycle_status": "active",
  • "business_unit_id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "insurance_profile_id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  • "trade_type_id": null,
  • "created_at": "2026-06-01T14:30:00Z",
  • "updated_at": "2026-06-03T09:15:00Z"
}

Archive an insured

Soft archive (sets lifecycle_status to archived). There is no destructive delete anywhere in the insureds resource. Idempotent: archiving an already-archived insured still returns 200. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/json
{
  • "ok": true
}

Bulk create, update, or archive insureds

One POST per operation kind, selected by the operation field. Up to 500 items per batch. Per-item results: one bad row never fails the batch. A single Idempotency-Key covers the whole batch; a replay returns the original per-item outcome array. A bulk call counts as one request against the request quota; its items count against a separate item quota. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
operation
required
string
Enum: "create" "update" "archive"
required
Array of objects (InsuredBulkItem) [ 1 .. 500 ] items

Responses

Request samples

Content type
application/json
{
  • "operation": "create",
  • "items": [
    ]
}

Response samples

Content type
application/json
{
  • "summary": {
    },
  • "items": [
    ]
}

COI Documents

Certificate of Insurance documents. Upload is asynchronous.

List COI documents for an insured

Cursor-paginated list of COI documents for one insured, ordered by created_at DESC, id DESC. The default page excludes archived documents. Returns 404 not_found when the insured does not belong to the authenticated tenant. Requires the read scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Upload a COI document

Multipart (multipart/form-data) upload, 15 MB max per file (15728640 bytes; larger returns 413 payload_too_large). Returns 201 immediately with processing_status set to processing; the extract-coi pipeline runs asynchronously (typically 8 to 10 seconds, up to about 80 seconds for large multi-page documents) and the document advances to completed or failed. Clients never hold a connection open for the pipeline; poll GET /v1/coi-documents/{id} for the outcome. application/pdf is the primary supported content type; other types are accepted but may fail extraction. Empty files are rejected with 422 validation_error. Requires the write scope. Exception to the platform-wide idempotency convention: the Idempotency-Key header is NOT honored on this route (the only write route where it is not). The multipart binary body is not request-hashed, so replays cannot be detected server-side; duplicate uploads are detectable by the client via the document list. The 409 idempotency_conflict response therefore never occurs here.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Request Body schema: multipart/form-data
required
file
required
string <binary>

The COI file. 15 MB max. application/pdf is the primary supported content type.

notes
string

Optional free-text notes stored on the document and echoed back in the notes field.

Responses

Response samples

Content type
application/json
{
  • "id": "5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a",
  • "insured_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "file_name": "acme-mechanical-coi-2026.pdf",
  • "status": "pending",
  • "processing_status": "processing",
  • "document_type": null,
  • "notes": "Uploaded via Procore sync",
  • "created_at": "2026-06-01T14:31:00Z",
  • "updated_at": "2026-06-01T14:31:00Z"
}

Get a COI document

Returns the document including its current processing_status. Consumers may poll this resource until processing_status leaves processing (becoming completed or failed). Requires the read scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "id": "5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a",
  • "insured_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "file_name": "acme-mechanical-coi-2026.pdf",
  • "status": "pending_review",
  • "processing_status": "completed",
  • "document_type": "Acord 25",
  • "notes": "Uploaded via Procore sync",
  • "created_at": "2026-06-01T14:31:00Z",
  • "updated_at": "2026-06-01T14:31:25Z"
}

Archive a COI document

Soft archive (sets status to archived). There is no destructive delete anywhere in the COI documents resource; the row survives and is excluded from the default document list. Idempotent: archiving an already-archived document still returns 200. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/json
{
  • "ok": true
}

Compliance

Read-only compliance verdicts and history per insured.

Get current compliance for an insured

Read-only. Returns the current verdict, deficiencies, satisfied and missing coverages, and contributing documents. This is the resource a thin compliance.status_changed webhook links to.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "insured_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "compliance_status": "non_compliant",
  • "compliance_score": 72.5,
  • "risk_score": 64,
  • "risk_tier": "medium",
  • "major_deficiency_count": 1,
  • "minor_deficiency_count": 0,
  • "waived_major_count": 0,
  • "waived_minor_count": 0,
  • "deficiencies": [
    ],
  • "satisfied_coverages": [
    ],
  • "missing_coverages": [
    ],
  • "last_evaluated_at": "2026-06-03T09:15:00Z",
  • "updated_at": "2026-06-03T09:15:00Z"
}

Get compliance history for an insured

Read-only, cursor-paginated history of compliance evaluations, newest first. Snapshot granularity and retention window are draft, verify in the api-compliance implementation PR.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Policies

Read-only extracted policies with limits and endorsements.

List extracted policies for an insured

Read-only. Returns extracted policies with their coverages, limits, endorsements, and additional insured wording.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Risk Profiles

Read-only tenant-scoped requirement profiles for compliance rules. Onboarding owns creation; the API exposes reads only.

List risk profiles

Cursor-paginated list of active risk (insurance) profiles for the authenticated tenant, each with its coverage requirement line items. Read-only: onboarding owns risk profile creation, so this resource exposes no write operations. Requires the read scope.

Authorizations:
apiKeyoauthClientCredentials
query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Get a risk profile

Returns one risk profile by id within the authenticated tenant, including its coverage requirement line items. A cross-tenant id returns 404 with no existence leak. Read-only. Requires the read scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
  • "name": "Standard Trade Contractors",
  • "external_reference_code": "STD-TRADE",
  • "lifecycle_status": "active",
  • "created_at": "2026-05-15T10:00:00Z",
  • "updated_at": "2026-05-15T10:00:00Z",
  • "coverages": [
    ]
}

Business Units

Optional org-structure overlay for insureds.

List business units

Authorizations:
apiKeyoauthClientCredentials
query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Create a business unit

Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
name
required
string non-empty
business_unit_code
string or null
parent_id
string or null <uuid>
contact_name
string or null
email
string or null <email>
phone
string or null

Responses

Request samples

Content type
application/json
{
  • "name": "Midwest Region",
  • "business_unit_code": "MIDWEST",
  • "contact_name": "Priya Raman",
  • "email": "midwest.compliance@example.com",
  • "phone": "312-555-0190"
}

Response samples

Content type
application/json
{
  • "id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "name": "Midwest Region",
  • "business_unit_code": "MIDWEST",
  • "parent_id": null,
  • "contact_name": "Priya Raman",
  • "email": "midwest.compliance@example.com",
  • "phone": "312-555-0190",
  • "lifecycle_status": "active",
  • "created_at": "2026-05-20T16:45:00Z",
  • "updated_at": "2026-05-20T16:45:00Z"
}

Get a business unit

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "name": "Midwest Region",
  • "business_unit_code": "MIDWEST",
  • "parent_id": null,
  • "contact_name": "Priya Raman",
  • "email": "midwest.compliance@example.com",
  • "phone": "312-555-0190",
  • "lifecycle_status": "active",
  • "created_at": "2026-05-20T16:45:00Z",
  • "updated_at": "2026-05-20T16:45:00Z"
}

Update a business unit

Partial update. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
name
string non-empty
business_unit_code
string or null
parent_id
string or null <uuid>
contact_name
string or null
email
string or null <email>
phone
string or null

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "business_unit_code": "string",
  • "parent_id": "1c6ca187-e61f-4301-8dcb-0e9749e89eef",
  • "contact_name": "string",
  • "email": "user@example.com",
  • "phone": "string"
}

Response samples

Content type
application/json
{
  • "id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e",
  • "name": "Midwest Region",
  • "business_unit_code": "MIDWEST",
  • "parent_id": null,
  • "contact_name": "Priya Raman",
  • "email": "midwest.region@example.com",
  • "phone": "312-555-0190",
  • "lifecycle_status": "active",
  • "created_at": "2026-05-20T16:45:00Z",
  • "updated_at": "2026-06-03T09:20:00Z"
}

Archive a business unit

Soft archive (sets lifecycle_status to archived). There is no destructive delete on business units. Idempotent: archiving an already-archived business unit still returns 200. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/json
{
  • "ok": true
}

External Mappings

External system ID to ebixcoi entity ID mappings.

List external mappings

Authorizations:
apiKeyoauthClientCredentials
query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

filter[system]
string

Filter by external system, for example procore or yardi.

filter[internal_kind]
string
Enum: "insured" "business_unit"

Filter by internal entity kind.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Create an external mapping

Maps an external system ID to an ebixcoi entity. Unique per (system, external_kind, external_id) within the tenant; a duplicate returns 422 validation_error with detail "mapping already exists". system, external_kind and external_id are lowercased and trimmed server-side. internal_id must reference an existing row of the given internal_kind within the tenant, otherwise 422 validation_error with detail "internal_id not found in tenant". Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
system
required
string
external_kind
required
string
external_id
required
string
internal_kind
required
string
Enum: "insured" "business_unit"
internal_id
required
string <uuid>

Must exist in the tenant for the given internal_kind.

match_signal
string or null
match_confidence
number or null

Responses

Request samples

Content type
application/json
{
  • "system": "procore",
  • "external_kind": "vendor",
  • "external_id": "88421",
  • "internal_kind": "insured",
  • "internal_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10"
}

Response samples

Content type
application/json
{
  • "id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
  • "system": "procore",
  • "external_kind": "vendor",
  • "external_id": "88421",
  • "internal_kind": "insured",
  • "internal_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "match_signal": null,
  • "match_confidence": null,
  • "created_at": "2026-06-01T14:32:00Z"
}

Get an external mapping

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "id": "6a5b4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
  • "system": "procore",
  • "external_kind": "vendor",
  • "external_id": "88421",
  • "internal_kind": "insured",
  • "internal_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10",
  • "match_signal": null,
  • "match_confidence": null,
  • "created_at": "2026-06-01T14:32:00Z"
}

Delete an external mapping

Soft delete. The mapping record is hidden from all subsequent reads (a GET returns 404 and the mapping no longer appears in list results), and its external identifiers may be re-registered with a new create. Response shapes are unchanged. Deleting an already-deleted id returns 404. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/json
{
  • "ok": true
}

Bulk create or delete external mappings

Same multi-status semantics as /v1/insureds/bulk: per-item results, 500-item ceiling, one bad row never fails the batch (HTTP 200 even with partial errors), one Idempotency-Key covers the batch. The operation is create or delete; for delete, items are resolved by {id} or by {system, external_kind, external_id}, and deletes are TRUE deletes. Requires the write scope.

Authorizations:
apiKeyoauthClientCredentials
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
operation
required
string
Enum: "create" "delete"
required
Array of objects [ 1 .. 500 ] items

Responses

Request samples

Content type
application/json
{
  • "operation": "create",
  • "items": [
    ]
}

Response samples

Content type
application/json
{
  • "summary": {
    },
  • "items": [
    ]
}

Webhooks

Outbound event subscriptions and delivery audit.

List webhook subscriptions

Cursor-paginated list. The default page excludes revoked subscriptions. No secret material ever appears in list rows.

Authorizations:
apiKey
query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Create a webhook subscription

Subscribes a target URL to event types. target_url must start with https://. Each event filter is either an exact . or a .* wildcard, where is one of insured, coi_document, compliance, policy, integration, external_mapping.

SIGNING SECRET, SHOWN ONCE: the 201 response is the only place the plaintext signing secret (format whsec_ followed by 32 hex chars) is ever returned. Store it immediately; reads, lists and updates never include it, and it cannot be retrieved later. Deliveries (Phase 2) are signed with HMAC-SHA256 using this secret in the X-EbixCOI-Signature header. Requires the write scope.

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
target_url
required
string <uri> ^https://

Must start with https://.

event_filters
required
Array of strings non-empty

Non-empty. Each entry is . or .* with in insured, coi_document, compliance, policy, integration, external_mapping.

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
  • "event_filters": [
    ],
  • "status": "active",
  • "created_at": "2026-06-01T14:35:00Z",
  • "updated_at": "2026-06-01T14:35:00Z",
  • "secret": "whsec_4f3e2d1c0b9a8f7e6d5c4b3a29181706"
}

Get a webhook subscription

Authorizations:
apiKey
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

Responses

Response samples

Content type
application/json
{
  • "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
  • "event_filters": [
    ],
  • "status": "active",
  • "created_at": "2026-06-01T14:35:00Z",
  • "updated_at": "2026-06-01T14:35:00Z"
}

Update a webhook subscription

Partial update of target_url, event_filters, or status. status accepts only active or paused (pause and resume); revocation happens exclusively via DELETE. A revoked subscription cannot be updated: any PATCH after revocation returns 422 validation_error with detail "subscription is revoked". Requires the write scope.

Authorizations:
apiKey
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
target_url
string <uri> ^https://

Must start with https://.

event_filters
Array of strings non-empty

Same vocabulary as on create.

status
string
Enum: "active" "paused"

Pause or resume delivery. Revocation is via DELETE only; sending revoked here is a 422, and a revoked subscription rejects every PATCH with 422.

Responses

Request samples

Content type
application/json
{
  • "status": "paused"
}

Response samples

Content type
application/json
{
  • "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
  • "event_filters": [
    ],
  • "status": "paused",
  • "created_at": "2026-06-01T14:35:00Z",
  • "updated_at": "2026-06-03T09:25:00Z"
}

Revoke a webhook subscription

DELETE is a REVOKE, never a hard delete: status becomes revoked, the row survives for audit, and it disappears from the default list. Revocation is idempotent (revoking an already-revoked subscription still returns 200) and irreversible via the API; a revoked subscription cannot be reactivated by PATCH. Requires the write scope.

Authorizations:
apiKey
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/json
{
  • "ok": true
}

List webhook deliveries

Audit log of delivery attempts (metadata only, no payloads). Webhook delivery itself ships in Phase 2, so this list is empty until then.

Authorizations:
apiKey
query Parameters
cursor
string

Opaque pagination cursor from a previous response's next_cursor. Keyset pagination ordered by created_at DESC, id DESC. Malformed cursors are treated as absent.

limit
integer [ 1 .. 500 ]
Default: 50

Page size. Default 50, max 500. Out-of-range values are clamped.

filter[subscription_id]
string <uuid>

Filter by subscription.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "next_cursor": null
}

Replay a webhook delivery (not implemented yet)

Will force redelivery of any delivery (succeeded, failed, or exhausted) once the Phase 2 delivery pipeline ships. Until then this endpoint is a stub that always returns 501 not_implemented. Requires the write scope.

Authorizations:
apiKey
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/problem+json
{}

OAuth

OAuth 2.0 token endpoint (client_credentials in Phase 1A).

OAuth 2.0 token endpoint

Issues an access token via the client_credentials grant (RFC 6749 section 4.4), the only grant in Phase 1A; any other grant_type returns unsupported_grant_type. Authorization code, PKCE, and refresh token grants arrive in a later phase.

Client authentication: client_id and client_secret may be sent in the request body, OR via an HTTP Basic Authorization header (base64 of client_id:client_secret per RFC 6749 section 2.3.1). Body credentials win when both are present. The request body may be application/x-www-form-urlencoded (RFC 6749 canonical) or application/json with the same fields.

Access tokens are bearer tokens (ebxo_ followed by 44 hex characters) with a fixed 1 hour TTL. Successful responses carry Cache-Control no-store and Pragma no-cache (RFC 6749 section 5.1).

THIS IS THE ONLY NON-problem+json SURFACE IN THE API. Token endpoint errors follow the RFC 6749 error object (error, error_description) for OAuth interoperability: invalid_client maps to 401 with a WWW-Authenticate challenge; invalid_request, unsupported_grant_type, and invalid_scope map to 400; slow_down (token mint cap, 120 tokens per client per rolling hour) maps to 429 with Retry-After. Every other route in the API, including resource calls made WITH these tokens, keeps the RFC 7807 problem+json envelope.

The api-oauth function also exposes a /clients management surface (create, list, revoke OAuth clients). That surface is an admin session surface (logged-in admin bearer token, like API key management) and is not part of the public API contract, so it is not documented here.

Request Body schema:
required
grant_type
required
string
Value: "client_credentials"

Phase 1A supports client_credentials only.

client_id
string

ebxc_ followed by 24 hex characters. Required here unless sent via HTTP Basic.

client_secret
string

ebxs_ followed by 44 hex characters. Required here unless sent via HTTP Basic.

scope
string

Space-separated subset of the client's allowed scopes (read, write). Defaults to all allowed scopes when omitted; any requested scope outside the allowed set returns 400 invalid_scope.

Responses

Request samples

Content type
grant_type=client_credentials&client_id=ebxc_1a2b3c4d5e6f7a8b9c0d1e2f&client_secret=ebxs_0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a&scope=read%20write

Response samples

Content type
application/json
{
  • "access_token": "ebxo_9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d",
  • "token_type": "Bearer",
  • "expires_in": 3600,
  • "scope": "read write"
}

API Keys

API key management (admin session authenticated).

List API keys

Lists the tenant's API keys. Never returns the key hash or plain key. This endpoint manages credentials, so it is authenticated with a logged-in admin session bearer token (col_admin or higher), not with an API key.

Authorizations:
apiKey

Responses

Response samples

Content type
application/json
{
  • "keys": [
    ]
}

Create an API key

Generates a new key. The plain key (ebx_ prefixed, 48 characters) is returned ONCE in this response and never stored or shown again. Maximum 25 active keys per tenant. Admin session bearer token required (col_admin or higher).

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters
scopes
Array of strings
Items Enum: "read" "write"

Defaults to both read and write when omitted.

expires_at
string or null <date-time>

Responses

Request samples

Content type
application/json
{
  • "name": "Procore integration",
  • "scopes": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "4b3a2c1d-0e9f-4d8c-b7a6-5f4e3d2c1b0a",
  • "key_prefix": "ebx_3f9c0a1b",
  • "plain_key": "ebx_3f9c0a1b2e4d6c8a0f1e3d5c7b9a1f2e4d6c8a0f1e3d",
  • "scopes": [
    ],
  • "created_at": "2026-06-01T14:30:00Z"
}

Revoke an API key

Sets revoked_at. A key belonging to another tenant returns 404 with no existence leak. Admin session bearer token required (col_admin or higher).

Authorizations:
apiKey
path Parameters
id
required
string <uuid>

Resource identifier (UUID).

header Parameters
Idempotency-Key
string <uuid>

Client-generated UUID. A replay within 24 hours with the same key and an identical request body returns the original response; the same key with a different body returns 409 idempotency_conflict.

Responses

Response samples

Content type
application/json
{
  • "ok": true
}