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:
Onboarding workflow (order of operations):
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.
| 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. |
{- "data": [
- {
- "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"
}, - {
- "id": "b8f0d2c4-6e1a-4b3c-9d5e-7f8a9b0c1d2e",
- "name": "Birchwood Electric Inc",
- "email": "certs@birchwoodelectric.example",
- "contact_name": "Miguel Santos",
- "phone": "312-555-0177",
- "address_line1": "902 N Halsted St",
- "address_line2": null,
- "city": "Chicago",
- "state": "IL",
- "zip": "60642",
- "country": "US",
- "tax_id": null,
- "external_id": "VEND-88423",
- "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-05-28T11:12:00Z",
- "updated_at": "2026-05-28T11:12:00Z"
}
], - "next_cursor": "eyJjYSI6IjIwMjYtMDUtMjhUMTE6MTI6MDBaIiwiaWQiOiJiOGYwZDJjNC02ZTFhLTRiM2MtOWQ1ZS03ZjhhOWIwYzFkMmUifQ"
}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.
| 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. |
| name required | string non-empty |
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> |
{- "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"
}{- "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"
}Returns one insured by id within the authenticated tenant. A cross-tenant id returns 404 with no existence leak. Requires the read scope.
| id required | string <uuid> Resource identifier (UUID). |
{- "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"
}Partial update. Server-owned alias and portal fields are never writable. Requires the write scope.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
| name | string non-empty |
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> |
{- "phone": "312-555-0167",
- "contact_name": "Dana Whitfield-Cole"
}{- "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"
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "ok": true
}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.
| 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. |
| operation required | string Enum: "create" "update" "archive" |
required | Array of objects (InsuredBulkItem) [ 1 .. 500 ] items |
{- "operation": "create",
- "items": [
- {
- "name": "Acme Mechanical LLC",
- "email": "coi@acmemech.example",
- "external_id": "VEND-88421",
- "business_unit_code": "MIDWEST",
- "risk_profile_code": "STD-TRADE"
}, - {
- "external_id": "VEND-88422",
- "business_unit_code": "MIDWEST"
}, - {
- "name": "Birchwood Electric Inc",
- "external_id": "VEND-88423",
- "business_unit_code": "MIDWEST",
- "risk_profile_code": "STD-TRADE"
}
]
}{- "summary": {
- "created": 2,
- "updated": 0,
- "skipped": 0,
- "errored": 1
}, - "items": [
- {
- "index": 0,
- "outcome": "ok"
}, - {
- "index": 1,
- "outcome": "error",
- "error": {
- "code": "validation_error",
- "detail": "name is required for create"
}
}, - {
- "index": 2,
- "outcome": "ok"
}
]
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "data": [
- {
- "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"
}
], - "next_cursor": null
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "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"
}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.
| id required | string <uuid> Resource identifier (UUID). |
{- "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"
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "ok": true
}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.
| id required | string <uuid> Resource identifier (UUID). |
{- "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": [
- {
- "coverage_type": "Automobile Liability",
- "attribute_key": "combined_single_limit",
- "severity": "major",
- "message": "Combined single limit 500000 is below the required 1000000."
}
], - "satisfied_coverages": [
- "General Liability",
- "Workers Compensation"
], - "missing_coverages": [
- {
- "coverage_type": "Umbrella Liability",
- "reason": "No approved evidence on file."
}
], - "last_evaluated_at": "2026-06-03T09:15:00Z",
- "updated_at": "2026-06-03T09:15:00Z"
}Read-only, cursor-paginated history of compliance evaluations, newest first. Snapshot granularity and retention window are draft, verify in the api-compliance implementation PR.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "data": [
- {
- "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5e",
- "created_at": "2026-06-03T09:15:00Z",
- "trigger_event": "document_approved",
- "old_status": "compliant",
- "new_status": "non_compliant",
- "old_risk_score": 22,
- "new_risk_score": 64,
- "old_risk_tier": "low",
- "new_risk_tier": "medium",
- "old_major_deficiency_count": 0,
- "new_major_deficiency_count": 1,
- "old_minor_deficiency_count": 0,
- "new_minor_deficiency_count": 0
}, - {
- "id": "f9e8d7c6-b5a4-4c3d-8e2f-1a0b9c8d7e6f",
- "created_at": "2026-06-01T15:02:00Z",
- "trigger_event": "initial_evaluation",
- "old_status": null,
- "new_status": "compliant",
- "old_risk_score": null,
- "new_risk_score": 22,
- "old_risk_tier": null,
- "new_risk_tier": "low",
- "old_major_deficiency_count": null,
- "new_major_deficiency_count": 0,
- "old_minor_deficiency_count": null,
- "new_minor_deficiency_count": 0
}
], - "next_cursor": null
}Read-only. Returns extracted policies with their coverages, limits, endorsements, and additional insured wording.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "data": [
- {
- "document_id": "5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a",
- "coverage_type": "General Liability",
- "policy_number": "GL-2026-004821",
- "insurer_name": "Travelers Indemnity Co",
- "effective_date": "2026-01-01",
- "expiration_date": "2027-01-01",
- "limits": {
- "each_occurrence_limit": "1000000",
- "aggregate_limit": "2000000",
- "med_exp_limit": "10000",
- "personal_adv_injury_limit": "1000000",
- "damage_to_rented_premises_limit": "300000",
- "products_comp_op_agg_limit": "2000000"
}, - "endorsements": {
- "additional_insured": true,
- "additional_insured_wording": "Midwest Region LLC, its officers, agents and employees are included as additional insured on a primary and non-contributory basis per CG 20 10 04 13.",
- "waiver_of_subrogation": true
}
}, - {
- "document_id": "5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a",
- "coverage_type": "Automobile Liability",
- "policy_number": "CA-2026-117734",
- "insurer_name": "Travelers Indemnity Co",
- "effective_date": "2026-01-01",
- "expiration_date": "2027-01-01",
- "limits": {
- "combined_single_limit": "500000"
}, - "endorsements": {
- "additional_insured": true,
- "waiver_of_subrogation": true,
- "covered_auto_any_auto": true,
- "covered_auto_hired": true,
- "covered_auto_non_owned": true
}
}
], - "next_cursor": null
}Read-only tenant-scoped requirement profiles for compliance rules. Onboarding owns creation; the API exposes reads only.
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.
| 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. |
{- "data": [
- {
- "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": [
- {
- "coverage_type_id": "c0a1b2c3-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
- "coverage_type_key": "general_liability",
- "coverage_type_name": "General Liability",
- "min_am_best_rating": "A-",
- "min_am_best_financial_size": "VII",
- "allow_umbrella_aggregation": true
}, - {
- "coverage_type_id": "d1b2c3a4-e5f6-4a7b-8c9d-1f2a3b4c5d6e",
- "coverage_type_key": "automobile_liability",
- "coverage_type_name": "Automobile Liability",
- "min_am_best_rating": "A-",
- "min_am_best_financial_size": "VII",
- "allow_umbrella_aggregation": false
}
]
}
], - "next_cursor": null
}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.
| id required | string <uuid> Resource identifier (UUID). |
{- "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": [
- {
- "coverage_type_id": "c0a1b2c3-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
- "coverage_type_key": "general_liability",
- "coverage_type_name": "General Liability",
- "min_am_best_rating": "A-",
- "min_am_best_financial_size": "VII",
- "allow_umbrella_aggregation": true
}, - {
- "coverage_type_id": "d1b2c3a4-e5f6-4a7b-8c9d-1f2a3b4c5d6e",
- "coverage_type_key": "automobile_liability",
- "coverage_type_name": "Automobile Liability",
- "min_am_best_rating": "A-",
- "min_am_best_financial_size": "VII",
- "allow_umbrella_aggregation": false
}
]
}| 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. |
{- "data": [
- {
- "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"
}
], - "next_cursor": null
}Requires the write scope.
| 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. |
| name required | string non-empty |
| business_unit_code | string or null |
| parent_id | string or null <uuid> |
| contact_name | string or null |
string or null <email> | |
| phone | string or null |
{- "name": "Midwest Region",
- "business_unit_code": "MIDWEST",
- "contact_name": "Priya Raman",
- "email": "midwest.compliance@example.com",
- "phone": "312-555-0190"
}{- "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"
}| id required | string <uuid> Resource identifier (UUID). |
{- "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"
}Partial update. Requires the write scope.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
| name | string non-empty |
| business_unit_code | string or null |
| parent_id | string or null <uuid> |
| contact_name | string or null |
string or null <email> | |
| phone | string or null |
{- "name": "string",
- "business_unit_code": "string",
- "parent_id": "1c6ca187-e61f-4301-8dcb-0e9749e89eef",
- "contact_name": "string",
- "email": "user@example.com",
- "phone": "string"
}{- "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"
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "ok": true
}| 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. |
{- "data": [
- {
- "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"
}
], - "next_cursor": null
}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.
| 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. |
| 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 |
{- "system": "procore",
- "external_kind": "vendor",
- "external_id": "88421",
- "internal_kind": "insured",
- "internal_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10"
}{- "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"
}| id required | string <uuid> Resource identifier (UUID). |
{- "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"
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "ok": true
}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.
| 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. |
| operation required | string Enum: "create" "delete" |
required | Array of objects [ 1 .. 500 ] items |
{- "operation": "create",
- "items": [
- {
- "system": "procore",
- "external_kind": "vendor",
- "external_id": "88421",
- "internal_kind": "insured",
- "internal_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10"
}, - {
- "system": "procore",
- "external_kind": "vendor",
- "external_id": "88421",
- "internal_kind": "insured",
- "internal_id": "7c2e4a1e-93f2-4f6e-9d3a-5b8e2c7d4f10"
}, - {
- "system": "procore",
- "external_kind": "project",
- "external_id": "proj-3301",
- "internal_kind": "business_unit",
- "internal_id": "3f8b2d6c-1a4e-4c2b-8e7f-9d0a1b2c3d4e"
}
]
}{- "summary": {
- "created": 2,
- "deleted": 0,
- "errored": 1
}, - "items": [
- {
- "index": 0,
- "outcome": "ok"
}, - {
- "index": 1,
- "outcome": "error",
- "error": {
- "code": "validation_error",
- "detail": "mapping already exists"
}
}, - {
- "index": 2,
- "outcome": "ok"
}
]
}Cursor-paginated list. The default page excludes revoked subscriptions. No secret material ever appears in list rows.
| 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. |
{- "data": [
- {
- "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
- "event_filters": [
- "compliance.*",
- "coi_document.*"
], - "status": "active",
- "created_at": "2026-06-01T14:35:00Z",
- "updated_at": "2026-06-01T14:35:00Z"
}
], - "next_cursor": null
}Subscribes a target URL to event types. target_url must start with https://. Each event filter is either an exact
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.
| 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. |
| target_url required | string <uri> ^https:// Must start with https://. |
| event_filters required | Array of strings non-empty Non-empty. Each entry is |
{- "event_filters": [
- "compliance.*",
- "coi_document.*"
]
}{- "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
- "event_filters": [
- "compliance.*",
- "coi_document.*"
], - "status": "active",
- "created_at": "2026-06-01T14:35:00Z",
- "updated_at": "2026-06-01T14:35:00Z",
- "secret": "whsec_4f3e2d1c0b9a8f7e6d5c4b3a29181706"
}| id required | string <uuid> Resource identifier (UUID). |
{- "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
- "event_filters": [
- "compliance.*",
- "coi_document.*"
], - "status": "active",
- "created_at": "2026-06-01T14:35:00Z",
- "updated_at": "2026-06-01T14:35:00Z"
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
| 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. |
{- "status": "paused"
}{- "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
- "event_filters": [
- "compliance.*",
- "coi_document.*"
], - "status": "paused",
- "created_at": "2026-06-01T14:35:00Z",
- "updated_at": "2026-06-03T09:25:00Z"
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "ok": true
}Audit log of delivery attempts (metadata only, no payloads). Webhook delivery itself ships in Phase 2, so this list is empty until then.
| 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. |
{- "data": [
- {
- "id": "d1c2b3a4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
- "subscription_id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
- "event_id": "e0d1c2b3-a4f5-4e6d-9c8b-7a6f5e4d3c2b",
- "event_type": "compliance.status_changed",
- "status": "delivered",
- "attempt_number": 1,
- "http_status": 200,
- "scheduled_for": "2026-06-03T09:15:05Z",
- "next_retry_at": null,
- "delivered_at": "2026-06-03T09:15:06Z",
- "created_at": "2026-06-03T09:15:05Z"
}
], - "next_cursor": null
}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.
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "title": "Unauthorized",
- "status": 401,
- "code": "unauthorized",
- "detail": "API key is missing, invalid, revoked, or expired.",
- "request_id": "0a1b2c3d-0000-4000-8000-000000000001"
}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.
| 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. |
grant_type=client_credentials&client_id=ebxc_1a2b3c4d5e6f7a8b9c0d1e2f&client_secret=ebxs_0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a&scope=read%20write
{- "access_token": "ebxo_9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d",
- "token_type": "Bearer",
- "expires_in": 3600,
- "scope": "read write"
}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.
{- "keys": [
- {
- "id": "4b3a2c1d-0e9f-4d8c-b7a6-5f4e3d2c1b0a",
- "name": "Procore integration",
- "key_prefix": "ebx_3f9c0a1b",
- "scopes": [
- "read",
- "write"
], - "created_at": "2026-06-01T14:30:00Z",
- "last_used_at": "2026-06-09T22:04:00Z",
- "expires_at": null,
- "revoked_at": null
}
]
}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).
| 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. |
| 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> |
{- "name": "Procore integration",
- "scopes": [
- "read",
- "write"
]
}{- "id": "4b3a2c1d-0e9f-4d8c-b7a6-5f4e3d2c1b0a",
- "key_prefix": "ebx_3f9c0a1b",
- "plain_key": "ebx_3f9c0a1b2e4d6c8a0f1e3d5c7b9a1f2e4d6c8a0f1e3d",
- "scopes": [
- "read",
- "write"
], - "created_at": "2026-06-01T14:30:00Z"
}Sets revoked_at. A key belonging to another tenant returns 404 with no existence leak. Admin session bearer token required (col_admin or higher).
| id required | string <uuid> Resource identifier (UUID). |
| 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. |
{- "ok": true
}