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 |
Requesting access
Section titled “Requesting access”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.
1 · The endpoint
Section titled “1 · The endpoint”https://api.nitida.gofuture.spaceThat 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.
The whole setup
Section titled “The whole setup”NITIDA_ENDPOINT=https://api.nitida.gofuture.spaceNITIDA_RUNTIME_KEY=amk_rt_...NEXT_PUBLIC_NITIDA_TENANT_ID=12import { 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,});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.