Skip to content

The durable contract for the Galanthus C ABI: versioning, status and outcome handling, result-envelope ownership, errors, interrupts, stores, secrets, and threading.

Header and Version

#include <galanthus/c_api/gln_capi.h>

The header is C-callable. C++ translation units get extern "C" declarations from the header itself.

GLN_API_VERSION is the compile-time ABI constant in gln_capi.h. The current value is 9u.

GLN_API uint32_t    GLN_CALL gln_get_api_version(void);
GLN_API const char* GLN_CALL gln_get_library_version(void);

Bindings should check the loaded library before using the rest of the ABI:

if (gln_get_api_version() != GLN_API_VERSION) {
    return 1;
}

Compile against the installed header that ships with the shared library being loaded. Do not infer struct layout or function signatures from generated HTML, older docs, or a copied binding file.

Calling Convention and Names

GLN_CALL is the ABI calling convention (__cdecl on Windows, platform default elsewhere). GLN_API supplies import/export decoration. Hand-written FFI prototypes must include both effects.

Current operation entry points are backend-oriented:

GLN_API gln_status_t GLN_CALL gln_open_fints_backend(
    const gln_fints_config_t* in_config,
    gln_state_store_t*        in_state_store,
    gln_continuation_store_t* in_continuation_store_or_null,
    gln_secret_t*             in_pin,
    gln_backend_t**           out_backend,
    gln_error_t*              out_error);

GLN_API gln_status_t GLN_CALL gln_retrieve_accounts(
    gln_backend_t*         in_backend,
    gln_backend_result_t** out_result);

Input parameters use in_; output parameters use out_. Optional parameters carry _or_null in their name.

Status Codes

Every fallible function returns gln_status_t. GLN_OK is zero.

typedef enum {
    GLN_OK = 0,
    GLN_ERR_INVALID_ARG = 1,
    GLN_ERR_OUT_OF_MEMORY = 2,
    GLN_ERR_TRANSPORT = 3,
    GLN_ERR_PROTOCOL = 4,
    GLN_ERR_REJECTED = 5,
    GLN_ERR_TAN_REQUIRED = 6,
    GLN_ERR_DECOUPLED_PENDING = 7,
    GLN_ERR_VOP_CONFIRMATION_REQUIRED = 8,
    GLN_ERR_NOT_FOUND = 9,
    GLN_ERR_NOT_SUPPORTED = 10,
    GLN_ERR_INTERNAL = 11,
    GLN_ERR_FOLLOW_UP_REQUIRED = 12,
    GLN_ERR_PLUGIN_NOT_FOUND = 13,
    GLN_ERR_PLUGIN_NOT_TRUSTED = 14,
    GLN_ERR_TOKEN_PERSIST_FAILED = 15,
    GLN_ERR_RESUME_ARTIFACT_INVALID = 16
} gln_status_t;
CodeMeaning
GLN_OKCall or operation succeeded.
GLN_ERR_INVALID_ARGCaller violated the function contract.
GLN_ERR_OUT_OF_MEMORYAllocation failed inside the library.
GLN_ERR_TRANSPORTHTTP, TLS, socket, or network failure.
GLN_ERR_PROTOCOLProvider protocol exchange failed.
GLN_ERR_REJECTEDProvider rejected the requested action.
GLN_ERR_TAN_REQUIREDOperation paused for TAN input.
GLN_ERR_DECOUPLED_PENDINGOperation paused for out-of-band approval polling.
GLN_ERR_VOP_CONFIRMATION_REQUIREDOperation paused for Verification of Payee confirmation.
GLN_ERR_NOT_FOUNDRequested persisted artifact or key material is absent.
GLN_ERR_NOT_SUPPORTEDOperation or helper is unavailable for this backend, build, or platform.
GLN_ERR_INTERNALLibrary invariant failed. Treat this as a product bug.
GLN_ERR_FOLLOW_UP_REQUIREDProvider returned a manual follow-up state.
GLN_ERR_PLUGIN_NOT_FOUNDA plugin-backed backend could not resolve its plugin.
GLN_ERR_PLUGIN_NOT_TRUSTEDA plugin-backed backend found an untrusted plugin.
GLN_ERR_TOKEN_PERSIST_FAILEDOAuth or token material could not be persisted.
GLN_ERR_RESUME_ARTIFACT_INVALIDA persisted resume artifact is malformed, stale, or incompatible.

Backend Result Envelopes

Backend operations return two layers of status:

  1. The C function return value says whether an envelope was produced.
  2. The gln_backend_result_t envelope says what happened in the provider operation.

For backend operations, a GLN_OK function return means *out_result is non-null and must be destroyed with gln_destroy_backend_result. Inspect the envelope even when the operation itself failed or paused:

gln_backend_result_t* result = NULL;
gln_status_t call_status = gln_retrieve_accounts(backend, &result);
if (call_status != GLN_OK) {
    /* No envelope exists. This is argument validation or allocation failure. */
    return 1;
}

gln_status_t operation_status = gln_get_backend_result_status(result);
gln_backend_outcome_t outcome = gln_get_backend_result_outcome(result);

if (outcome == GLN_BACKEND_OUTCOME_SUCCESS && operation_status == GLN_OK) {
    const gln_accounts_t* accounts = gln_get_backend_result_accounts(result);
    for (size_t i = 0; i < gln_get_accounts_count(accounts); ++i) {
        const gln_account_t* account = gln_get_account_at(accounts, i);
        printf("%s\n", gln_get_account_iban(account));
    }
}
else if (outcome == GLN_BACKEND_OUTCOME_ACTION_REQUIRED) {
    const gln_interrupt_info_t* interrupt = gln_get_backend_result_interrupt_info(result);
    gln_continuation_t* continuation = gln_take_backend_result_continuation(result);
    /* Persist or resume continuation before destroying it. */
    gln_destroy_continuation(continuation);
    (void)interrupt;
}
else {
    const gln_error_t* error = gln_get_backend_result_error(result);
    fprintf(stderr, "%s: %s\n",
            error && error->issue_type ? error->issue_type : "unknown",
            error && error->message ? error->message : "");
}

gln_destroy_backend_result(result);

gln_get_backend_result_outcome classifies the operation:

OutcomeMeaning
GLN_BACKEND_OUTCOME_SUCCESSThe requested operation completed; use gln_get_backend_result_kind and the matching gln_get_backend_result_* accessor.
GLN_BACKEND_OUTCOME_ACTION_REQUIREDThe operation paused; inspect gln_get_backend_result_interrupt_info and take the continuation if follow-up is possible.
GLN_BACKEND_OUTCOME_REJECTEDThe provider rejected the operation; inspect gln_get_backend_result_error.
GLN_BACKEND_OUTCOME_ERRORValidation, transport, protocol, plugin, token, or internal failure; inspect gln_get_backend_result_error.
GLN_BACKEND_OUTCOME_UNKNOWNReturned for a null result pointer or an uninitialized envelope.

The envelope owns all borrowed views derived from it:

  • gln_get_backend_result_error
  • gln_get_backend_result_interrupt_info
  • gln_get_backend_result_opaque_json
  • typed list and single-row handles returned by gln_get_backend_result_*
  • row pointers returned by gln_get_account_at, gln_get_balance_at, gln_get_transaction_at, and the other typed collection accessors
  • every const char* returned by typed accessors

Those values are valid only until gln_destroy_backend_result. Do not release them separately. Copy data into host-language objects before destroying the envelope.

gln_take_backend_result_continuation is different: it transfers ownership of the continuation out of the envelope. After taking it, destroy it with gln_destroy_continuation or persist it with gln_save_continuation and then destroy the in-memory handle.

Errors

Open functions, store constructors, continuation helpers, and similar non-envelope functions use gln_error_t* output slots.

typedef struct {
    uint32_t            struct_size;
    const char*         issue_type;
    const char*         message;
    const char*         details;
    gln_error_detail_t* detail;
} gln_error_t;

GLN_API gln_status_t GLN_CALL gln_default_error(gln_error_t* out_value);

Initialize each slot with gln_default_error before passing it to the library. After a non-GLN_OK return, read the fields you need and call gln_release_error. The string fields and detail are owned by the slot and become invalid when it is released or reused.

For backend result envelopes, gln_get_backend_result_error returns a borrowed const gln_error_t*. Do not pass that borrowed pointer to gln_release_error; the envelope releases it.

Structured detail is optional. When present, inspect it before releasing the owning error slot or destroying the owning envelope:

const gln_error_detail_t* detail = gln_get_error_detail(error);
if (detail != NULL && gln_get_error_detail_kind(detail) == GLN_ERROR_DETAIL_EBICS_STATUS) {
    const char* code = gln_get_error_detail_ebics_technical_code(detail);
    (void)code;
}

Interrupts

Interrupt metadata is exposed through gln_interrupt_info_t:

typedef enum {
    GLN_INTERRUPT_TAN_REQUIRED = 0,
    GLN_INTERRUPT_DECOUPLED_PENDING = 1,
    GLN_INTERRUPT_VOP_CONFIRMATION_REQUIRED = 2
} gln_interrupt_kind_t;

For backend result envelopes, use gln_get_backend_result_interrupt_info only when the outcome is GLN_BACKEND_OUTCOME_ACTION_REQUIRED. The returned pointer is borrowed from the envelope. Read the arm selected by kind, copy any fields needed by the host language, then destroy the envelope after taking any continuation.

Interrupt info is borrowed from gln_backend_result_t. Copy any fields needed after the envelope is destroyed.

Ownership Summary

Returned thingRelease with
Backend handlegln_close_backend
Backend result envelopegln_destroy_backend_result
Continuation taken from an envelope or loaded from a storegln_destroy_continuation
Populated direct gln_error_t slotgln_release_error
Library-owned char* outputgln_release_string
Library-owned binary buffergln_release_buffer
Secret handlegln_destroy_secret
State storegln_destroy_state_store
Continuation storegln_destroy_continuation_store
EBICS key storegln_destroy_key_store
Revolut token storegln_destroy_revolut_token_store
Wise token storegln_destroy_wise_token_store

Destroy and release functions accept NULL unless the installed header says otherwise. A handle becomes invalid as soon as its destroy or close function returns.

Lifecycle and Threading

Call gln_init_runtime before opening backends, creating file-backed stores, or running operations. Call gln_shutdown_runtime when the process is done with the library.

gln_error_t error = {0};
gln_default_error(&error);
gln_status_t s = gln_init_runtime(NULL, &error);
if (s != GLN_OK) {
    gln_release_error(&error);
    return 1;
}

/* Work with Galanthus. */

gln_shutdown_runtime();

Distinct handles may be used from distinct threads concurrently. Each handle, secret, continuation, and result envelope is single-threaded; do not share an individual handle between threads.

gln_init_runtime and gln_shutdown_runtime are not safe to interleave with operations. Call gln_init_runtime once before the operating phase begins and gln_shutdown_runtime once after it ends.

See Also