Client snippets

There is no vizvuz package to install. The API is one HTTP call, and a dependency that wraps one HTTP call costs you more in supply-chain risk than it saves in typing. Copy what you need; it is yours to change.

Every snippet reads the key from the environment variable VIZVUZ_KEY.

Shell

translate() {
  curl -sS -X POST https://api.vizvuz.com/v1/translate \
    -H "Authorization: Bearer ${VIZVUZ_KEY}" \
    -H "Content-Type: application/json" \
    -d "$(jq -n --arg t "$1" --arg l "$2" '{text:[$t],target_lang:$l}')" \
  | jq -r '.translations[0].text'
}

translate "Hello world" DE

PHP

<?php
declare(strict_types=1);

function vizvuzTranslate(array $texts, string $targetLang, array $options = []): array
{
    $payload = json_encode(['text' => $texts, 'target_lang' => $targetLang] + $options, JSON_THROW_ON_ERROR);

    $ch = curl_init('https://api.vizvuz.com/v1/translate');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 35,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . getenv('VIZVUZ_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => $payload,
    ]);
    $body = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($body === false) {
        throw new RuntimeException('transport failure');
    }
    $decoded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
    if ($status >= 400) {
        throw new RuntimeException($decoded['code'] . ': ' . $decoded['detail']);
    }
    return array_column($decoded['translations'], 'text');
}

print_r(vizvuzTranslate(['Hello world'], 'DE', ['formality' => 'more']));

JavaScript

const RETRYABLE = new Set(['rate_limited', 'engine_unavailable', 'service_unavailable', 'internal_error']);

export async function translate(texts, targetLang, options = {}, attempts = 4) {
  for (let attempt = 0; attempt < attempts; attempt++) {
    const response = await fetch('https://api.vizvuz.com/v1/translate', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.VIZVUZ_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ text: texts, target_lang: targetLang, ...options }),
    });

    if (response.ok) {
      const { translations } = await response.json();
      return translations.map((t) => t.text);
    }

    const problem = await response.json();
    if (!RETRYABLE.has(problem.code)) {
      throw new Error(`${problem.code}: ${problem.detail}`);
    }
    const wait = Number(response.headers.get('Retry-After') ?? 2 ** attempt);
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
  throw new Error('giving up after retries');
}

Python

import os
import time
import requests

RETRYABLE = {"rate_limited", "engine_unavailable", "service_unavailable", "internal_error"}
ENDPOINT = "https://api.vizvuz.com/v1/translate"


def translate(texts, target_lang, attempts=4, **options):
    payload = {"text": texts, "target_lang": target_lang, **options}
    headers = {"Authorization": f"Bearer {os.environ['VIZVUZ_KEY']}"}

    for attempt in range(attempts):
        response = requests.post(ENDPOINT, headers=headers, json=payload, timeout=35)
        if response.ok:
            return [t["text"] for t in response.json()["translations"]]

        problem = response.json()
        if problem["code"] not in RETRYABLE:
            raise RuntimeError(f"{problem['code']}: {problem['detail']}")
        time.sleep(float(response.headers.get("Retry-After", 2 ** attempt)))

    raise RuntimeError("giving up after retries")

Go

package vizvuz

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type request struct {
	Text       []string `json:"text"`
	TargetLang string   `json:"target_lang"`
}

type response struct {
	Translations []struct {
		Text                   string `json:"text"`
		DetectedSourceLanguage string `json:"detected_source_language"`
	} `json:"translations"`
	Characters int `json:"characters"`
}

type problem struct {
	Code   string `json:"code"`
	Detail string `json:"detail"`
}

func Translate(texts []string, targetLang string) ([]string, error) {
	body, err := json.Marshal(request{Text: texts, TargetLang: targetLang})
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodPost, "https://api.vizvuz.com/v1/translate", bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("VIZVUZ_KEY"))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 35 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode >= 400 {
		var p problem
		_ = json.NewDecoder(res.Body).Decode(&p)
		return nil, fmt.Errorf("%s: %s", p.Code, p.Detail)
	}

	var parsed response
	if err := json.NewDecoder(res.Body).Decode(&parsed); err != nil {
		return nil, err
	}
	out := make([]string, 0, len(parsed.Translations))
	for _, t := range parsed.Translations {
		out = append(out, t.Text)
	}
	return out, nil
}

If you prefer a package

The DeepL client libraries for Python and Node accept a custom server URL and work against this API unchanged — see Migrating from DeepL. That is a supported path, not a hack.

Whatever you write, do these three things

  1. Batch. Fifty texts in one call instead of fifty calls — see

Rate limits.

  1. Retry only what is retryable. Branch on code, never on the message — see

Errors.

  1. Log X-Request-Id. It is the only thing that makes a support request

answerable in minutes rather than days.

Last updated Sep 1, 2026, 12:00 AM