Work with Reports

This guide shows how to create a report, read it, update it, and find it again from your own systems. Every call names the report by an identifier; the section below explains the two identifiers you can use.

All examples use the production base URL https://api.kalepa.com and a token in $TOKEN. See Getting started for the token.

The two identifiers

IdentifierWho assigns itShapeExample
Kalepa report IDKalepa, when the report is createdUUIDf116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3
Your external_idYou, on create or later with PATCHA non-empty string (see the path rules below)ACME-2026-000123

The values ACME-2026-000123, COMMERCIAL, and EAST in this guide are examples. Replace them with your own submission number, org group, and sub-org group.

Both identifiers work in the {report_id} path segment of these endpoints:

  • GET /external/reports/{report_id}
  • PATCH /external/reports/{report_id}
  • GET /external/reports/{report_id}/files
  • PUT /external/reports/{report_id}/files/{uploaded_file_id} and DELETE /external/reports/{report_id}/files/{uploaded_file_id} (only {report_id} takes your identifier; {uploaded_file_id} is the id Kalepa returned when you uploaded the file)
    — see Work with report files
  • POST /external/reports/{report_id}/client-data — see Work with client data
  • The policy tower: /external/reports/{report_id}/policies, .../policies/{policy_id}, .../policies/{policy_id}/coverages, .../policies/{policy_id}/premiums, .../coverages/{coverage_id}, .../coverages/{coverage_id}/limits, .../coverages/{coverage_id}/deductibles, .../coverages/{coverage_id}/premiums, .../limits/{limit_id}, .../deductibles/{deductible_id} and .../premiums/{premium_id} (only {report_id} takes your identifier; the policy, coverage, limit, deductible and premium ids are the id values Kalepa returned)

Two more endpoints take the report identifier in the request body or form instead of the path, and accept both identifiers there:

  • POST /external/files — the report_id form field
  • POST /external/files/upload_url — the report_id body field
    — see Work with report files

GET /external/report_id goes the other way: you give it your external_id and it returns the Kalepa report ID. See Look up the Kalepa report ID from an external_id.

Every other report endpoint takes the Kalepa report ID only. That includes notes, submission notes, status, extract, and exports. On those endpoints an external_id is rejected as an invalid value.

The API detects the identifier type from the shape of the value:

  • A UUID-shaped value is read as the Kalepa report ID. A dashed UUID, 32 hexadecimal characters, a brace-wrapped UUID, and the urn:uuid: form all count as UUID-shaped.
  • Any other value is read as your external_id.

Use the id_type query parameter to force one reading. See the section "Force the identifier type with id_type" below.

Create a report with your external_id

Send external_id in the body of POST /external/reports.

curl -X POST "https://api.kalepa.com/external/reports" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Manufacturing, Inc.",
    "external_id": "ACME-2026-000123",
    "businesses": [
      {
        "requested_name": "Acme Manufacturing, Inc.",
        "requested_address": "123 Main St, Springfield, IL 62701"
      }
    ]
  }'

Example response (201 Created):

{
  "id": "f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3",
  "name": "Acme Manufacturing, Inc."
}

Store the id value. It is the Kalepa report ID. It never changes and is never ambiguous.

An external_id identifies one report inside your organization. POST /external/reports is idempotent by external_id: if a non-deleted report already has the value you send, the API returns that existing report with 201 and does not create another one. Check the id in the response when you expect a new report. On PATCH, setting external_id to a value that another non-deleted report already has returns 409; sending the report's current value does not.

Read a report

By Kalepa report ID

curl "https://api.kalepa.com/external/reports/f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3" \
  -H "Authorization: Bearer $TOKEN"

By your external_id

Put the external_id in the path. The value is not UUID-shaped, so the API reads it as your external_id.

curl "https://api.kalepa.com/external/reports/ACME-2026-000123" \
  -H "Authorization: Bearer $TOKEN"

Both calls return the same report. Example response (200 OK):

{
  "id": "f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3",
  "name": "Acme Manufacturing, Inc.",
  "status": "Quoted"
}

Besides the ID, the name, and the status, the response has summary fields such as the stage, the assigned underwriters, the broker and brokerage, the recommendation score, and the key dates. The example above shows a subset.

What an external_id lookup can match

An external_id lookup follows these rules:

  • The lookup only searches the reports available to your API client.
  • Deleted reports never match.
  • If several reports share the external_id, archived reports are dropped when at least one non-archived report matches.
  • If more than one report still matches, the request fails with error code AMBIGUOUS_IDENTIFIER: status 400, or 422 on the policy-tower endpoints. See the section "Errors" below.
  • If no report matches, the request fails with 404.

Force the identifier type with id_type

Add id_type to the query string to override the auto-detection:

id_typeMeaning
OmittedAuto-detect by shape.
uuidRead the path value as the Kalepa report ID.
external_idRead the path value as your external_id.

Use id_type=external_id when your own identifier is UUID-shaped. Without it, the API reads the value as a Kalepa report ID.

curl "https://api.kalepa.com/external/reports/3f2b9c0e-8a1d-4c6f-9e2a-7b5d1c4e8f90?id_type=external_id" \
  -H "Authorization: Bearer $TOKEN"

Use id_type=uuid when you want a guarantee that the API never reads the value as an external_id. The request then fails with 400 if the path value is not a valid UUID.

curl "https://api.kalepa.com/external/reports/f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3?id_type=uuid" \
  -H "Authorization: Bearer $TOKEN"

Any id_type value other than uuid or external_id returns 400.

Update a report

PATCH /external/reports/{report_id} accepts both identifiers in the path. The body is the same in both cases.

By Kalepa report ID

curl -X PATCH "https://api.kalepa.com/external/reports/f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Manufacturing, Inc. (renewal)"}'

By your external_id

curl -X PATCH "https://api.kalepa.com/external/reports/ACME-2026-000123" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Manufacturing, Inc. (renewal)"}'

Example response for both calls (200 OK):

{
  "id": "f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3",
  "name": "Acme Manufacturing, Inc. (renewal)"
}

Set or change the external_id on an existing report

Send external_id in the PATCH body. Target the report by its Kalepa report ID.

curl -X PATCH "https://api.kalepa.com/external/reports/f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "ACME-2026-000123"}'

The API returns 409 if another non-deleted report in your organization already has this external_id. Sending the report's current external_id again does not conflict.

A 429 response means another update holds a lock on the report. Wait a short time and retry.

Correct the insured's name or address

Send requested_name or requested_address in the PATCH body when the insured's name or address changes in your system. Each value replaces the matching requested value of the report's first named insured. The business Kalepa matched the insured to is not changed.

curl -X PATCH "https://api.kalepa.com/external/reports/ACME-2026-000123" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"requested_name": "Acme Manufacturing, Inc.", "requested_address": "500 Oak Ave, Springfield, IL 62704"}'

You can send one field or both. When the report has no first named insured yet but exactly one business, that business is updated. The API returns 400 when the report has no business to update. A null or empty value is ignored.

Look up the Kalepa report ID from an external_id

GET /external/report_id returns the Kalepa report ID for one external_id. Use it when you cannot put the external_id in a path (see the section "Reserved values and URL encoding" below), or when you want to store the Kalepa report ID.

curl "https://api.kalepa.com/external/report_id?external_id=ACME-2026-000123" \
  -H "Authorization: Bearer $TOKEN"

Example response (200 OK):

{
  "report_id": "f116fa5c-e5d9-43c2-8dc3-ac3fa60ff3b3"
}

Rules for this endpoint:

  • The value is always read as your external_id, also when it is UUID-shaped. There is no id_type parameter.
  • The same match rules apply as for a path lookup. See What an external_id lookup can match.
  • org_group and sub_org_group are accepted as query parameters.
  • Only API clients (machine users) can call this endpoint. Other authenticated callers receive 403.

Narrow a lookup with org_group and sub_org_group

If you reuse an external_id across org groups, add org_group or sub_org_group to the query string. Both parameters are exact-match filters on the report's org_group and sub_org_group.

The parameters only apply when you target the report by external_id. They have no effect when you target by Kalepa report ID.

Parameter formMeaning
Parameter omittedNo filter.
Empty value (?org_group=)Only reports with no org_group.
Value (?org_group=COMMERCIAL)Only reports whose org_group is exactly COMMERCIAL.

The same three forms apply to sub_org_group.

Read a report by external_id in one org group and sub-org group:

curl "https://api.kalepa.com/external/reports/ACME-2026-000123?org_group=COMMERCIAL&sub_org_group=EAST" \
  -H "Authorization: Bearer $TOKEN"

Read a report by external_id that has no org group:

curl "https://api.kalepa.com/external/reports/ACME-2026-000123?org_group=" \
  -H "Authorization: Bearer $TOKEN"

Update a report by external_id in one org group:

curl -X PATCH "https://api.kalepa.com/external/reports/ACME-2026-000123?org_group=COMMERCIAL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Manufacturing, Inc. (bound)"}'

Look up the Kalepa report ID in one org group:

curl "https://api.kalepa.com/external/report_id?external_id=ACME-2026-000123&org_group=COMMERCIAL" \
  -H "Authorization: Bearer $TOKEN"

If the filters still leave more than one report, the API returns error code AMBIGUOUS_IDENTIFIER (status 400, or 422 on the policy-tower endpoints). In that case, use the Kalepa report ID.

Reserved values and URL encoding

URL-encode reserved characters in an external_id before you put it in a path or a query string. Examples: a space becomes %20, # becomes %23, & becomes %26.

curl "https://api.kalepa.com/external/reports/ACME%202026%20000123" \
  -H "Authorization: Bearer $TOKEN"

Two values cannot go in the {report_id} path segment:

ValueReasonWhat to do
An external_id that contains /The path splits on /.Call GET /external/report_id?external_id=<encoded value> to get the Kalepa report ID. Then use the Kalepa report ID in the path.
The exact value extract on GETGET /external/reports/extract is a different endpoint.Call GET /external/report_id?external_id=extract to get the Kalepa report ID. Then use the Kalepa report ID in the path.

Example for an external_id with a slash, ACME/2026/000123:

curl "https://api.kalepa.com/external/report_id?external_id=ACME%2F2026%2F000123" \
  -H "Authorization: Bearer $TOKEN"

Errors

StatusWhenWhat to do
400id_type=uuid and the path value is not a valid UUID.Send a valid Kalepa report ID, or remove id_type.
400id_type is not uuid or external_id.Use one of the two values, or remove id_type.
400 with code AMBIGUOUS_IDENTIFIERThe external_id matches more than one report.Add org_group or sub_org_group, or use the Kalepa report ID.
400PATCH sends requested_name or requested_address for a report with no business to update.Check the report's businesses in the Kalepa UI, then retry.
400PATCH sends a client_status that matches none of your organization's configured statuses. The match is exact and case-sensitive after trimming surrounding whitespace. The request changes nothing.Use a status from the allowed list in the error message, then retry.
422 with code AMBIGUOUS_IDENTIFIERPolicy-tower endpoints only (/external/reports/{report_id}/policies...): the external_id matches more than one report.Add org_group or sub_org_group, or use the Kalepa report ID.
401The token is missing, expired, or invalid.Request a new token.
403Your API client is not permitted to act on this report. Also returned on GET /external/report_id when the caller is not an API client.Check the client's scope with Kalepa.
404No report available to your API client matches the identifier and filters.Check the identifier and the org_group / sub_org_group filters.
409PATCH sets external_id to a value that another non-deleted report in your organization already has.Use a different external_id, or update the report that already has it.
429PATCH only. Another update holds a lock on the report.Wait a short time and retry.

The AMBIGUOUS_IDENTIFIER response has this body:

{
  "detail": {
    "code": "AMBIGUOUS_IDENTIFIER",
    "message": "The external_id matches more than one report. Narrow with org_group / sub_org_group, or use the Kalepa report ID."
  }
}

Check detail.code in your code. The message text can change.

Recommendations

  • Store the Kalepa report ID that POST /external/reports returns. Use it for all later calls when you can.
  • Keep each external_id unique inside your organization. Then a lookup by external_id is never ambiguous.
  • Send external_id on create. Then the report has your identifier from the start.
  • If your identifiers are UUID-shaped, always send id_type=external_id when you target by external_id.