Skip to content

Docs

Quickstart

From an API key to a stored image and a detected face in a few minutes. The base URL is https://api.viso.lol/v1.

01

Get access

viso.lol is in private beta. Join the waitlist; once your account exists, sign in to the dashboard. Every account starts with a personal team on the free plan.

02

Create an API key

Create a project, open API keys and create a key with the scopes you need (face:read, face:write, images:read, images:write). The full key is shown once: store it as a secret on your server.

export VISO_API_KEY="viso_sk_..."

Keys are secret. Never ship them to browsers or mobile apps; browsers upload through signed upload URLs (step 4).

03

Make a request

Install the SDK (zero dependencies, any runtime with fetch) or use plain HTTP.

npm install @viso-lol/sdk
import { Viso } from "@viso-lol/sdk";

// Reads VISO_API_KEY from the environment when apiKey is omitted.
const viso = new Viso({ apiKey: process.env.VISO_API_KEY });

const usage = await viso.usage.get();
console.log(usage.team.faceOperations); // { used: 0, limit: 1000 }

Create a bucket, upload an image and run Face on it by ID. Face is off until a team admin enables it for the project in the dashboard; until then Face routes answer 403 face.not_enabled.

const bucket = await viso.images.buckets.create({ name: "Avatars" });

const file = await viso.images.upload({
  bucketId: bucket.id,
  path: "avatars/ada.png",
  body: await readFile("ada.png"),
});
console.log(file.url); // public CDN URL

// Face must be enabled for the project (dashboard → Face).
const { faces } = await viso.face.detect({
  image: { fileId: file.id },
  attributes: "all",
});
04

Upload from browsers

Your server asks for a single-use upload URL narrowed to one path, a size and the types you accept. The browser sends the file straight to it, with progress.

// On your server
const { uploadUrl } = await viso.images.createUploadUrl({
  bucketId: bucket.id,
  path: `avatars/${user.id}.png`,
  maxSize: 5 * 1024 * 1024,
  expiresIn: 600,
});
// In the browser: no API key, the URL works once.
import { uploadToSignedUrl } from "@viso-lol/sdk";

const file = await uploadToSignedUrl(uploadUrl, input.files[0], {
  onProgress: ({ loaded, total }) => setProgress(loaded / (total ?? loaded)),
});
05

Errors and limits

Errors share one envelope with a stable code and the requestId to quote when you report a problem. The SDK throws them as VisoError.

{
  "error": {
    "code": "images.file_exists",
    "message": "A file already exists at this path and `overwrite` is false.",
    "requestId": "req_01jd8x3k5m9q2w7e4r6t8y0a1v"
  }
}
  • Each key may make 60 requests per minute. Over it, the API answers 429 rate_limit.exceeded with Retry-After; the SDK waits and retries.
  • Plan quotas (1 GB of storage, 1,000 face operations a month, 1,000 stored faces) answer 403 quota.exceeded. Don't retry those.
  • 503 responses mean the face provider is busy; they are safe to retry.

Every endpoint, parameter and error code is in the API reference.