Query data with SAPI¶
This page explains how a SAPI client calls KX Sensors APIs, reads the response, and controls routing, timeouts, and retries.
A client calls any public API — one with isPublic: true in api.yaml — through a single method: execute. In v2 this was split between callAPI and callFunction; the two are combined in v3.
Call an API¶
execute takes the API name, a dictionary of parameters, an optional header, an optional retry policy, and a response object. It returns a boolean for success and populates the response object.
| Parameter | Purpose |
|---|---|
apiName |
Name of the API to call, for example .kxs.getUsers. |
request |
Dictionary of API parameters, keyed by parameter name. |
header |
Dictionary of values that augment the SAPI request header. Use it to influence routing, priority, and timeouts. Optional. |
retryPolicy |
Retry policy for this call. Overrides the connection's default policy. Optional. |
response |
Object that receives the result, the response code, and timing information. |
deserializeResponse |
Whether SAPI deserializes the response payload. Available in C and C# only. Defaults to true. See Read the response. |
The following examples call an API and read a dictionary result. They call a placeholder API, .myapp.getCounts, which takes an equipmentID parameter. Replace the API name and the parameters with an API that your deployment defines.
void queryDict(KxsHandle handle)
{
K keys = ktn(KS, 0);
K vals = ktn(0, 0);
keys = js(&keys, ss("equipmentID"));
vals = jk(&vals, kj(101));
K request = xD(keys, vals);
KxsResponse response;
bool ok = sapiExecute(handle, ".myapp.getCounts", request, NULL, true, NULL, &response);
if (!ok || response.rc != 0)
{
printf("Call failed, rc=%d ac=%d\n", response.rc, response.ac);
return;
}
K names = kK(response.payload)[0];
K values = kK(response.payload)[1];
for (int i = 0; i < names->n; i++)
printf("name=%s value=%lld\n", kS(names)[i], kJ(values)[i]);
}
public static void QueryDict(Kxs kxs)
{
string[] keys = { "equipmentID" };
object[] values = { 101L };
c.Dict request = new(keys, values);
KxsResponse response = new();
bool ok = kxs.Execute(".myapp.getCounts", request, null, true, null, response);
if (!ok || response.RC != (short)RC.Ok)
{
Console.WriteLine($"Call failed, rc={response.RC} ac={response.AC}");
return;
}
c.Dict payload = (c.Dict)response.Payload;
string[] names = payload.x as string[] ?? Array.Empty<string>();
long[]? vals = payload.y as long[];
for (int i = 0; i < names.Length; i++)
Console.WriteLine($"name={names[i]} value={vals?[i]}");
}
public static void queryDict(Kxs kxs) {
String[] requestKeys = new String[] { "equipmentID" };
Object[] requestValues = new Object[] { 101L };
c.Dict request = new c.Dict(requestKeys, requestValues);
KxsResponse response = new KxsResponse();
boolean ok = kxs.execute(".myapp.getCounts", request, null, null, response);
if (!ok || response.getRC() != RC.OK.value) {
LOG.error("Call failed, rc={} ac={}", response.getRC(), response.getAC());
return;
}
c.Dict payload = (c.Dict) response.getPayload();
String[] names = (String[]) payload.x;
long[] values = (long[]) payload.y;
for (int i = 0; i < names.length; i++)
LOG.debug("name={} value={}", names[i], values[i]);
}
import pykx
def query_dict(sapi):
request = pykx.Dictionary({
pykx.SymbolAtom("equipmentID"): pykx.LongAtom(101)
})
response = KxsResponse()
ok = sapi.execute(req_corr=0, api_name=".myapp.getCounts",
request=request, header=None, response=response)
if not ok or response.rc != 0:
print(f"Call failed, rc={response.rc} ac={response.ac} ai={response.ai}")
return
for key, value in response.payload.items():
print(f"{key} = {value.py()}")
Read the response¶
execute populates the response object rather than returning the result:
| Field | Contents |
|---|---|
payload |
The API's result. |
header |
The SAPI response header. |
rc |
Response code. 0 indicates success. |
ac |
Application code. |
ai |
Application information. |
reqCorr |
Request correlator. The same value appears in server-side logs, so use it to trace a request end to end. |
queueTm |
Time the request spent queued, in milliseconds. |
svcTm |
Service time, in milliseconds. |
The payload's type follows the API's result type. A dictionary result arrives as c.Dict in C# and Java, and as a pykx.Dictionary in Python; a table result arrives as c.Flip or pykx.Table. Check the type before casting.
Java and Python always return deserialized objects. In C and C#, set deserializeResponse to false to receive the raw payload as bytes instead, and deserialize it yourself.
if (response.Payload is c.Flip table)
{
for (int i = 0; i < table.x.Length; i++)
{
long[] column = (long[])table.y[i];
Console.WriteLine($"{table.x[i]}: {column.Length} rows");
}
}
if (response.getPayload() instanceof c.Flip) {
c.Flip table = (c.Flip) response.getPayload();
for (int i = 0; i < table.x.length; i++) {
long[] column = (long[]) table.y[i];
LOG.debug("{}: {} rows", table.x[i], column.length);
}
}
if isinstance(response.payload, pykx.Table):
for column in response.payload.keys():
print(f"{column.py()} = {response.payload[column].py()}")
To trace what the server did with a request, search the server logs for the reqCorr value. See Understand log entries and correlators.
Influence routing with the request header¶
The header parameter takes a dictionary of properties that the Gateway uses to route the request. Setting them well matters for query performance: a request that specifies no time range is routed to the RDB, IDB, and HDB alike, even when the data it wants is only in the RDB.
The properties available to a client include feeds, nodes, tsRange, routePrefs, scs, dur, priority, isMut, isSolo, testCtl, and isSequenced. For what each one does, the enumerations behind them, and how the Gateway combines them with the API definition, see Set up query processing.
The client also has a role in choosing which Gateway receives the request — see Choose a Gateway.
Set timeouts¶
There are two separate timeouts, in different units.
Client-side. The timeout field of the retry policy limits how long SAPI waits for a single attempt to complete, in milliseconds. When it expires, SAPI abandons the attempt and returns a response with an rc of TIMEOUT.
Server-side. Add a timeout field to the request header to limit how long the server spends on a single attempt, in seconds.
| Field | Type | Applies to | Unit |
|---|---|---|---|
retryPolicy.timeout |
int | A single client attempt | Milliseconds |
header.timeout |
int | A single server operation | Seconds |
If you set neither, the server falls back to the timeout for the API's configured duration category (sapiDurShortTO, sapiDurMediumTO, sapiDurLongTO, or sapiDurEpicTO), or to sapiTO. See Set up query processing.
Retry failed calls¶
A retry policy controls how often SAPI reattempts an operation and how long it waits between attempts. Pass one per call, or set a default for the connection in the configuration object.
| Field | Purpose |
|---|---|
maxAttempts |
Maximum number of attempts before the operation fails |
timeout |
Timeout for a single attempt, in milliseconds |
retryInterval |
Interval between attempts, in milliseconds |
retryScaling |
Multiplier applied to the interval after each attempt. Must be 1 or greater. Defaults to 1, which keeps the interval constant. |
The interval grows by retryScaling after each failed attempt. With maxAttempts of 5, retryInterval of 5000, and retryScaling of 1.2:
| Attempt | Wait before attempt |
|---|---|
| 1 | None |
| 2 | 5,000 ms |
| 3 | 6,000 ms (5,000 × 1.2) |
| 4 | 7,200 ms (6,000 × 1.2) |
| 5 | 8,640 ms (7,200 × 1.2) |
After the fifth attempt, the operation fails.
SAPI retries a call immediately, without applying retryInterval, when the attempt times out client-side (rc of TIMEOUT) or the server returns NO_DEST.
If you pass no retry policy, execute uses the retry policy from the connection configuration. A timeout of 0 means wait indefinitely.
Wait for the response¶
SAPI exposes one query call, execute, and it always waits for the response. The call doesn't return until a response arrives or the retry policy is exhausted. The asynchronous transport underneath isn't visible to the application.
Internally, execute sends the request to a Gateway, then waits for a response for the retry policy's timeout. If none arrives in time, it resends the request, up to maxAttempts, waiting retryInterval between attempts and multiplying the interval by retryScaling each time. After the last attempt it returns a timeout response.