The CLV1 protocol is now publicRead the spec
Browse the library

Documentation libraryGet started by language

Get started with the C library

The C library is ClavisAUH's C++ SDK behind 19 C functions, for every language that can call a C function: C, Delphi and Free Pascal, Lua, Python through ctypes, C# through P/Invoke, VBA, AutoIt, AutoHotkey, PureBasic… This guide goes from downloading the library to the first activation, with sample code in C, Python, Lua and Delphi.

5 min read

What you need

  • Windows x64 or Linux x64. The library comes in 64-bit only for now: 32-bit programs (VB6, 32-bit AutoIt and AutoHotkey, 32-bit Office, Delphi Win32 builds) cannot load it yet. There is no macOS or ARM build yet.
  • An app on the dashboard. Its Integration › Connect software page has the API address, the App ID, the public key and a Make a test key button. The API address ends in /api, with no /v1: https://<your ClavisAUH site>/api.

1. Download the library

Download from Downloads and check the SHA-256:

  • Windows: clavis-c-1.0.0-windows-x64.zip, with clavis.dll (one file that needs only Windows's own DLLs), clavis.lib to link with MSVC, and an include folder with clavis.h and clavis_errors.h.
  • Linux: clavis-c-1.0.0-linux-x64.tar.gz, with libclavis.so.1 (and libclavis.so pointing to it) and the include folder. The machine needs glibc 2.32 or later and the system's libsodium and libcurl (Debian, Ubuntu: apt install libsodium23 libcurl4).

On Windows, put clavis.dll next to your executable. On Linux, load the library by its path, or put its folder on LD_LIBRARY_PATH.

2. The rules

These five hold whatever language calls the library:

  • Strings in and out are UTF-8, ending with a zero byte. Languages that use UTF-16 (C#, Delphi, AutoHotkey) convert before calling.
  • Strings the library returns belong to the client and stay valid only until the next call on that client: copy them if you keep them.
  • Every function that returns an int returns 0 (CLAVIS_OK) on success, or a fixed error number from the same error codes table as every SDK (the C column).
  • Use one client on one thread only. Each call waits for the network, so keep calls off the interface thread.
  • On Windows every function uses the cdecl calling convention. The library keeps the device key and the latest offline token in the state folder you pass, so sessions and offline tokens carry over between runs without you handling any file (the seat itself follows the machine's hashed identifiers).

3. K1 in C

Open a client for this machine. The API address, App ID and public key are the three values on the Connect software page; the last argument is your software's version:

clavis_client* client = clavis_open(env("CLAVIS_BASE_URL", ""), env("CLAVIS_APP_ID", ""),
                                    env("CLAVIS_APP_KEY", ""), env("CLAVIS_STATE_DIR", ".clavis"), "1.0.0");
if (!client) {
  printf("FAIL settings\n");
  return 1;
}

Activate the customer's key. When no answer arrives, clavis_offline tells whether the saved offline token is still good on this machine. The library saves one on every plan, unless Online-Only Mode is on; without a usable token, clavis_offline returns CLAVIS_E_OFFLINE_UNAVAILABLE (offline_unavailable): going online helps, waiting does not.

int status = clavis_init(client);
if (status == CLAVIS_OK) status = clavis_activate(client, env("CLAVIS_LICENSE", ""));
if (status != CLAVIS_OK) {
  /* No answer: the offline token saved last time keeps the software working. */
  if (clavis_retryable(status) && clavis_offline(client) == CLAVIS_OK) {
    printf("Offline: using the license saved on this machine\nOK k1 offline\n");
    clavis_close(client);
    return 0;
  }
  printf("%s\nFAIL %s\n", clavis_error_message(client, lang), clavis_error_code(client));
  clavis_close(client);
  return 1;
}

While your software runs, send a heartbeat regularly to keep the session, every 5 to 30 minutes or so: a session ends after 60 minutes without one.

status = clavis_heartbeat(client);

Compile the whole k1.c sample below against the library you downloaded:

# Linux
gcc k1.c -I clavis-c-1.0.0-linux-x64/include -L clavis-c-1.0.0-linux-x64 -lclavis -o k1
LD_LIBRARY_PATH=clavis-c-1.0.0-linux-x64 ./k1
:: Windows, in the x64 Native Tools Command Prompt for VS 2022
cl /utf-8 k1.c /I clavis-c-1.0.0-windows-x64\include clavis-c-1.0.0-windows-x64\clavis.lib
copy clavis-c-1.0.0-windows-x64\clavis.dll .
k1.exe
/* K1 through the C library: activate, heartbeat, offline when the server is out of reach.
 * Settings come from CLAVIS_BASE_URL, CLAVIS_APP_ID, CLAVIS_APP_KEY, CLAVIS_LICENSE, CLAVIS_STATE_DIR, CLAVIS_LANG.
 * Ends with "OK k1 active" (or "OK k1 offline"), or the sentence for the user and "FAIL <code>". */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "clavis.h"

static const char* env(const char* name, const char* fallback) {
  const char* value = getenv(name);
  return value && *value ? value : fallback;
}

int main(void) {
  const char* lang = env("CLAVIS_LANG", "en");
  if (clavis_abi_version() != CLAVIS_ABI_VERSION) {
    printf("FAIL abi\n");
    return 1;
  }
  clavis_client* client = clavis_open(env("CLAVIS_BASE_URL", ""), env("CLAVIS_APP_ID", ""),
                                      env("CLAVIS_APP_KEY", ""), env("CLAVIS_STATE_DIR", ".clavis"), "1.0.0");
  if (!client) {
    printf("FAIL settings\n");
    return 1;
  }

  int status = clavis_init(client);
  if (status == CLAVIS_OK) status = clavis_activate(client, env("CLAVIS_LICENSE", ""));
  if (status != CLAVIS_OK) {
    /* No answer: the offline token saved last time keeps the software working. */
    if (clavis_retryable(status) && clavis_offline(client) == CLAVIS_OK) {
      printf("Offline: using the license saved on this machine\nOK k1 offline\n");
      clavis_close(client);
      return 0;
    }
    printf("%s\nFAIL %s\n", clavis_error_message(client, lang), clavis_error_code(client));
    clavis_close(client);
    return 1;
  }

  status = clavis_heartbeat(client);
  if (status != CLAVIS_OK) {
    printf("%s\nFAIL %s\n", clavis_error_message(client, lang), clavis_error_code(client));
    clavis_close(client);
    return 1;
  }
  printf("Message of the day: %s\n", clavis_variable(client, "motd"));
  clavis_logout(client);
  clavis_close(client);
  printf("OK k1 active\n");
  return 0;
}

The sample reads its settings from the CLAVIS_BASE_URL, CLAVIS_APP_ID, CLAVIS_APP_KEY and CLAVIS_LICENSE environment variables (CLAVIS_APP_KEY is the public key; the license is a test key from Make a test key):

$env:CLAVIS_BASE_URL = "<API address>"
$env:CLAVIS_APP_ID = "<App ID>"
$env:CLAVIS_APP_KEY = "<public key>"
$env:CLAVIS_LICENSE = "<test key>"
export CLAVIS_BASE_URL="<API address>" CLAVIS_APP_ID="<App ID>" CLAVIS_APP_KEY="<public key>" CLAVIS_LICENSE="<test key>"

The last line reads OK k1 active. Run it again on the same machine and it still succeeds without using another seat: the server knows the machine by its hashed identifiers (Devices, HWID and transfers). The line Message of the day: shows the app's variable named motd, and stays empty until you create one (App variables).

4. From other languages

Each sample below does exactly the K1 above, through that language's way of calling C. The Python and Lua samples run against the real API in CI; the Delphi sample is not compiled in CI (there is no Delphi there).

Python (ctypes, nothing to install, Python 3.8 or later). The whole sample: it declares each function's types, passes strings as UTF-8 bytes (a Python str would be refused) and decodes the bytes it gets back:

"""K1 through the C library with ctypes: Python 3.8 or later, nothing to install, only the library file.

Put clavis.dll (Windows, one file) or libclavis.so (Linux) next to this file, or point CLAVIS_LIBRARY at it. Settings: CLAVIS_BASE_URL, CLAVIS_APP_ID, CLAVIS_APP_KEY, CLAVIS_LICENSE,
CLAVIS_STATE_DIR, CLAVIS_LANG. Strings cross into the library as UTF-8 bytes (setting() below encodes them), and
the strings it returns come back as bytes to decode.
"""

import ctypes
import os
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
NAME = {"win32": "clavis.dll", "darwin": "libclavis.dylib"}.get(sys.platform, "libclavis.so")
PATH = os.environ.get("CLAVIS_LIBRARY") or os.path.join(HERE, NAME)
if sys.platform == "win32":
    os.add_dll_directory(os.path.dirname(os.path.abspath(PATH)))  # where Windows looks for clavis.dll
lib = ctypes.CDLL(PATH)

c_str, c_int, handle = ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p
lib.clavis_open.restype, lib.clavis_open.argtypes = handle, [c_str] * 5
lib.clavis_close.restype, lib.clavis_close.argtypes = None, [handle]
for name in ("clavis_init", "clavis_heartbeat", "clavis_logout", "clavis_offline"):
    getattr(lib, name).restype, getattr(lib, name).argtypes = c_int, [handle]
lib.clavis_activate.restype, lib.clavis_activate.argtypes = c_int, [handle, c_str]
lib.clavis_variable.restype, lib.clavis_variable.argtypes = c_str, [handle, c_str]
lib.clavis_error_code.restype, lib.clavis_error_code.argtypes = c_str, [handle]
lib.clavis_error_message.restype, lib.clavis_error_message.argtypes = c_str, [handle, c_str]
lib.clavis_retryable.restype, lib.clavis_retryable.argtypes = c_int, [c_int]
lib.clavis_abi_version.restype, lib.clavis_abi_version.argtypes = c_int, []
# The declarations above match ABI version 1 (CLAVIS_ABI_VERSION in clavis.h).
if lib.clavis_abi_version() != 1:
    sys.exit("FAIL abi")


def setting(name, fallback=""):
    return os.environ.get(name, fallback).encode("utf-8")


# Messages in Vietnamese need a UTF-8 console (the default on Linux and in Windows Terminal).
sys.stdout.reconfigure(encoding="utf-8")
LANG = setting("CLAVIS_LANG", "en")
client = lib.clavis_open(
    setting("CLAVIS_BASE_URL"),
    setting("CLAVIS_APP_ID"),
    setting("CLAVIS_APP_KEY"),
    setting("CLAVIS_STATE_DIR", ".clavis"),
    b"1.0.0",
)
if not client:
    sys.exit("FAIL settings")


def fail():
    """Shows the last error in the customer's language, closes the client and stops."""
    print(lib.clavis_error_message(client, LANG).decode("utf-8"))
    print("FAIL", lib.clavis_error_code(client).decode())
    lib.clavis_close(client)
    sys.exit(1)


status = lib.clavis_init(client) or lib.clavis_activate(client, setting("CLAVIS_LICENSE"))
if status != 0:
    if lib.clavis_retryable(status) and lib.clavis_offline(client) == 0:
        print("Offline: using the license saved on this machine")
        print("OK k1 offline")
        lib.clavis_close(client)
        sys.exit(0)
    fail()

if lib.clavis_heartbeat(client) != 0:
    fail()
print("Message of the day:", lib.clavis_variable(client, b"motd").decode("utf-8"))
lib.clavis_logout(client)
lib.clavis_close(client)
print("OK k1 active")

Run it with python k1_ctypes.py, with clavis.dll or libclavis.so next to the file, or its path in CLAVIS_LIBRARY.

Lua (LuaJIT FFI). Declare:

ffi.cdef([[
typedef struct clavis_client clavis_client;
int clavis_abi_version(void);
clavis_client* clavis_open(const char* base_url, const char* app_id, const char* app_public_key,
                           const char* state_dir, const char* app_version);
void clavis_close(clavis_client* client);
int clavis_init(clavis_client* client);
int clavis_activate(clavis_client* client, const char* key);
int clavis_heartbeat(clavis_client* client);
int clavis_logout(clavis_client* client);
int clavis_offline(clavis_client* client);
const char* clavis_variable(clavis_client* client, const char* name);
const char* clavis_error_code(clavis_client* client);
const char* clavis_error_message(clavis_client* client, const char* lang);
int clavis_retryable(int error);
]])
local clavis = ffi.load(os.getenv("CLAVIS_LIBRARY") or "clavis")

Call:

local status = clavis.clavis_init(client)
if status == 0 then status = clavis.clavis_activate(client, setting("CLAVIS_LICENSE")) end
if status ~= 0 then
  if clavis.clavis_retryable(status) == 1 and clavis.clavis_offline(client) == 0 then
    print("Offline: using the license saved on this machine")
    print("OK k1 offline")
    os.exit(0)
  end
  print(ffi.string(clavis.clavis_error_message(client, setting("CLAVIS_LANG", "en"))))
  print("FAIL " .. ffi.string(clavis.clavis_error_code(client)))
  os.exit(1)
end

Run it with CLAVIS_LIBRARY=./libclavis.so luajit k1.lua.

Delphi and Free Pascal (Win64 builds). Declare:

type
  TClavisClient = Pointer;

function clavis_open(BaseUrl, AppId, AppPublicKey, StateDir, AppVersion: PAnsiChar): TClavisClient; cdecl;
  external ClavisLib;
procedure clavis_close(Client: TClavisClient); cdecl; external ClavisLib;
function clavis_init(Client: TClavisClient): Integer; cdecl; external ClavisLib;
function clavis_activate(Client: TClavisClient; Key: PAnsiChar): Integer; cdecl; external ClavisLib;
function clavis_heartbeat(Client: TClavisClient): Integer; cdecl; external ClavisLib;
function clavis_logout(Client: TClavisClient): Integer; cdecl; external ClavisLib;
function clavis_offline(Client: TClavisClient): Integer; cdecl; external ClavisLib;
function clavis_variable(Client: TClavisClient; Name: PAnsiChar): PAnsiChar; cdecl; external ClavisLib;
function clavis_error_code(Client: TClavisClient): PAnsiChar; cdecl; external ClavisLib;
function clavis_error_message(Client: TClavisClient; Lang: PAnsiChar): PAnsiChar; cdecl; external ClavisLib;
function clavis_retryable(Error: Integer): Integer; cdecl; external ClavisLib;

Call:

Status := clavis_init(Client);
if Status = 0 then
  Status := clavis_activate(Client, PAnsiChar(Setting('CLAVIS_LICENSE', '')));
if Status <> 0 then
begin
  if (clavis_retryable(Status) = 1) and (clavis_offline(Client) = 0) then
  begin
    WriteLn('Offline: using the license saved on this machine');
    WriteLn('OK k1 offline');
    Halt(0);
  end;
  WriteLn(UTF8ToString(clavis_error_message(Client, PAnsiChar(Setting('CLAVIS_LANG', 'en')))));
  WriteLn('FAIL ', string(clavis_error_code(Client)));
  Halt(1);
end;

C# and .NET Framework (P/Invoke). See Get started with Unity: the same code runs outside Unity.

VBA (64-bit Office), AutoIt, AutoHotkey, PureBasic. There is no tested sample for these yet. Call the functions named in include/clavis.h through the language's DLL calls (Declare PtrSafe, DllCall…), following the rules in section 2: a 64-bit program, UTF-8 strings, returned strings copied before the next call.

5. See it on the dashboard

  • Integration › Connect software: step 4 turns to Connected, with c/1.0.0 and the machine's operating system.
  • Licensing & releases › Licenses: the key goes from Unused to Active.
  • End users › Devices & sessions: the machine that ran it, and the session it opened.

6. Handle errors

After a call returns something other than 0:

  • clavis_error_code(client): the code as text, such as invalid_credentials or device_limit.
  • clavis_error_message(client, "en"): a sentence for the person using your software, in English or "vi".
  • clavis_retryable(number): not 0 when no answer arrived; trying again later may work.

The error numbers come from clavis_errors.h and never change. When you build against one clavis.h and run with another library build, compare clavis_abi_version() with CLAVIS_ABI_VERSION as the C sample does: the library only adds functions, it never changes or removes one.

7. Package and release

  • Windows: ship clavis.dll next to your executable, with nothing else to install. Linux: ship libclavis.so.1 and say that customers' machines need libsodium and libcurl.
  • Put the state folder (the fourth argument of clavis_open) where the user can write, one per user: %APPDATA%\YourApp on Windows, ~/.config/your-app on Linux. Never in the install folder.
  • Pass the version you release to clavis_open: the server uses it to block builds that are too old and to announce new ones, see Signed releases and updates.
  • Before you sell, turn on Publicly released in Integration › App settings. Every TEST key stops working, so a test key that got out cannot be used.

Questions

My language cannot call a C function. Use the command line tool: anything that can run a command and read JSON can use it.

Which functions are there? Nineteen, each with a line in include/clavis.h: open and close a client, activate, trial, register, sign in, redeem a key, heartbeat, sign out, entitlements (clavis_has), variables (clavis_variable), update checks, offline, and reading errors.

What if a customer reinstalls the system or changes machines? After a reinstall on the same machine, the server still recognizes it from its hashed hardware identifiers. A new machine takes a new seat; when none is left the customer gets device_limit, and you Reset devices on the Licenses page. See Devices, HWID and transfers.

How can we help?

Frequently asked

What is ClavisAUH?

A licensing and authentication service for desktop software: license keys, device binding, end-user sign-in and signed responses. All run from one dashboard

Why can't a cracker fake your server?

The SDK only trusts answers signed with your app's private key. That key never leaves our servers, so a fake server can't sign and gets rejected

What ends up in my binary?

Your app id and public key. Nothing secret, because each device makes its own key pair on first run

Does it work offline?

Yes. Every plan gets a signed offline token for 7 days, and your app checks it locally. Premium lets you choose how long. A revoked license can keep working offline for that time, unless Online-Only Mode is on

Which languages are supported?

JavaScript and TypeScript today. C++, C# and Python are next, on the same open protocol

Is Basic really free?

Yes. No time limit, no card. Move to Premium when you need more

Get in touch