Skip to content
scroll to zoom · drag to pan

Getting your credentials

Most of what you need is already public. The API key is the only value you do not have, and the tenant id comes with it — they are issued together and they are bound to each other.

value what it is who has it
endpoint the asset-manager service URL published below
CDN base where bytes are served from already the default — you rarely set it
tenantId your numeric tenant issued with your key
API key amk_rt_… the one thing we send you

nitida is in closed beta — it currently runs the media for our own products, and access is granted case by case rather than through a signup form.

Write to nitida@gofuture.space with what you are building and roughly how much media you expect. You get back a tenant code, a tenant id, and three keys.

There is no self-serve flow yet, and saying so plainly beats pointing at a form that does not exist.

https://api.nitida.gofuture.space

That is the real, current production endpoint — not a placeholder. It is public; every request to it is authenticated by your key.

In a browser context the endpoint may be relative (/api/media), which is how the BFF pattern works: your page calls your own route, and your route holds the key. Node and Bun always need an absolute URL and throw a clear error otherwise.

2 · The CDN base — you do not need to set this

Section titled “2 · The CDN base — you do not need to set this”

It defaults to https://8ok.uk. Unless someone told you otherwise, leave it alone: no env var, no fallback expression, nothing to configure.

// This is enough. There is no `?? "https://8ok.uk"` to write.
setTenantId(12);

Set it only if you were given a different host.

3 · The tenant, and why you cannot pick one

Section titled “3 · The tenant, and why you cannot pick one”

Your tenant is a pair: a code like "acme" and a numeric id like 12.

The numeric id is what builds URLs, and it is safe in the browser — it only appears in public paths. Ship it as NEXT_PUBLIC_NITIDA_TENANT_ID.

⚠️ Variant paths use the id in base36: tenant 12 is /c/v/…, tenant 10 is /a/v/…. This is invisible for tenants 0–9, where decimal and base36 look identical, and it is the single most common 404 in this SDK. Never build the path by hand — call getAssetUrl after setTenantId.

4 · The three keys, and the fourth you ask for

Section titled “4 · The three keys, and the fourth you ask for”

Issued together, per tenant:

key ops who holds it
runtime amk_rt_* read, write, list your server / BFF
admin amk_ad_* + delete, admin, upscale your back-office, never a user-facing app
ci amk_ci_* + delete, admin build pipelines

A runtime key carries no paid operation, and that is deliberate. A metered op bills a third party per call — an upscale run costs €0.10 — so a credential that lives on a server handling public traffic must not be able to spend money. Runtime keys used to carry upscale, at 12 000 requests per minute; nothing ever used it.

key ops how you get it
metered amk_mt_* read, write, list, upscale ask for it — not issued by default

It is rate-limited to 60 requests per minute and cannot delete. If you call a metered route without one, the platform answers QUOTA_EXCEEDED with a monthly limit of zero — which is not a rate limit you can wait out. A ceiling of zero means this credential is not entitled to the operation at all, so retrying never succeeds. Ask for a metered key.

They are printed exactly once — the platform cannot show them to you again. Save them to your secret manager the moment you receive them. Rotating means issuing a new set and revoking the old one.

.env — server only
NITIDA_ENDPOINT=https://api.nitida.gofuture.space
NITIDA_RUNTIME_KEY=amk_rt_...
.env — safe to expose
NEXT_PUBLIC_NITIDA_TENANT_ID=12
lib/media.ts — server
import { NitidaClient } from "@nitida/sdk/server";
export const nt = new NitidaClient({
endpoint: process.env.NITIDA_ENDPOINT!,
apiKey: process.env.NITIDA_RUNTIME_KEY!,
tenantCode: "acme", // log-only; the key is what actually scopes you
tenantId: 12,
});
lib/media-urls.ts — imported anywhere, client components included
import { setTenantId } from "@nitida/asset-client";
// Process-global: pin it once where your helpers live and every module that
// imports a builder is configured. Forgetting this is the base36 404 above.
setTenantId(Number(process.env.NEXT_PUBLIC_NITIDA_TENANT_ID));

That is the entire configuration. Two env vars on the server, one in the client, and no CDN base to think about.

Telling a credential problem from something that looks like one

Section titled “Telling a credential problem from something that looks like one”
what you see what it means
tenant_not_found the tenant code has no row, or the 60-second config cache is stale
401 / 403 on presign wrong key, revoked key, or a runtime key attempting an admin op
404 on a variant URL usually the base36 prefix or a missing setTenantId — but check visibility first: a private asset answers 404 on every public door, and that IS an access decision. See Access and privacy
400 on a transform URL not auth either — the width is off the allowed ladder

The 400 is never a credential problem. The 404 usually is not — but since private assets shipped it sometimes is, and that case is invisible from the URL, which is why visibility is the first thing to check rather than the last.