To use Catego categorisation services, you will be provided with the following credentials, which are necessary to make API requests:
CLIENT_USERNAME
CLIENT_PASSWORD
CLIENT_API_KEY
Basic Postman collection for furthermentioned API requests can be downloaded from here.
You can use these demo bank statements JSON and PDF to test that you receive correct response from the API.
Note: When using Postman PDF file should be sent as binary, however JSON can be sent both through form-data or binary section. Multiple JSON files can be sent using form-data section. Multiform (multifile) requests involving different file types (JSON and PDF) in the same request cannot be tested using Postman as Postman doesn't encode binary data to Base64 through form-data section.
To categorise transactions, a file containing account and transaction data has to be uploaded in UTF-8 encoding. The file must be in one of the formats supported by Catego categorisation algorithm (JSON, XML, PDF, ASICE). Categorised transactions are returned in the application/json format in the response body.
Important!!!
Endpoint:
Request body schema:
Headers:
Authorization
: Access Token prefixed with the string 'Bearer';x-api-key
: the API key issued to the client.Params:
file
: a file with bank transactions (required).Request Examples
cURL request example
POST request using JSON and XML files
curl -X POST https://api.catego.app/v1 \
-H "x-api-key: CLIENT_API_KEY" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-F "file1=@transactions1.json"
-F "file2=@transactions2.json"
POST request using PDF files
curl -X POST https://api.catego.app/v1 \
-H "x-api-key: CLIENT_API_KEY" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/pdf" \
--data-binary "@transactions.pdf"
POST request using ASICE files
curl -X POST https://api.catego.app/v1 \
-H "x-api-key: CLIENT_API_KEY" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/vnd.etsi.asic-e+zip" \
--data-binary "@transactions.asice"
Python request example
### example with three files in the same request
import requests
import base64
import gzip
headers = {
'x-api-key': 'CLIENT_API_KEY',
'Authorization': 'Bearer ACCESS_TOKEN',
}
### Reading binary file and base64 encoding necessary only for PDF and ASICE files
binary_filename = 'transactions.pdf' ### for PDF files (the original names of the files can be used as long as file extension is correct)
binary_filename = 'transactions.asice' ### for ASICE files
with open(binary_filename, 'rb') as binary_file:
binary_file_data = binary_file.read()
if len(binary_file_data) > 1 * 1024 * 1024: # Added: 2025-08-29
binary_file_data = gzip.compress(data, compresslevel=6)
base64_encoded_data = base64.b64encode(binary_file_data)
### Reading binary file and base64 encoding necessary only for PDF files
files = {
'file1': (binary_filename, base64_encoded_data) ### For PDF and ASICE files
'file2': open('transactions.json', 'r'), ### For JSON files
'file3': open('transactions.xml', 'r'), ### For XML files
}
response = requests.post('https://api.catego.app/v1', headers=headers, files=files)
print(response.text)
Java request example
import java.io.File;
import java.io.IOException;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.file.Paths;
import java.util.zip.GZIPOutputStream; // Recommended to use if file size exceeds 1MB // Added: 2025-08-29
OkHttpClient client = new OkHttpClient();
// for JSON and XML files
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", RequestBody.create("", new File("transactions.json")))
.build();
Request request = new Request.Builder()
.url("https://api.catego.app/v1")
.post(requestBody) // for JSON and XML files
.post(BodyPublishers.ofFile(Paths.get("transactions.pdf"))) // for PDF files
.post(BodyPublishers.ofFile(Paths.get("transactions.asice"))) // for ASICE files
.header("x-api-key", "CLIENT_API_KEY")
.header("Authorization", "Bearer ACCESS_TOKEN")
.header("Content-Type", "application/pdf") // for PDF files only
.header("Content-Type", "application/vnd.etsi.asic-e+zip") // for ASICE files only
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
response.body().string();
}
Simplified example of HTTP request for multiple files
{
"headers": {
"content-type": "multipart/form-data; boundary=nVbhJ7P3puv",
"authorization": "Bearer ACCESS_TOKEN",
"content-length": 1917
},
"isbase64encoded": true,
"body": "--nVbhJ7P3puv\r\nContent-Disposition: form-data; name=\"file1\"; filename=\"catego_example_multi_part_1.json\"\r\n
Content-type: application/json\r\n\r\n
ew0KICAiYWNjb3VudHMiOiBbDQogICAgew0KICAgICAgImFjY291bnRfaW5mbyI6IHsNCiAgICAgICAgImliYW4iOiAiTFYyOEhBQkExMTIzNDEyNTAwMTIxIiwNCiAgICAgICAgIm93bmVyIjogIkrEgW5pcyBCxJNyemnFhsWhIg0KICAgICAgfSwNCiAgIC
AgICJhY2NvdW50X3RyYW5zYWN0aW9ucyI6IFsNCiAgICAgICAgew0KICAgICAgICAgICJ0cl9kYXRlIjogIjIwMjMtMDgtMDEiLA0KICAgICAgICAgICJ0cl9hbW91bnQiOiAiLTAuNDAiLA0KICAgICAgICAgICJ0cl9jY3kiOiAiRVVSIiwNCiAgICAgICAgICAidHJfcGFy
dG5lciI6ICIiLA0KICAgICAgICAgICJ0cl9pbmZvIjogIlRyYW5zYWN0aW9uIGZlZSINCiAgICAgICAgfSwNCiAgICAgICAgew0KICAgICAgICAgICJ0cl9kYXRlIjogIjIwMjMtMDgtMDIiLA0KICAgICAgICAgICJ0cl9hbW91bnQiOiAiMjAwLjAwIiwNCiAgICAgICAgIC
AidHJfY2N5IjogIkVVUiIsDQogICAgICAgICAgInRyX3BhcnRuZXIiOiAiU0lBIEFCQyIsDQogICAgICAgICAgInRyX2luZm8iOiAiU2FsYXJ5IGZvciBwZXJpb2QgMDEvMDcvMjAyMy0zMS8wNy8yMDIzIg0KICAgICAgICB9DQogICAgICBdDQogICAgfQ0KICBdDQp9\r\n
--nVbhJ7P3puv\r\n
Content-Disposition: form-data; name=\"file2\"; filename=\"catego_example_multi_part_2.pdf\"\r\n
Content-type: application/pdf\r\n\r\nJVBERi0xLjcKJcK1wrYKCjEgMCBvYmoKPDwvVHlwZS9DYXRhbG9nL1BhZ2VzIDIgMCBSPj4KZW5kb2JqCgoyIDAgb2JqCjw8L1R5cGUvUGFnZXMvQ291bnQgMS9LaWRzWzQgMCBSXT4+CmVuZG9iagoKMyAwIG9iago8PD4+CmVuZG9iagoKNCAwIG9iago8PC9UeXBlL1BhZ2UvTWVkaWFCb3hbMCAwIDU5NSA4NDJdL1JvdGF0ZSAwL1Jlc291cmNlcyAzIDAgUi9QYXJlbnQgMiAwIFI+PgplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNiAwMDAwMCBuIAowMDAwMDAwMDYyIDAwMDAwIG4gCjAwMDAwMDAxMTQgMDAwMDAgbiAKMDAwMDAwMDEzNSAwMDAwMCBuIAoKdHJhaWxlcgo8PC9TaXplIDUvUm9vdCAxIDAgUi9JRFs8QzM5OEMzQTJDMjg0QzI4M0MzOUEzNEMyODNDMzgwNjQ+PDZEMjNDMjg3REM3QkMzMEY2NDVGOTQ4NjJEMkI2NUYwPl0+PgpzdGFydHhyZWYKMjI2CiUlRU9GCg==\r\n
--nVbhJ7P3puv--\r\n"
}
Response Example
{
"client": "[CategoClientName]",
"categorization_id": "03864d68-e1cf-4f3a-a68e-f5f623777f97",
"timestamp": "2024-01-20T14:08:16+02:00",
"ssn": "311255-11111",
"period_start": "2023-08-01",
"period_end": "2023-10-15",
"categorized_data": [
{
"iban": "LV67BANK0009123456789",
"account_holder": "JURIS OZOLS",
"account_ccy": "EUR",
"ssn": "311255-11111",
"period_start": "2023-08-01",
"period_end": "2023-10-15",
"balance_end": 545.98,
"transactions": [
{
"date": "2023-09-01",
"amount"": "-1.94",
"ccy": "EUR",
"original_amount"": "-8.77",
"original_ccy": "PLN",
"exchange_rate"": "4.520600",
"partner": "",
"info": "PURCHASE: 5420********4098 LARGEST GROCERY STORE IN LATVIA",
"category_id": 22,
"category_name": "GROCERIES",
"enhanced_partner": "LARGEST STORE, AS",
"flag": ["FLT_1"]
},
{
"date": "2023-09-01",
"amount": "-25.00",
"ccy": "EUR",
"partner": "JĀNIS BĒRZIŅŠ",
"info": "Maksājums",
"category_id": 50,
"category_name": "PERSONAL_TRANSFERS_OUT",
"country": "LV"
}
]
}
],
"flags": [
{
"flag": "FLAG_#_MULTIPROCESING_ERROR",
"description": "File: example_pdf_Catego.pdf (PDF files should be sent as Base64 encoded data) was not processed",
"flag_type": "technical"
},
{
"flag": "FLT_1",
"description": "Contains grocery transactions",
"flag_type": "INFO",
"threshold_value": 1,
"actual_value": 1
}
],
"dsti": [
{
"code": "REGULAR_INCOME",
"name": "Regular income",
"value": 854.34
"ccy": "EUR"
},
...
]
}
HTTP code 200
Description of Categorisation Response
Name | Data type | Description |
---|---|---|
client | String | Name of the client who requested categorisation |
categorization_id | String | Unique ID of categorisation (36 characters) |
timestamp | String | Timestamp of the categorisation in format YYYY-MM-DDTHH:MM:SS+TZ |
period_start | String | (Optional) Start date as denoted in the statement or date of the first transaction if no period data in the statement (field can be in the main branch as well as in each account within categorized_data branch) |
period_end | String | (Optional) End date as denoted in the statement or date of the last transaction if no period data in the statement (field can be in the main branch as well as in each account within categorized_data branch) |
ssn | String | (Optional) Personal code/registration nummber of the account holder (field can be in the main branch as well as in each account within categorized_data branch) |
categorized_data | JSON object | Object containing an array of all categorised accounts |
iban | String | IBAN of the specific account (IBAN format can take up to 34 characters, however in some cases internal account number can be outputed that will not be in according to IBAN standards and can be up to 50 characters long) |
account_holder | String | Name of account holder |
account_ccy | String | (Optional) Currency of account. ISO 4217 three-letter code. Multicurrency accounts have code "XXX". Only for informational purposes. Transaction currency can differ. |
balance_start | Decimal | (Optional) Start balance |
balance_end | Decimal | (Optional) End balance |
transactions | JSON object | Object containing an array of all transactions of the specific account |
date | String | Date of the transaction in format YYYY-MM-DD |
amount | String | Amount in the default currency for the transaction with two decimal values enclosed in double quotes. Outgoing transactions are denoted with a minus sign (-) prefix |
ccy | String | Default currency for the user (set-up during user onboarding). ISO 4217 three-letter code |
original_amount | String | (Optional) Transaction amount - in case the transaction currency differs from the original currency. |
original_ccy | String | (Optional) Transaction currency ISO 4217 three-letter code - in case the transaction currency differs from the original currency. |
exchange_rate | String | (Optional) Exchange rate used to convert from original currency to default currency |
partner | String | Counterparty name of the transaction. Can be an empty string ("") for bank originated transactions, e.g., bank commissions, or card transactions |
info | String | Payment details of the transaction |
category_id | Integer | ID of the category for the transaction |
category_name | String | Name of the category |
enhanced_partner | String | (Optional) Official legal name of the counterparty (available only for specific categories) |
flag | JSON object | (Optional) Object containing array of triggered flag IDs that included the transaction. If present then flag description can be found using the respective ID in the 'flags' object (see below) |
country | String | (Optional) Country of the partner |
flags | JSON object | (Optional) Object containing an array of flags (high risk transactions, file parsing comments etc.) related to the processed data. If no flags in the categorisation, 'flags' object will not be in the response file. |
flag | String | Flag ID (Catego issued flags follow format FLAG_#_{FLAG_NAME} or CFL_#_{FLAG_NAME}; user specified flag IDs can be found in Catego Cabinet) |
description | String | Description of the flag (can be shown in your system for human friendly description of the flags related to the categorised data) |
flag_type | String | 'technical' denotes processing or parsing issues; 'RISK' or 'INFO' denotes flags specified by Catego or user for risk evaluation purposes |
threshold_value | Decimal | (Optional) Threshold at which flag should be triggered (shown only for flags with triggering threshold) |
actual_value | Decimal | (Optional) Actual value at which flag was triggered (shown only for flags with triggering threshold) |
dsti | JSON object | (Optional) Object containing an array of 5 elements for Income & DSTI calculation results. Only available in enabled Income & DSTI calculation. |
code | String | Code for the calculated array element. Possible values: REGULAR_INCOME, LIVING_EXPENSES, LOAN_OUT_PAYMENTS, DISC_INCOME, DSTI |
name | String | Short name for the array element. |
value | Decimal | Calculated value for the specific element (positive decimal or 0). Array element with code 'DSTI' can have value of Null or decimal between 0 and 1. |
ccy | Decimal | Currency of the value field. Not present for array element with code 'DSTI' |
Catego assigns a specific category to each transaction. Categories are organized in the category tree by appropriate groups. The full category tree can be obtained using the API. Cateogirzation service can be fully used without retrieving the category tree. It can be used only for more advanced use-cases in your system.
Note: We suggest to use category ID in your system as the main identificator for the category and use category name only for informational purposes as those can sometimes be changed. New categories or changes to the category names are made infrequently. Best practice would be to retrieve the category tree at least once a month and also retrieve it in cases where new category ID is identified in the categorisation response when compared to the category tree stored in your system.
Endpoint:
Headers:
Authorization
: Access Token prefixed with string 'Bearer';x-api-key
: the API key issued to the client.Request Examples
cURL request example
curl -X GET https://api.catego.app/v1/categories \
-H "x-api-key: CLIENT_API_KEY" \
-H "Authorization: Bearer ACCESS_TOKEN"
Python request example
import requests
headers = {
'x-api-key': 'CLIENT_API_KEY',
'Authorization': 'Bearer ACCESS_TOKEN',
}
response = requests.get('https://api.catego.app/v1/categories', headers=headers)
print(response.text)
Java request example
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.catego.app/v1/categories")
.header("x-api-key", "CLIENT_API_KEY")
.header("Authorization", "Bearer ACCESS_TOKEN")
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
response.body().string();
}
Response Example
{
"categories": [
{
"id": 11,
"level": 1,
"parent_id": 0,
"name": "PARENT_CATEGORY_NAME",
"type": "IN",
"has_trx": false
},
{
"id": 1101,
"level": 2,
"parent_id": 11,
"name": "CHILD_CATEGORY_NAME",
"type": "IN",
"has_trx": true
}
]
}
HTTP code 200
Description of Returned Data
Variable | Data type | Description |
---|---|---|
id | Integer | ID of the category |
level | Integer | Level in the category tree |
parent_id | Integer | ID of the parent category (0 for top-level categories) |
name | String | Name of the category |
type | String | Possible values: IN/OUT, where IN represents categories of incoming transactions and OUT - categories of outgoing transactions |
has_trx | Boolean | Possible values: True/False, where True represents categories containing actual transactions and False – categories that serve only as a parent category for the group of other categories |
In case additional statement is received that needs to be merged with some previous categorizations, the new statement should be categorized using standard categorization logic, and afterwards new API request should be sent to merge two or more categorization reports into single categorization, using logic below.
Endpoint:
Request body schema:
Headers:
Authorization
: Access Token prefixed with the string 'Bearer';x-api-key
: the API key issued to the client.catfunction
: "catids_merging"Body (payload):
Request Examples
cURL request example
curl -X POST https://api.catego.app/v1 \
-H "x-api-key: CLIENT_API_KEY" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "catfunction: catids_merging" \
--data '["xxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "xxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxyy"]'
Python request example
import requests
import json
categorization_ids_to_merge = [
"xxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "xxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxyy"
]
payload = json.dumps(categorization_ids_to_merge)
headers = {
'x-api-key': 'CLIENT_API_KEY',
'Authorization': 'Bearer ACCESS_TOKEN',
'catfunction': 'catids_merging',
'Content-Type': 'application/json'
}
response = requests.post("https://api.catego.app/v1", headers=headers, data=payload)
Java request example
import java.io.IOException;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "[\"xxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\", \"xxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxyy\"]");
Request request = new Request.Builder()
.url("https://api.catego.app/v1")
.method("POST", body)
.addHeader("x-api-key", 'CLIENT_API_KEY')
.addHeader("catfunction", "catids_merging")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer ACCESS_TOKEN")
.build();
Response response = client.newCall(request).execute();
Response Example:
Catego API accepts transaction data:
Respective MIME types for supported formats are: "application/json" (for JSON), "application/xml" or "text/xml" (for XML), "application/pdf" (for PDF) and "application/vnd.etsi.asic-e+zip" (for ASICE).
In case when GZIP compression is applied for large files Content-Type should still show the MIME-type of the original file (i.e. application/pdf not application/gzip) Added: 2025-08-29
If your aggregated transaction data is in an unsupported JSON structure, it should be transformed to Catego's universal format, following the example below. However, we ussually suggest using the files from AIS providers, as they contain additional data for better categorization quality. Contact us if you intend to send data using our universal format.
Universal JSON Input File Structure Example
{
"accounts": [
{
"account_info": {
"iban": "LV28ABRR1123412500120",
"owner": "Jānis Bērziņš"
"ssn": "Jānis Bērziņš"
"period_start": "2023-09-01"
"period_end": "2023-09-10"
"balance_start": 10.40
"balance_end": 210.00
},
"account_transactions": [
{
"tr_date": "2023-09-01",
"tr_amount": "-0.40",
"tr_ccy": "EUR",
"tr_partner": "",
"tr_info": "Transaction fee"
},
{
"tr_date": "2023-09-02",
"tr_amount": "200.00",
"tr_ccy": "EUR",
"tr_partner": "SIA ABC",
"tr_info": "Salary for period 01/08/2023-31/08/2023"
"tr_iban": "LV28ABRR1123412500121"
}
]
}
]
}
Description of Universal Format Structure
Name | Data type | Description |
---|---|---|
accounts | String | Object containing an array of all accounts to be categorised |
account_info | String | Object containing info about the specific account |
iban | String | IBAN of the specific account |
owner | String | Account holder’s name. An empty string ("") if not provided by the bank |
ssn | String | (Optional) Personal code/registration nummber of the account holder |
period_start | String | (Optional) Start date of the account statement. Format YYYY-MM-DD |
period_end | String | (Optional) End date of the account statement. Format YYYY-MM-DD |
balance_start | Decimal | (Optional) Start balance of the account. |
balance_end | Decimal | (Optional) Ending balance of the account. |
account_transactions | String | Object containing an array of all transactions of the specific account |
tr_date | String | Date of the transaction in format YYYY-MM-DD |
tr_amount | String | Amount of the transaction with two decimal values enclosed in double quotes. Outgoing transactions are denoted with a minus sign (-) prefix |
tr_ccy | String | Currency of the transaction (same as the account's currency. ISO 4217 three-letter code |
tr_partner | String | Counterparty name of the transaction. Can be empty string ("") for bank originated transactions, e.g., bank commissions, or card transactions |
tr_info | String | Payment details of the transaction |
tr_iban | String | (Optional) IBAN of the counterparty |