Flinks Upload API — Integration Guide (Auth & mTLS)

Modified on Fri, 5 Jun at 4:17 PM

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:

LayerDescription
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 TokenA short-lived access token obtained by calling the token endpoint, passed in the Authorization header of every API request.
⚠ Both layers are required simultaneously. A valid certificate without a token, or a valid token without the certificate, will result in a rejected 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 (.pem or .crt) — issued by Flinks' Certificate Authority, used for mTLS
? Security note: Never commit your private key, Client Secret, or certificate to source control. Store them in a secrets manager or environment variables.

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:

StepActionDetails
1Obtain an access tokenCall the token endpoint using your Client ID and Client Secret (via Basic Auth) over mTLS. You receive a short-lived Bearer token.
2Call the Upload APIMake 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-urlencoded

Body

grant_type=client_credentials
scope=ocr.write

Successful Response

{
  "access_token": "eyJhbGci...",
  "token_type": "Bearer",
  "expires_in": 300
}
⏱ Access tokens are valid for 5 minutes (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 requests

Step 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 requests

Step 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);
? C# note: If your certificate and private key are in separate PEM files, combine them into a PFX using OpenSSL:
openssl pkcs12 -export -in certificate.pem -inkey private-key.pem -out certificate-and-key.pfx

Upload 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.com

Full Flow Summary

#ActionEndpointKey Output
1Upload first filePOST /uploadAggregationId
2Upload additional files (optional — repeat per file)POST /upload with AggregationIdSame AggregationId confirmed
3Complete the upload and trigger OCRPOST /upload/completeLogin.Id for result retrieval
4Retrieve 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"
}
FieldRequiredDescription
Files[].FileNameRequiredThe original filename including extension (e.g. statement.pdf).
Files[].ContentRequiredThe base64-encoded content of the file.
AggregationIdOmit on first call; Required on subsequent callsOmit 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.
TagOptionalAn optional label to associate with this upload.
ClientNameOptionalAn 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)."
}
? Save the AggregationId from this response. You will need it for every subsequent file upload and for the complete call.
⚠ Important: If no 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."
}
FieldDescription
AggregationIdConfirms the upload session that was completed.
Login.IdThe LoginId generated for this upload session. Use this to retrieve fraud analysis results once processing is complete.

File and Size Limits

LimitValue
Accepted file typesPDF, PNG, JPG, JPEG, TIF, TIFF
Max size per file18 MB
Max total per upload session180 MB
Files per requestOne 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.

The Bearer token for this endpoint is your API secret key, provided to you by your Flinks contact — not the OAuth token obtained in Step 1.

Endpoint: GET https://{instance}-api.private.fin.ag/v3/{customerId}/upload/fraudanalysis/{loginId}

ParameterInDescription
customerIdPathYour Flinks Customer ID.
loginIdPathThe Login.Id returned by the POST /upload/complete response.
AuthorizationHeaderBearer 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"
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
FieldDescription
DocumentAnalysis[].StatusProcessing status of the document. Expect COMPLETED when results are ready.
DocumentAnalysis[].FullAnalysis[].DocumentTypeType of document analysed (e.g. BANK_STATEMENT).
FraudSignalsArray of detected fraud signals. An empty array means no fraud was detected.
FraudSignals[].TypeThe type of fraud signal triggered.
FraudSignals[].PageNumberPage of the document where the signal was detected.
FraudSignals[].CountNumber of occurrences of this signal.
FraudSignals[].UnderlyingDataAdditional supporting data for the signal.
? The fraud analysis endpoint should only be called once processing is complete. If called too early, the document status may not yet be 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 StatusLikely CauseResolution
400 Bad RequestMalformed token request bodyEnsure Content-Type is application/x-www-form-urlencoded and grant_type=client_credentials is included.
401 UnauthorizedInvalid or missing credentialsVerify your Client ID and Client Secret. Confirm the Basic Auth header is correctly Base64-encoded.
401 UnauthorizedExpired access tokenTokens expire after 5 minutes. Request a new token from the token endpoint and retry.
403 ForbiddenCertificate mismatch or not trustedConfirm you are presenting the certificate issued by Flinks (not the CSR). Ensure your private key matches the certificate.
SSL Handshake ErrorCertificate not being sentCheck 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)
? Do not share your private key or Client Secret when reaching out for support. Flinks will never ask for these.

Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article