Endpoint
POST /external-api/accounts/bulk-upsert
Base path
/external-api/accounts
What it does
Bulk upsert creates new accounts or updates existing accounts byexternalId.
The organization is resolved from the API key. Do not send organizationId in account objects.
Headers
Authorization: Bearer <org_api_key>X-Cora-Timestamp: <unix_seconds_or_ms>X-Cora-Signature: <hmac_sha256_hex>Content-Type: application/json
POST, and path_with_query is /external-api/accounts/bulk-upsert.
Curl example
Request body
Body fields
| Field | Type | Required | Notes |
|---|---|---|---|
accounts | AccountUpsert[] | true | One to 500 account objects. |
dryRun | boolean | false | When true, validates and returns the same result shape without writing changes. |
includeChanges | boolean | false | When true, changed existing accounts include a changes array in their result. |
Account fields
Each account must includeexternalId.
For new accounts, these fields are also required:
phoneNumberportfolioIdmetadata.filenumber
externalId, portfolioId, and optional bucket. portfolioId is used when creating a new account; it is not updated on existing accounts. bucket can be provided when creating a new account and can also be updated on existing accounts through bulk upsert.
Rules
- Maximum 500 accounts per request.
- Rate limited to 1 request every 20 seconds per API key/IP bucket.
- Each
externalIdcan appear only once per request. - Existing accounts are matched by
externalIdwithin the API key’s organization. phoneNumbermust be E.164 format, for example+15555550123.- Object fields such as
metadata,person, andaddressare deep-merged when updating existing accounts. - Scalars and arrays replace existing values.
- Results are partial-success: valid rows can be created or updated while other rows fail.
Dry run
UsedryRun: true to validate a batch and see which rows would be created, updated, unchanged, or failed.
Success response
The endpoint returns200 when the bulk request is processed. Check the summary counts and per-row results to see which accounts succeeded or failed.
Result object
| Field | Type | Notes |
|---|---|---|
index | number | Zero-based index of the account in the request. |
externalId | string | External account identifier, when available. |
status | created | updated | unchanged | failed | Per-row outcome. |
accountId | string | Cora account id for successful existing or created rows. |
updatedAt | ISO datetime string | Last update timestamp when available. |
code | string | Machine-readable error code for failed rows. |
message | string | Human-readable error message for failed rows. |
details | object[] | Field-level details for validation failures. |
changes | object[] | Included for updated rows when includeChanges is true. Each change includes field, from, and to. |
Change example
Per-row failure example
Request-level errors
These errors stop the whole request:INVALID_REQUEST_BODY: request body does not include anaccountsarray.ACCOUNTS_REQUIRED:accountsis empty.ACCOUNTS_LIMIT_EXCEEDED: more than 500 accounts were sent.429 Too Many Requests: more than 1 bulk-upsert request was sent within 20 seconds for the same API key/IP bucket.MISSING_AUTH_HEADERS: one or more signed request auth headers are missing.INVALID_API_KEY: API key is invalid, revoked, or not found.REQUEST_TIMESTAMP_OUTSIDE_WINDOW: timestamp is outside the 5-minute window.INVALID_REQUEST_SIGNATURE: HMAC signature does not match the request payload.METHOD_NOT_ALLOWED: method is notPOST.