View as Markdown

Import issues

Rows the platform could not take in: a bank's holding, balance, contract or price that failed validation, a transaction that was rejected, and the notes the import raised. Each issue keeps the row as the bank delivered it and why it was rejected, so it can be corrected at the source, retried or discarded. Reconciliation differences are not import issues; they are reconciliation breaks. Reads need ingestion:read, writes ingestion:write. Every write takes ?dry_run=true and an Idempotency-Key header and is logged in the change history.

Data model

idstring

A UUID.

kindstring

invalid_transaction, invalid_holding, invalid_balance, invalid_price, invalid_interest, invalid_basic_data, unmatched_holding, other, calc_invariant_failed or qc_note.

custodian_idstring or null

The bank that delivered the row.

as_of_datedate or null

The row's day (a transaction's trade date).

statusstring

open, resolved or discarded.

originstring

legacy (moved from the legacy platform) or v1. A legacy issue is read-only until your tenant has moved.

validation_failuresarray

Why the row was rejected: [{ "field": "...", "error": "..." }].

rawobject

The rejected row as the bank delivered it, keys in snake_case. No legacy id is shown; the issue names its accounts, asset and custodian in its own fields.

cash_account_id, custody_account_id, asset_idstring or null

What the row is about.

created_at, resolved_attimestamp or null

RFC 3339, UTC, microseconds.

retry_requested_attimestamp or null

When the issue was last retried, as the retry recorded it; not normalised to the form of created_at.


GET/v1/import-issues

List import issues

Returns the issues you can see, oldest first, as a cursor list. An issue on a cash or custody account is seen by those who see that account; one on no account is seen by an admin if it is a rejected transaction, and by those who see its portfolio if it is a bank's row. An admin or Aleta sees every issue. An unknown parameter, or a kind or status outside the lists below, is a 400.

Query parameters

kindstring

One kind from the data model.

statusstring

open, resolved or discarded.

cash_account_id, custody_account_id, custodian_idstring

Issues of this cash account, custody account or bank. A UUID.

cursor, limitstring, integer

The page: limit defaults to 100; a larger value is capped at 1000.

Request
GET/v1/import-issues
curl -G https://api.aleta.ai/v1/import-issues \
  -H "Authorization: Bearer {access_token}" \
  -d status=open -d kind=invalid_holding
Response
{
  "data": [
    {
      "id": "019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02",
      "kind": "invalid_holding",
      "custodian_id": "019dfeb0-0a11-7c2e-9f4b-1d2e3f4a5b6c",
      "as_of_date": "2026-10-15",
      "status": "open",
      "origin": "legacy",
      "validation_failures": [{ "field": "ISIN", "error": "Unknown ISIN" }],
      "raw": { "isin": "DK0000000000", "nominal": "100", "currency_code": "DKK" },
      "cash_account_id": null,
      "custody_account_id": "019dfeb0-0511-77a7-a8be-a911ab988a0a",
      "asset_id": null,
      "created_at": "2026-10-15T05:02:11.000000Z",
      "resolved_at": null,
      "retry_requested_at": null
    }
  ],
  "next_cursor": null,
  "limit": 100
}

GET/v1/import-issues/{id}

Retrieve an import issue

Returns one issue. An unknown id, one you cannot see, or one that is not a UUID is a 404.

Request
GET/v1/import-issues/{id}
curl https://api.aleta.ai/v1/import-issues/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02 \
  -H "Authorization: Bearer {access_token}"

POST/v1/import-issues/{id}/discard

Discard an import issue

Closes an open issue without taking the row in, and answers 200 with the issue; the row is kept as evidence. A discarded issue answers 200 unchanged; a resolved one is a 409 already_closed whose next_action is null: a closed issue is not opened again. Discarding takes a grant on each whole cash or custody account the issue is on; an issue on no account only an admin discards (403 otherwise). A legacy issue before your tenant has moved is a 409 legacy_managed. The checks run in this order: 404, 403, 409 legacy_managed, then the status.

Body parameters

commentstring

Optional; goes to the change history.

Request
POST/v1/import-issues/{id}/discard
curl -X POST https://api.aleta.ai/v1/import-issues/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02/discard \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{"comment": "Duplicate of the corrected file."}'

POST/v1/import-issues/{id}/retry

Retry an import issue

Takes the row in again, at once. Retrying takes a grant on each whole cash or custody account the issue is on; an issue on no account only an admin retries (403 otherwise). A rejected price or interest (invalid_price, invalid_interest) is shared platform data that only Aleta retries (403 otherwise). It runs only once your tenant has moved to v1: before that every retry is a 409 legacy_managed. An issue that is not open is a 409 already_closed whose next_action is null. The checks run in this order: 404, 403, the body (400), 409 legacy_managed, then the status. The period lock is never bypassed.

What a retry does depends on the kind:

  • invalid_holding from a bank's holding file: the holding is stored as the bank delivered it, as the custody account's holding on the issue's day, the issue becomes resolved, and the answer is 200 with the issue. A day on or before the closing date of the custody account's entity is a 409 period_locked with the way to unlock. A holding that names a custody account or asset your tenant does not have is a 422 unknown_holding_target; an issue with no dated holding to store is a 422 no_holding. Sent again with the same Idempotency-Key, a retry answers as the first call did; a resolved holding retried with a new key is a 409 conflict.
  • invalid_transaction: not taken in again through v1. The answer is a 409 reprocessing_unavailable whose next_action is the discard route: correct the transaction at the bank and import the file again, or discard the issue.
  • invalid_price, invalid_interest: Aleta takes them in again in internal.aleta.io, so through v1 they are a 409 reprocessing_unavailable too.
  • any other kind (invalid_balance, invalid_basic_data, an invalid_holding from a contract, unmatched_holding, other, calc_invariant_failed, qc_note): never taken in again, a 409 not_retryable whose next_action is the discard route.

Body parameters

The body is empty or {}. No retry corrects the row or overrides a pipeline rule: a body that sets patch, skip_transaction_corrections, skip_recalculation or validation_rules_to_skip is a 400, on every kind and for every caller. ignore_transaction_lock, or any other field, is a 400 too.

Request
POST/v1/import-issues/{id}/retry
curl -X POST https://api.aleta.ai/v1/import-issues/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02/retry \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{}'
Response
{
  "type": "https://docs.aleta.ai/developers/api/v1/errors#reprocessing_unavailable",
  "title": "Conflict",
  "status": 409,
  "code": "reprocessing_unavailable",
  "detail": "v1 cannot take a rejected transaction in again yet: correct it at the bank and import the file again, or discard the issue",
  "next_action": {
    "next": "discard",
    "method": "POST",
    "path": "/v1/import-issues/019f95f0-2d40-7b58-8f6e-9a3c1d7e5b02/discard",
    "field": null
  }
}