Docs / Tools
SDKs and client libraries
Install and configure the official Python, Node.js and Go SDKs, and learn what they handle for you so you don't reimplement it.
Last updated June 3, 2026
#Overview
Tend maintains official SDKs for Python, Node.js and Go. All three target API version 2026-03-01, follow semantic versioning, and are thin by design: they wrap the REST API at https://api.tendcomputer.com/v2, add typed request and response objects, and take care of the tedious parts such as retries, idempotency keys, pagination and webhook signature verification.
| Language | Package | Install | Current version |
|---|---|---|---|
| Python | tend | pip install tend | 2.7.1 |
| Node.js | @tend/sdk | npm install @tend/sdk | 2.9.0 |
| Go | github.com/tendcomputer/tend-go | go get github.com/tendcomputer/tend-go | 2.4.2 |
Anything the SDKs can do, plain HTTP can do too. If you prefer to call the API directly with curl or your language's HTTP client, every endpoint works with the Authorization: Bearer <key> header and JSON bodies. The SDKs simply save you from writing the same 300 lines of retry logic that everyone eventually writes.
#Installation and authentication
Each SDK reads its API key from an explicit constructor argument or, if omitted, from the TEND_API_KEY environment variable. Keys beginning with tnd_live_ operate on production resources and keys beginning with tnd_dev_ operate on the development environment of the same project. The SDK never guesses; it sends whatever key you give it, and the API returns 401 invalid_api_key if the prefix or key is wrong.
# pip install tend==2.7.1
import os
from tend import Tend
client = Tend(
api_key=os.environ["TEND_API_KEY"],
region="eu-central",
)
job = client.jobs.create(
url="https://hooks.harborline.example/reports/build",
run_at="2026-06-04T08:00:00Z",
payload={"report": "weekly-summary", "account_id": 48213},
)
print(job.id, job.status)// npm install @tend/sdk@2.9.0
import Tend from "@tend/sdk";
const client = new Tend({
apiKey: process.env.TEND_API_KEY,
region: "eu-central",
});
const job = await client.jobs.create({
url: "https://hooks.harborline.example/reports/build",
runAt: "2026-06-04T08:00:00Z",
payload: { report: "weekly-summary", accountId: 48213 },
});
console.log(job.id, job.status);// go get github.com/tendcomputer/tend-go@v2.4.2
package main
import (
"context"
"fmt"
"log"
"os"
tend "github.com/tendcomputer/tend-go"
)
func main() {
client := tend.NewClient(os.Getenv("TEND_API_KEY"), tend.WithRegion("eu-central"))
job, err := client.Jobs.Create(context.Background(), &tend.JobCreateParams{
URL: "https://hooks.harborline.example/reports/build",
RunAt: "2026-06-04T08:00:00Z",
Payload: map[string]any{"report": "weekly-summary", "account_id": 48213},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(job.ID, job.Status)
}#What the SDKs handle for you
The value of an SDK is the list of things you no longer have to think about. All three libraries implement the following behaviors, and all can be configured or disabled.
- Idempotency keys. Any
createcall that changes state automatically sends anIdempotency-Keyheader with a random UUID, so a network retry never schedules the same job twice. The API remembers keys for 24 hours; reusing a key with a different body returns409 idempotency_conflict. Pass your own key to make a call deduplicate across process restarts. - Transport retries. Connection errors,
503 region_unavailableand429 rate_limit_exceededare retried up to 2 times by default, using exponential backoff with jitter and honoring theRetry-Afterheader. Validation errors (4xxother than 429) are never retried, since they will fail identically every time. - Pagination. List methods return page objects with
data,has_moreandnext_cursor, plus an auto-paging iterator. See the pagination guide for details on cursors and filters. - Typed errors. Failed calls raise or return errors carrying the HTTP status, the machine-readable
codesuch asinterval_too_short, the human-readable message, and thedetailslist. - Webhook verification. A helper checks the
Tend-Signatureheader against the raw request body and your signing secret, and rejects stale timestamps.
#Verifying webhook signatures
Tend signs every webhook delivery and puts the signature in the Tend-Signature header. Verify it before you trust the payload. The important detail is that verification requires the raw, unparsed request body; if your framework has already parsed and re-serialized the JSON, the signature will not match, and you will spend an afternoon staring at two identical-looking strings.
from flask import Flask, request, abort
from tend.webhooks import verify_signature, SignatureError
app = Flask(__name__)
SECRET = "whsec_4d1f7a9c20be83e6"
@app.post("/tend/webhook")
def handle():
try:
event = verify_signature(
payload=request.get_data(), # raw bytes, not request.json
header=request.headers["Tend-Signature"],
secret=SECRET,
)
except SignatureError:
abort(400)
print(event["run_id"], event["status"])
return "", 204import express from "express";
import { verifySignature } from "@tend/sdk/webhooks";
const app = express();
const SECRET = "whsec_4d1f7a9c20be83e6";
app.post("/tend/webhook", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = verifySignature({
payload: req.body, // Buffer, not parsed JSON
header: req.header("Tend-Signature"),
secret: SECRET,
});
console.log(event.run_id, event.status);
res.sendStatus(204);
} catch {
res.sendStatus(400);
}
});func handler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
event, err := webhooks.VerifySignature(body, r.Header.Get("Tend-Signature"), "whsec_4d1f7a9c20be83e6")
if err != nil {
http.Error(w, "bad signature", http.StatusBadRequest)
return
}
log.Println(event.RunID, event.Status)
w.WriteHeader(http.StatusNoContent)
}Your endpoint must respond within 15 seconds. Slow or non-2xx responses are retried for up to 48 hours. Acknowledge quickly and do the real work asynchronously.
#Configuration options
| Option | Default | Description |
|---|---|---|
api_key | TEND_API_KEY env var | Bearer key beginning with tnd_live_ or tnd_dev_. |
region | us-east | Region to route new jobs to. Hobby projects are limited to us-east; Pro can use any of the four regions. |
max_retries | 2 | Transport-level retries for connection errors, 429 and 503. |
timeout | 30 seconds | Per-request timeout. Unrelated to the 15-minute maximum job runtime. |
base_url | https://api.tendcomputer.com/v2 | Override for proxies or a local mock server. |
api_version | 2026-03-01 | Pin an API version. SDKs pin the version they were released against. |
Set max_retries to 0 if you are already running your own retry layer, such as a message queue with a dead-letter policy. Layering retries on retries multiplies request volume and can push you into 429 rate_limit_exceeded faster than you would expect.
#Versioning and support policy
SDKs follow semantic versioning: patch releases fix bugs, minor releases add features without breaking existing code, and major releases may change method signatures. The current major version for each language is 2. When a new major version ships, the previous one receives security and critical bug fixes for 12 months.
- Release notes for each SDK are published with the corresponding GitHub release and in the changelog.
- Pin an exact version in production lockfiles and upgrade deliberately, reading the notes first.
- Report SDK bugs by email to support@tendcomputer.com with the SDK version, language runtime version and, if possible, the request ID from the
Tend-Request-Idresponse header.