Skip to main content

SDKs

Two typed clients, generated from the same API contract. Download them at zero.nnumbers.com.br/downloads.

Why a file, and not a public registry

The platform namespace does not resolve through npm install or go get — that is an architectural decision. What is distributed is the real consumption model: the packed npm package and the Go package to vendor by copy, exactly how the CLI and the MCP server consume the Go SDK.

TypeScript​

npm install ./zero-sdk-typescript.tgz
import { ZeroClient } from '@nnumbers/zero';

const zero = new ZeroClient({
baseUrl: 'https://api.zero.nnumbers.com.br',
token: process.env.ZERO_TOKEN,
});

// deploy, with an idempotency key — repeating never duplicates
const deployment = await zero.createDeployment(service, {},
{ idempotencyKey: crypto.randomUUID() });

Go​

tar -xzf zero-sdk-go.tar.gz -C internal/
import "yourcompany.com/app/internal/zero"

client, err := zero.New(zero.Config{
BaseURL: "https://api.zero.nnumbers.com.br",
Token: zero.StaticToken(os.Getenv("ZERO_TOKEN")),
})

What both guarantee​

  • Idempotency — every mutation accepts a key; repeating the call never creates two resources.
  • Typed errors — the platform's error catalog becomes a type, with the code and the suggested action.
  • Live following — an operation's stream arrives as an iterable (TypeScript) or a channel (Go), without you assembling SSE by hand.

Fields that may be null​

A field the API may return as null is typed as nullable: T | null in TypeScript, a pointer in Go. The compiler now requires you to handle the null — in Go, check for nil before dereferencing; in TypeScript, narrow the type before using the value.

Networking Between Projects​

Both SDKs have the operations of networking between projects: networks, the environment in the network, permissions, project exposure, internal name, and Gateway internal entries.

const network = await zero.createNetwork(organization, { name: 'payments' },
{ idempotencyKey: crypto.randomUUID() });
await zero.attachEnvironmentNetwork(environment, { network_id: network.id });
await zero.setServiceInternalName(service, { name: 'api' }); // api.zero.internal

Two lists mix shapes, and the kind field says which each item is: a network's permissions (service_grant or gateway_entry_grant) and who comes into a project (service_grant or private_route). In TypeScript, the union narrows by kind, with no cast:

for (const item of (await zero.listNetworkAccessGrants(network.id)).items) {
if (item.kind === 'service_grant') console.log(item.target_service_name, item.port);
else console.log(item.entry_address);
}

In Go, the items arrive as json.RawMessage, and zero.DecodificarPermissaoDaRede and zero.DecodificarChegadaAoProjeto split them by kind. A kind your SDK version does not know comes back with no shape and no error, with the JSON in Bruto — the listing does not break because of a new type.

The network of an environment outside any network comes back null: network is null in TypeScript and nil in Go.

Changing the address​

setCanonicalDomain (TypeScript) and SetCanonicalDomain (Go) change the project's public address. Besides the new address, the response carries:

FieldWhat it is
operation_idThe deployment that puts the new address live — follow it to the end. Null when the project has never deployed: the previous address is released immediately and there is nothing to follow
previousThe previous address. May be null

While the deployment runs, the previous address appears among the project's domains with the state releasing: it answers until the deployment with the new address becomes ready, and is then released.