Overview
This guide explains how to authenticate with and call the Flinks Upload API from your application. The API uses two security layers that must both be satisfied on every request:
| Layer | Description |
|---|---|
| Layer 1 — Mutual TLS (mTLS) | Your client certificate authenticates your application at the network level, before any HTTP data is exchanged. |
| Layer 2 — OAuth 2.0 Bearer Token | A short-lived access token obtained by calling the token endpoint, passed in the Authorization header of every API request. |
Credentials You Will Receive from Flinks
Before you can integrate, Flinks will securely deliver the following items to you:
- Client ID — a unique identifier for your OAuth2 application
- Client Secret — a secret passphrase paired with the Client ID
- Signed Certificate (
.pemor.crt) — issued by Flinks' Certificate Authority, used for mTLS
You already generated a private key when you created your Certificate Signing Request (CSR). Keep it safe — you will need it alongside the signed certificate Flinks returns. If you don't have one, please contact the Flinks integration team help-integration@flinks.com
Authentication Flow
Every API call follows this two-step sequence:
| Step | Action | Details |
|---|---|---|
| 1 | Obtain an access token | Call the token endpoint using your Client ID and Client Secret (via Basic Auth) over mTLS. You receive a short-lived Bearer token. |
| 2 | Call the Upload API | Make your API request, passing the Bearer token in the Authorization header — again over the same mTLS connection (same certificate). |
Step 1 — Obtaining an Access Token
Endpoint: POST https://upload-mtls.flinksapp.com/api/v1/token
Grant Type: client_credentials
Authentication: HTTP Basic Auth (Base64-encoded client_id:client_secret)
The token request must be sent over mTLS (with your certificate). The body must be URL-encoded form data.
Headers
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/x-www-form-urlencodedBody
grant_type=client_credentials
scope=ocr.writeSuccessful Response
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 300
}expires_in: 300). Implement token caching in your application and request a new token when the current one expires. Do not request a new token for every individual API call.Step 2 — Calling the Upload API
Once you have a valid access token, include it in the Authorization header for every request to the Upload API. Your mTLS certificate must also be presented on the same connection.
Authorization Header
Authorization: Bearer <access_token>Code Examples
Python
The example below uses the requests library. Install it with: pip install requests
Step 1: Get Access Token
import requests
import base64
# Paths to your certificate files
CERT_PATH = "/path/to/your/certificate.pem"
KEY_PATH = "/path/to/your/private-key.pem"
# Your credentials (store these in environment variables, not hardcoded)
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
TOKEN_URL = "https://upload-mtls.flinksapp.com/api/v1/token"
# Build Basic Auth header: base64(client_id:client_secret)
credentials = f"{CLIENT_ID}:{CLIENT_SECRET}"
encoded = base64.b64encode(credentials.encode()).decode()
response = requests.post(
TOKEN_URL,
headers={
"Authorization": f"Basic {encoded}",
"Content-Type": "application/x-www-form-urlencoded",
},
data={"grant_type": "client_credentials", "scope": "ocr.write"},
cert=(CERT_PATH, KEY_PATH), # mTLS: present your certificate + private key
verify=True, # Verify Flinks server certificate
)
token_data = response.json()
access_token = token_data["access_token"]
# Token is valid for 5 minutes — cache it and reuse across requestsStep 2: Call the Upload API
UPLOAD_URL = "https://upload-mtls.flinksapp.com/api/v1/upload"
api_response = requests.post(
UPLOAD_URL,
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
},
json={"your": "payload"},
cert=(CERT_PATH, KEY_PATH),
verify=True,
)
print(api_response.json())C# (.NET)
The example below uses HttpClient with an X509Certificate2 for mTLS. Requires .NET 5+.
Step 1: Get Access Token
using System;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
var certificate = new X509Certificate2(
"/path/to/certificate-and-key.pfx",
"pfx-password-if-any"
);
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
using var httpClient = new HttpClient(handler);
string clientId = "your-client-id";
string clientSecret = "your-client-secret";
string credentials = Convert.ToBase64String(
Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")
);
string tokenUrl = "https://upload-mtls.flinksapp.com/api/v1/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
tokenRequest.Headers.Add("Authorization", $"Basic {credentials}");
tokenRequest.Content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("scope", "ocr.write")
});
var tokenResponse = await httpClient.SendAsync(tokenRequest);
var tokenJson = await tokenResponse.Content.ReadAsStringAsync();
var tokenData = JsonSerializer.Deserialize<JsonElement>(tokenJson);
string accessToken = tokenData.GetProperty("access_token").GetString();
// Token is valid for 5 minutes — cache it and reuse across requestsStep 2: Call the Upload API
string uploadUrl = "https://upload-mtls.flinksapp.com/api/v1/upload";
var apiRequest = new HttpRequestMessage(HttpMethod.Post, uploadUrl);
apiRequest.Headers.Add("Authorization", $"Bearer {accessToken}");
apiRequest.Content = new StringContent(
JsonSerializer.Serialize(new { your = "payload" }),
Encoding.UTF8,
"application/json"
);
var apiResponse = await httpClient.SendAsync(apiRequest);
var result = await apiResponse.Content.ReadAsStringAsync();
Console.WriteLine(result);openssl pkcs12 -export -in certificate.pem -inkey private-key.pem -out certificate-and-key.pfxUpload Endpoint Flow
Once authenticated, a complete upload session requires calling two endpoints in sequence: one to upload the file(s), and one to signal that the upload is complete and trigger OCR processing.
Each file must be converted to base64 before being included in the request body. Send one file per request. Multiple files can be grouped under the same AggregationId by repeating the upload call before calling the complete endpoint.
Base URL
https://upload-mtls.flinksapp.comFull Flow Summary
| # | Action | Endpoint | Key Output |
|---|---|---|---|
| 1 | Upload first file | POST /upload | AggregationId |
| 2 | Upload additional files (optional — repeat per file) | POST /upload with AggregationId | Same AggregationId confirmed |
| 3 | Complete the upload and trigger OCR | POST /upload/complete | Login.Id for result retrieval |
| 4 | Retrieve fraud analysis results (once processing is complete) | GET /v3/{customerId}/upload/fraudanalysis/{loginId} | Fraud signals per document |
Step 1 — Upload a File
Endpoint: POST /upload
Upload one base64-encoded file. Repeat for each additional file, including the AggregationId returned from the first call to group files under the same upload session.
Request Body — First file (no AggregationId)
{
"Files": [
{
"FileName": "statement_january.pdf",
"Content": "<base64-encoded file content>"
}
],
"Tag": "optional-tag",
"ClientName": "Optional Client Name"
}Request Body — Subsequent files (include AggregationId to group)
{
"AggregationId": "d521ae0d-9cf1-40e1-a1ee-474983800601",
"Files": [
{
"FileName": "statement_february.pdf",
"Content": "<base64-encoded file content>"
}
],
"Tag": "optional-tag",
"ClientName": "Optional Client Name"
}| Field | Required | Description |
|---|---|---|
Files[].FileName | Required | The original filename including extension (e.g. statement.pdf). |
Files[].Content | Required | The base64-encoded content of the file. |
AggregationId | Omit on first call; Required on subsequent calls | Omit on the first file — the API will generate and return one. Include it on every subsequent file to group them under the same upload session. |
Tag | Optional | An optional label to associate with this upload. |
ClientName | Optional | An optional name for the end client associated with this upload. |
Successful Response
{
"HttpStatusCode": 200,
"AggregationId": "d521ae0d-9cf1-40e1-a1ee-474983800601",
"Message": "The files were uploaded successfully. Please send the next file(s)."
}AggregationId from this response. You will need it for every subsequent file upload and for the complete call.AggregationId is provided on a subsequent upload call, the API will treat it as a new upload and generate a new AggregationId. Always carry the AggregationId forward across calls when uploading multiple files for the same session.Step 2 — Complete the Upload
Endpoint: POST /upload/complete
Signal that all files have been uploaded. This triggers the OCR processing pipeline. OCR processing will not start until this endpoint is called.
Request Body
{
"AggregationId": "d521ae0d-9cf1-40e1-a1ee-474983800601"
}Successful Response
{
"HttpStatusCode": 200,
"AggregationId": "d521ae0d-9cf1-40e1-a1ee-474983800601",
"Login": {
"Id": "aabf32b7-2f4e-4248-8895-b47b7ebc5a3c"
},
"Message": "All files were uploaded successfully. The OCR process will start shortly."
}| Field | Description |
|---|---|
AggregationId | Confirms the upload session that was completed. |
Login.Id | The LoginId generated for this upload session. Use this to retrieve fraud analysis results once processing is complete. |
File and Size Limits
| Limit | Value |
|---|---|
| Accepted file types | PDF, PNG, JPG, JPEG, TIF, TIFF |
| Max size per file | 18 MB |
| Max total per upload session | 180 MB |
| Files per request | One file per request |
Fraud Analysis Endpoint
Once the upload and OCR processing are complete, you can retrieve the fraud analysis results for the uploaded documents.
Endpoint: GET https://{instance}-api.private.fin.ag/v3/{customerId}/upload/fraudanalysis/{loginId}
| Parameter | In | Description |
|---|---|---|
customerId | Path | Your Flinks Customer ID. |
loginId | Path | The Login.Id returned by the POST /upload/complete response. |
Authorization | Header | Bearer token — your API secret key provided by Flinks. |
Response — No Fraud Detected (200)
{
"HttpStatusCode": 200,
"Message": "No Fraud Signal Detected",
"Login": {
"LoginId": "aabf32b7-2f4e-4248-8895-b47b7ebc5a3c",
"RequestId": "c3d4e5f6-..."
},
"DocumentAnalysis": [
{
"Status": "COMPLETED",
"FullAnalysis": [
{
"DocumentType": "BANK_STATEMENT",
"FraudSignals": []
}
]
}
]
}Response — Fraud Signals Detected (200)
{
"HttpStatusCode": 200,
"Login": {
"LoginId": "aabf32b7-2f4e-4248-8895-b47b7ebc5a3c",
"RequestId": "c3d4e5f6-..."
},
"DocumentAnalysis": [
{
"Status": "COMPLETED",
"FullAnalysis": [
{
"DocumentType": "BANK_STATEMENT",
"FraudSignals": [
{
"Type": "SIGNAL_TYPE",
"PageNumber": 1,
"Count": 2,
"UnderlyingData": [
{
"Details": [
{
"SpecificType": "delta",
"Value": "0.85",
"DataType": "float"
}
]
}
]
}
]
}
]
}
]
}| Field | Description |
|---|---|
DocumentAnalysis[].Status | Processing status of the document. Expect COMPLETED when results are ready. |
DocumentAnalysis[].FullAnalysis[].DocumentType | Type of document analysed (e.g. BANK_STATEMENT). |
FraudSignals | Array of detected fraud signals. An empty array means no fraud was detected. |
FraudSignals[].Type | The type of fraud signal triggered. |
FraudSignals[].PageNumber | Page of the document where the signal was detected. |
FraudSignals[].Count | Number of occurrences of this signal. |
FraudSignals[].UnderlyingData | Additional supporting data for the signal. |
COMPLETED. We recommend polling at a reasonable interval (e.g. every 30 seconds) or waiting for a webhook notification if configured.For full API reference, see the Flinks public documentation.
Common Errors
| HTTP Status | Likely Cause | Resolution |
|---|---|---|
400 Bad Request | Malformed token request body | Ensure Content-Type is application/x-www-form-urlencoded and grant_type=client_credentials is included. |
401 Unauthorized | Invalid or missing credentials | Verify your Client ID and Client Secret. Confirm the Basic Auth header is correctly Base64-encoded. |
401 Unauthorized | Expired access token | Tokens expire after 5 minutes. Request a new token from the token endpoint and retry. |
403 Forbidden | Certificate mismatch or not trusted | Confirm you are presenting the certificate issued by Flinks (not the CSR). Ensure your private key matches the certificate. |
| SSL Handshake Error | Certificate not being sent | Check that your code is explicitly loading and attaching the client certificate to the HTTP client handler. |
Support
If you encounter issues not covered in this guide, contact your Flinks contact with the following information:
- The full error message and HTTP status code received
- The timestamp and endpoint you were calling
- Confirmation of whether the issue occurs on the token request, the API call, or both
- Your environment (language, library version)
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article