SDKs

Two clients, one API contract. Neither contains any risk logic: they send the request and report what Spark answered, so an SDK can never disagree with the model.

SDK Status Location
Python Available sdk/python
Node and TypeScript Available sdk/node
Go, Ruby, PHP, Java Upcoming not started

Both are tested. The Python tests drive the SDK against the real running API; the Node tests drive the real SDK source with a stubbed transport.

A note on the import name

The Python package imports as spark_sdk, not spark. This repository already ships a spark package containing the model training code, and two packages with the same name would shadow each other.

Python

Install

pip install -e sdk/python

Use

import os
from spark_sdk import Spark

client = Spark(api_key=os.environ["SPARK_TEST_API_KEY"])

result = client.risk.score(
    transaction_id="txn_123",
    amount=1499,
    customer_id="customer_42",
    merchant_id="merchant_7",
)

print(result.decision)      # APPROVE, REVIEW or BLOCK
print(result.risk_score)    # 0.0 to 1.0
print(result.risk_band)     # LOW, MEDIUM or HIGH

The key is read from SPARK_API_KEY when you do not pass one, so it never has to appear in your source.

Errors

Every failure has its own type, and each carries the machine readable reason the API returned, so you branch on that rather than on English text.

from spark_sdk import (
    SparkAuthError, SparkRequestError, SparkRateLimitError,
    SparkNotAvailableError, SparkServerError, SparkError,
)

try:
    result = client.risk.score(amount=1499, customer_id="c", merchant_id="m")
except SparkRequestError as exc:
    for field in exc.fields:
        print(field["field"], field["problem"])
except SparkRateLimitError as exc:
    time.sleep(exc.retry_after_seconds or 1)
except SparkAuthError:
    ...   # key missing, revoked or not allowed here
except SparkError:
    ...   # catches everything above
Status Exception
401, 403 SparkAuthError
400, 422 SparkRequestError
429 SparkRateLimitError
501 SparkNotAvailableError
500 and above SparkServerError

Retries and timeouts

Connection failures, 429 and 5xx are retried with exponential backoff and jitter. A rejected request is never retried, because sending it again cannot change the answer.

client = Spark(api_key=..., timeout=10.0, max_retries=3)

Reading the result

result.decision                  # APPROVE, REVIEW or BLOCK
result.is_blocked                # decision == "BLOCK"
result.needs_review              # decision == "REVIEW"
result.scored_without_history    # nothing was known about any party
result.reasons                   # what moved the score, in words
result.review_threshold          # the cut this decision was made against
result.block_threshold
result.raw                       # everything the server sent

scored_without_history matters. When nothing is known about the customer, the merchant or the payment type, Spark raises the score to a floor. That floor is above the block threshold in the balanced setting, so a BLOCK on such a transaction means unknown, not risky. Treat those differently if you can.

Node and TypeScript

Use

import { Spark } from "@spark-ai/sdk";

const client = new Spark({ apiKey: process.env.SPARK_API_KEY });

const result = await client.risk.score({
  transactionId: "txn_123",
  amount: 1499,
  customerId: "customer_42",
  merchantId: "merchant_7",
});

console.log(result.decision, result.riskScore);

The surface is camelCase; the wire format stays snake_case. The SDK converts between them so you never have to.

Errors

import { SparkRequestError, SparkRateLimitError } from "@spark-ai/sdk";

try {
  await client.risk.score({ amount: 1499, customerId: "c", merchantId: "m" });
} catch (err) {
  if (err instanceof SparkRequestError) {
    for (const f of err.fields) console.log(f.field, f.problem);
  } else if (err instanceof SparkRateLimitError) {
    await new Promise((r) => setTimeout(r, (err.retryAfterSeconds ?? 1) * 1000));
  }
}

The same status to type mapping applies as in Python.

Fields the SDKs do not have

There is no currency parameter and no timestamp parameter in either SDK.

The model was fitted on the amount, the parties involved, the payment type and the location. It has no currency feature and no wall clock feature. Accepting those values would suggest Spark does something with them, and it would not. Convert to a single currency before you call, and Spark orders transactions by arrival.

Test keys and live keys

client.is_test_mode      # Python
client.isTestMode        # Node

A key starting sk_test_ is a test key. Test keys always resolve to the built-in model and never touch production state. A live key resolves to the model your organization approved for production; until one is approved, the built-in model is used, and the response says so through model_id.

Keeping the key out of your logs

Neither client will print the key. The Python repr and the Node toJSON deliberately omit it, so logging a client object cannot leak the credential. This is covered by a test in both SDKs.

Never put a live key in browser code. Anything shipped to a browser is readable by anyone who opens it.