Skip to content

Connect with SAPI

This page explains how a C, C#, Java, or Python client establishes a connection to KX Sensors with SAPI, and how SAPI maintains that connection.

Every SAPI client follows the same sequence: build a configuration object, call connect, use the connection to query or publish, then call disconnect. A single connection supports API calls, publishing, and message queue listeners at the same time.

Before you begin

You need:

  • The SAPI library for your language, and its dependencies. See Get the SAPI library.
  • The host name of at least one KX Sensors node running qetcd, and the node's base port.
  • KX Sensors credentials, if authentication is enabled for the service classes you connect to.
  • Certificates, if the deployment requires TLS.

Get the SAPI library

Download the SAPI library from the KX download portal, or ask KX to send it to you. Each language gets a different package:

Language Package
C A ZIP file containing the header and library. KX ships separate builds for Windows and Linux.
C# A ZIP file containing the Kx.Sapi library.
Java A JAR file, available from Maven.
Python The pysapi package.

The C#, Java, and Python packages each combine the Windows and Linux binaries, so you use the same package on either operating system.

Minimum configuration

At minimum, SAPI needs to know where Discovery is and which local ports it may use:

Field Purpose
discoverySvcHosts One or more host:port entries for KX Sensors nodes, using the node's base port. SAPI queries Discovery, at base port + 3 (or + 4 for TLS), to find the Gateway, RT, and MQ endpoints it needs.
listenPorts A local port range that SAPI binds to accept inbound connections from Response Processes.
advertiseHosts The address Response Processes use to reach the client. If you omit it, SAPI resolves a local address from the network adapter configuration.
instanceName A name for this client. It appears in the SAPI header of every request and in server-side logs. If you omit it, SAPI generates one from the computer name, process ID, and a counter.
username and password Credentials, if authentication is enabled.

Set discoverySvcHosts to the node's base port, not the port qetcd actually listens on. SAPI adds 3 to reach qetcd on the plain port, or 4 when TLS is enabled — with the default base port of 20000, SAPI connects to 20003 or 20004. See Ports and connection direction.

Connect to the system

The following examples connect to a single-node deployment and then disconnect. Error handling is shown because SAPI does not throw exceptions — see Handle errors.

#define KXVER 3

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

#include "sapi.h"

int main()
{
    SAPIConfig *config = calloc(1, sizeof(SAPIConfig));

    config->instanceName = "your-instance-name";
    config->listenPorts = "10000-10200";

    char *discoverySvcHosts[] = { "your-discovery-node.com:20000" };
    config->discoverySvcHosts = discoverySvcHosts;
    config->discoverySvcHostsCount = 1;

    config->advertiseHosts = malloc(sizeof(char *));
    *config->advertiseHosts = "10.10.0.3";
    config->advertiseHostsCount = 1;

    SAPIRetryPolicy *rp = calloc(1, sizeof(SAPIRetryPolicy));
    rp->maxAttempts = 10;
    rp->retryInterval = 1000;
    rp->retryScaling = 1.5f;
    rp->timeout = 20000;
    config->retryPolicy = rp;

    KxsResponse response;
    KxsHandle handle = sapiConnect(config, &response);

    if (response.rc != 0)
    {
        printf("Connect failed, rc=%d ac=%d\n", response.rc, response.ac);
        return 1;
    }

    // Use the connection here.

    sapiDisconnect(handle);
    return 0;
}
using Kx.Sapi;
using Kx.Sapi.Constants;

internal class Program
{
    private static void Main()
    {
        Kxs kxs = new();

        SapiConfig sapiConfig = new()
        {
            InstanceName = "your-instance-name",
            DiscoveryHosts = ["your-discovery-node.com:20000"],
            ListenPorts = "10000-10200",
            RetryPolicy = new RetryPolicy(10, 20000, 1000, 1.5f)
        };

        KxsResponse response = new();

        if (!kxs.Connect(sapiConfig, response))
        {
            Console.WriteLine($"Connect failed, rc={response.RC} ac={response.AC}");
            return;
        }

        // Use the connection here.

        kxs.Disconnect();
    }
}
package com.kx.sapi.examples;

import com.kx.sapi.Kxs;
import com.kx.sapi.KxsResponse;
import com.kx.sapi.RetryPolicy;
import com.kx.sapi.SapiConfig;

public class Connect {
    public static void main(String[] args) {
        SapiConfig options = new SapiConfig();

        options.setInstanceName("your-instance-name");
        options.setListenPorts("10000-10010");
        options.setAdvertiseHosts("10.10.0.3");
        options.setDiscoveryHosts(new String[] { "your-discovery-node.com:20000" });
        options.setRetryPolicy(new RetryPolicy(3, 20000, 1000, 1.5f));

        Kxs kxs = new Kxs();
        KxsResponse response = new KxsResponse();

        if (!kxs.connect(options, response)) {
            System.out.printf("Connect failed, rc=%d ac=%d%n",
                response.getRC(), response.getAC());
            return;
        }

        // Use the connection here.

        kxs.disconnect();
    }
}
from pysapi import SAPI, SAPIConfig, KxsResponse, SAPIRetryPolicy

cfg = SAPIConfig(
    instanceName="pysapi",
    discoverySvcHosts=["your-discovery-node.com:20000"],
    discoverySvcHostsCount=1,
    advertiseHosts=["10.10.0.2"],
    advertiseHostsCount=1,
    listenPorts="10000-10200",
    retryPolicy=SAPIRetryPolicy(maxAttempts=10, timeout=20_000,
                                retryInterval=1_000, retryScaling=1.5),
)

sapi = SAPI()
response = KxsResponse()

if not sapi.connect(cfg, response):
    print(f"Connect failed, rc={response.rc} ac={response.ac}")
else:
    # Use the connection here.
    sapi.disconnect()

PySAPI requires Python 3.10 or later. It doesn't require a license to run.

What happens during connect

connect is blocking: it returns only when the connection attempt has finished. It performs the following steps.

  1. Validates the configuration you supplied, including the TLS configuration.
  2. Generates an instance name, if you didn't set instanceName.
  3. Resolves a local address from the network adapter configuration, if you didn't set advertiseHosts.
  4. Binds a port from listenPorts to listen for inbound TCP connections from Response Processes.
  5. Binds a UDP port to listen for inbound messages carrying acknowledgements for synchronous publishes. SAPI always binds this port, even if the client never publishes.
  6. Connects to the discoverySvcHosts entries to discover the endpoints of other processes.
  7. Connects to every Gateway endpoint it discovered.

Response Process connections are not established during connect. A Response Process opens a connection to the client only when it first needs to return an API result.

Two other calls establish connections later in the client's life:

  • registerPublisher connects to the RT endpoints discovered for the stream that serves the given feed.
  • registerQueueListener connects to the MQ endpoints discovered for the given queue.

Authenticate

Set username and password in the configuration object. SAPI sends them as part of the qIPC handshake, and qetcd validates them against the credentials database.

config->username = "your-username";
config->password = "your-password";
sapiConfig.Username = "your-username";
sapiConfig.Password = "your-password";
options.setUsername("your-username");
options.setPassword("your-password");
cfg.username = "your-username"
cfg.password = "your-password"

Whether credentials are required depends on the auth setting of each service class in manifest.yaml. If authentication isn't enabled for a service class, SAPI's credentials are ignored for connections to it. Use TLS whenever authentication is enabled, so that credentials aren't sent in plain text.

For roles, adding users, and the rest of the authentication model, see User authentication.

username and userID are different fields

username is the name SAPI authenticates with. userID is a separate numeric field, which server processes use. Setting userID doesn't authenticate the client, so always set username and password for authentication.

Encrypt the connection with TLS

Set the TLS flag and supply certificates in the configuration object. When you enable TLS, it applies to all of the client's connections:

  • Outbound Gateway connections
  • Inbound Response Process connections
  • Message queue connections
  • Discovery connections to qetcd

You can supply the CA certificate, client certificate, and key either as strings or as file paths. You can't mix the two forms: use strings for all three, or file paths for all three. The file path fields take precedence over the string fields.

SAPITLS *tls = calloc(1, sizeof(SAPITLS));

tls->CAFile = "certs/ca-cert.pem";
tls->certFile = "certs/client-cert.pem";
tls->keyFile = "certs/client-private-key.pem";
tls->verifyClient = true;
tls->verifyServer = true;

config->tls = tls;
config->useTLS = true;
sapiConfig.Tls = new SapiTls
{
    CAFile = "certs/ca-cert.pem",
    CertFile = "certs/client-cert.pem",
    KeyFile = "certs/client-private-key.pem",
    VerifyClient = true,
    VerifyServer = true
};

sapiConfig.UseTLS = true;
SapiTLS tls = new SapiTLS();

tls.setCAFile("certs/ca-cert.pem");
tls.setCertFile("certs/client-cert.pem");
tls.setKeyFile("certs/client-private-key.pem");
tls.setVerifyClient(true);
tls.setVerifyServer(true);

options.setTLS(tls);
options.setUseTLS(true);
from pysapi import SAPITLS

cfg.tls = SAPITLS(
    CAFile="certs/ca-cert.pem",
    certFile="certs/client-cert.pem",
    keyFile="certs/client-private-key.pem",
    verifyClient=True,
    verifyServer=True,
)
cfg.useTLS = True

The two verification flags are independent:

  • verifyClient — verify the certificate presented on inbound connections, such as those from Response Processes.
  • verifyServer — verify the certificate presented on outbound connections, such as those to Gateways.

Certificates must be in PEM format. The certificate file must start with the client's own certificate, followed by any intermediate CA certificates, ending with the root. For the server side of TLS, see In-transit TLS encryption.

If useTLS doesn't match the service class's server-side tls mode, the connection fails — SAPI doesn't fall back to the other mode on your behalf.

Choose a Gateway

A KX Sensors deployment usually runs several Gateways. SAPI maintains a connection to all of them and chooses which one receives each request using round-robin selection. Round-robin is the default and the only strategy that works, so you don't need to set gatewayStrategy. See Set up query processing.

To set the Gateways yourself, set gatewayHosts with explicit Gateway addresses. Doing so turns Discovery off.

Detect connection failures and reconnect

SAPI doesn't use application-level heartbeats. Instead it sets TCP socket options, including TCP keep-alives, so that a broken connection is detected quickly. One consequence is that high network latency, or a busy client that leaves TCP read and write buffers unprocessed for too long, can cause a working connection to be treated as failed and closed.

Reconnection behavior differs per connection type:

Connection Behavior after a failure
Discovery (qetcd) SAPI keeps one active Discovery connection, selecting endpoints from discoverySvcHosts in round-robin order. After a loss of connection it retries once a second, continuing round-robin through the list, until it reconnects.
Gateways SAPI retries once a second until it reconnects. It doesn't use retryPolicy for these retries.
RT The RT connection reconnects on its own. The only SAPI setting that affects reconnection is connTimeout in SAPIRTConfig.
Response Processes SAPI never reconnects. Response Processes always initiate these connections, so a lost connection is re-established by the Response Process the next time it needs to return a result to the client.

Condition the sockets

In v2, a separate library called netcon set TCP socket options for fast failure detection. In v3 you set them through socketConfig in the SAPI configuration:

Field Purpose
socRoundTripTime Maximum expected round-trip time to the server, in milliseconds
socSendRate Expected send rate per connection, in bytes per second
socReceiveRate Expected receive rate per connection, in bytes per second

SAPI derives the TCP socket options from these three values, so set them to reflect the network between the client and the server rather than to an aspirational target. The server-side counterpart is described in Network conditioning fast failure detection.

What happens when you leave a value at zero

SAPI doesn't validate these values or warn you about them, so a zero silently changes the behavior:

  • Send or receive rate of 0: SAPI skips setting the buffer size for that direction, and the operating system default applies. On Gateway connections, SAPI uses the larger of the two rates for both buffers, so a zero in one field is masked by a non-zero value in the other. On the RT connection, SAPI uses only the receive rate, so a receive rate of 0 means no buffer sizing at all.
  • Round-trip time of 0: SAPI treats it as 1 second. On Linux, this means a connection is dropped after a single missed keep-alive probe, which is about 2 seconds of silence, and a connect attempt gives up after one SYN retry. The TCP user timeout is set to 0, which Linux treats as the system default. SAPI still enables keep-alive, quick-ack, and low-delay.

If you don't set socketConfig at all, SAPI applies its native defaults. In Java, don't pass an empty new SapiSocketConfig(): a constructor with no arguments sets all three values to zero, which turns off buffer sizing and applies the round-trip behavior described above.

Handle errors

SAPI methods don't throw exceptions. Each one returns a boolean for success and populates a response object with the detail:

Field Meaning
rc Response code. 0 indicates success.
ac Application code.
ai Application information — further detail about the outcome.
reqCorr Request correlator, which also appears in server-side logs.
queueTm Time the request spent queued, in milliseconds.
svcTm Service time, in milliseconds.

Check both the boolean and rc, and log rc, ac, and ai together — on its own, the boolean doesn't tell you why a call failed.

rc and ac are plain numeric codes with no fixed, published enumeration — SAPI passes them through rather than interpreting them. Java and C# define named constants (such as RC.OK and RC.Ok) as a convenience mapping over these numbers; in C and Python, work with the numeric values directly.

Disconnect

disconnect closes the connection and stops any active subscriptions and listeners. In C, pass the handle returned by sapiConnect; in the other languages, call it on the client object.

Unregister publishers and queue listeners before disconnecting, so that the server doesn't hold state for a client that has gone away.

Settings removed in v3

The Delta Platform components were removed from the client libraries in v3, along with the settings that controlled failure detection and reconnection. If you are converting a v2 client, the following settings of DeltaApiCore.Dms.Config.DmsConfigurationSettings no longer apply, and their behavior is now covered by retryPolicy and socketConfig:

  • DeltaMessagingServerListenerConnectInterval
  • DeltaMessagingServerListenerReConnectInterval
  • RemoteSubscriberDefaultPublishDataQueueSize
  • DefaultDmsHeartbeatInterval
  • DefaultQrReconnectionInterval
  • DefaultQpReconnectionInterval
  • DefaultQrHeartBeatInterval

Next steps