# One API for text, image, video and audio

> One Tchavi key and one base URL cover chat, images, voice, transcription, embeddings, video and Jev. Every call, measured in credits.

- Published: 2026-10-14
- Author: Gael — Tchavi founder
- Canonical: https://tchavi.com/en/blog/one-api-text-image-video-audio

---


Yes, one key and one base URL are enough for text, images, voice, transcription, embeddings, video and Jev decisions. You change the endpoint path and the `model` field, nothing else.

To check it, I ran a real test: a chat call, an image at minimum quality, a voice clip, embeddings, a transcription of that clip and a Jev decision. Everything went through the same key, for 26.549 credits in total, about KES 2 at October 2026 rates. I did not generate the video: I give you the flow and the price per second.

## In short

- **Tchavi, an OpenAI-compatible AI API gateway you pay for with mobile money, serves six kinds of calls with a single key**: chat, image, audio (voice and transcription), embeddings, video and Jev decisions. Base URL: `https://tchavi.com/api/v1`.
- **What changes from one type to the next**: the path (`/chat/completions`, `/images/generations`, `/audio/speech`…) and the model. The `Authorization` header stays the same.
- **Video is asynchronous**: you send a request, get a job ID, then poll `/v1/jobs/:id` until the video is ready.
- **Measured in October 2026, through Tchavi**: 26.549 credits for the six calls, about KES 2 (1 credit is about KES 0.076).
- **Tchavi is not cheaper than calling each provider directly**: what you gain is one key, one balance and one log, and payment in mobile money.

## The principle: one header, six paths

Every call below starts from this small Node helper (version 20 or later). It reads the key from an environment variable and reads the `x-credits-used` header of each response.

```js
const BASE = 'https://tchavi.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.TCHAVI_API_KEY}` };

async function post(path, body) {
  const res = await fetch(`${BASE}${path}`, {
    method: 'POST',
    headers: { ...headers, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  console.log(path, res.status, 'credits:', res.headers.get('x-credits-used'));
  return res;
}
```

The [principles doc](https://tchavi.com/en/docs/principles) explains the rest: the format is OpenAI's, and every response carries `X-Credits-Used`, `X-Credits-Remaining` and `X-Request-Id`.

These are public prices; some models are on discount right now, and each model page shows the price billed today.

One note on the examples: the text inputs of the chat, voice, transcription, embeddings and Jev calls are the French strings I measured with, with an English gloss in the comments. The token counts and the Jev result below come from those runs; English input would count slightly differently.

## Text: chat

Endpoint: `POST /v1/chat/completions` ([doc](https://tchavi.com/en/docs/chat-completions)). Test model: [GPT-5 Mini](https://tchavi.com/en/m/openai/gpt-5-mini), at 0.55 credits per 1,000 input tokens and 4.4 per 1,000 output tokens.

```js
const res = await post('/chat/completions', {
  model: 'gpt-5-mini',
  messages: [
    {
      role: 'user',
      // "Explain in two sentences what an online shop in Cotonou is."
      content: "Explique en deux phrases ce qu'est une boutique en ligne à Cotonou.",
    },
  ],
  max_completion_tokens: 400,
});
const data = await res.json();
console.log(data.choices[0].message.content);
```

Measured: 23 input tokens, 59 output tokens, **0.273 credits**. You can check the arithmetic: 23 × 0.55 / 1,000 + 59 × 4.4 / 1,000 = 0.27225, rounded up once to the next thousandth.

## Image: the lowest quality

Endpoint: `POST /v1/images/generations` ([doc](https://tchavi.com/en/docs/image-generation)). Test model: [GPT Image 1 Mini](https://tchavi.com/en/m/openai/gpt-image-1-mini), at 11 credits per image at `low` quality.

```js
import { writeFileSync } from 'node:fs';

const res = await post('/images/generations', {
  model: 'gpt-image-1-mini',
  prompt: 'A stall of mangoes at a market in Nairobi, morning light',
  size: '1024x1024',
  quality: 'low',
});
const data = await res.json();
writeFileSync('mangoes.png', Buffer.from(data.data[0].b64_json, 'base64'));
```

Measured: **11 credits**, the `low` quality price. If you leave out `quality`, the `high` quality is billed: 77 credits on this model. For a test, remember to set it.

## Voice: from speech synthesis to transcription

Two endpoints in the [audio doc](https://tchavi.com/en/docs/audio). Speech synthesis bills per 1,000 characters; transcription bills per started minute.

**Synthesis**: `POST /v1/audio/speech`, with [TTS Standard](https://tchavi.com/en/m/openai/tts-1) (`tts-1`) at 32 credits per 1,000 characters. The response is the raw audio file.

```js
// "Hello, your order is ready. The delivery rider will arrive in ten minutes."
const text = 'Bonjour, votre commande est prête. Le livreur arrive dans dix minutes.';
const res = await post('/audio/speech', { model: 'tts-1', input: text, voice: 'nova' });
const mp3 = Buffer.from(await res.arrayBuffer());
writeFileSync('order.mp3', mp3);
```

Measured: 70 characters, **2.24 credits** (70 × 32 / 1,000), an mp3 of about 69 KB.

**Transcription**: `POST /v1/audio/transcriptions`, as `multipart/form-data`, with [Whisper](https://tchavi.com/en/m/openai/whisper-1) at 13 credits per started minute.

```js
const form = new FormData();
form.append('model', 'whisper-1');
form.append('language', 'fr');
form.append('file', new Blob([mp3], { type: 'audio/mpeg' }), 'order.mp3');

const res = await fetch(`${BASE}/audio/transcriptions`, {
  method: 'POST',
  headers, // no Content-Type: fetch adds the multipart boundary
  body: form,
});
console.log((await res.json()).text, res.headers.get('x-credits-used'));
```

Measured on the clip I had just generated: **13 credits** for a measured duration of 0.07 minutes. A four-second clip is therefore billed as a full minute. The transcription was correct, with one detail: it ended with "et…" ("and…"), which was not in the original text. Always re-read the end of an automatic transcription.

## Embeddings: vectors

Endpoint: `POST /v1/embeddings` ([doc](https://tchavi.com/en/docs/embeddings)). Model: [Embedding Small](https://tchavi.com/en/m/openai/text-embedding-3-small), at 0.044 credits per 1,000 tokens.

```js
const res = await post('/embeddings', {
  model: 'text-embedding-3-small',
  input: 'Livraison à Cotonou', // "Delivery in Cotonou"
});
const data = await res.json();
console.log(data.data[0].embedding.length); // 1536
```

Measured: 6 tokens, 1,536 dimensions, **0.001 credits**. The raw calculation gives 0.000264, but the total is rounded up to the next thousandth of a credit, so 0.001. On a small call that floor matters; to vectorise a catalogue, it is the large volumes that count.

## Jev decisions: typed answers

Endpoint: `POST /v1/decisions` ([doc](https://tchavi.com/en/docs/decisions)). Instead of asking a chat model for JSON, you declare questions and receive typed answers with their probabilities. Model: [Jev 1.13](https://tchavi.com/en/m/typesafe/jev-1.13), at 0.09 credits per 1,000 input tokens.

```js
const res = await post('/decisions', {
  model: 'jev-1.13',
  // "The customer writes: 'My API key stopped working since I renewed it.'"
  state: "Le client écrit : « Ma clé API ne marche plus depuis que je l'ai renouvelée. »",
  questions: {
    team: {
      type: 'choice',
      instructions: 'Quelle équipe doit traiter cela ?', // "Which team should handle this?"
      criteria: {
        billing: 'paiements ou crédits', // payments or credits
        technical: 'API, clés', // API, keys
      },
    },
    urgency: {
      type: 'score',
      instructions: "Quel est le niveau d'urgence ?", // "What is the urgency level?"
      criteria: ['pas urgent', 'normal', 'urgent', 'critique'], // not urgent, normal, urgent, critical
    },
  },
});
console.log((await res.json()).answers);
```

Measured: the `technical` team with a probability of 1, and an urgency of 1.6 on the 0 to 3 scale (confidence 0.6), for **0.035 credits**. Decision models only work on this endpoint: sending `jev-1.13` to `/v1/chat/completions` returns a `400` error.

## Video: submit, then poll

Video generation is asynchronous, because a render takes time. Two endpoints: `POST /v1/videos/generations` ([doc](https://tchavi.com/en/docs/video-generation)) and `GET /v1/jobs/:id` ([jobs doc](https://tchavi.com/en/docs/jobs)).

The flow:

1. You send the request. The response comes back immediately with a `202` and a job `id`.
2. You poll `GET /v1/jobs/:id`. The status goes from `pending` to `processing`, then to `completed` or `failed`.
3. At `completed`, the response contains `output.video_url`. The video is deleted 24 hours after it is generated: download it.
4. At `failed`, the credits charged are refunded automatically.

```js
const submit = await post('/videos/generations', {
  model: 'veo-3.1-lite',
  prompt: 'A boda boda rider crosses a Nairobi market at sunrise',
  duration: 4,
  resolution: '720p',
  generate_audio: false,
});
const job = await submit.json();

let task;
do {
  await new Promise((r) => setTimeout(r, 5000));
  task = await (await fetch(`${BASE}/jobs/${job.id}`, { headers })).json();
} while (task.status === 'pending' || task.status === 'processing');

console.log(task.status, task.output?.video_url);
```

I did not generate a video for this article. The price is per second of output. For [Veo 3.1 Lite](https://tchavi.com/en/m/google/veo-3.1-lite), it is 66 credits per second at 720p without audio, and 110 with audio: a 4-second video without audio therefore comes to 264 credits, about KES 20. Accepted durations run from 4 to 8 seconds.

## The total, call by call

| Call                        | Model                  | Measured credits | About KES  |
| --------------------------- | ---------------------- | ---------------- | ---------- |
| Chat (23 tokens in, 59 out) | gpt-5-mini             | 0.273            | 0.02       |
| Image (1024×1024, `low`)    | gpt-image-1-mini       | 11               | 0.83       |
| Voice (70 characters)       | tts-1                  | 2.24             | 0.17       |
| Embeddings (6 tokens)       | text-embedding-3-small | 0.001            | under 0.01 |
| Transcription (4 s clip)    | whisper-1              | 13               | 0.98       |
| Jev decision (2 questions)  | jev-1.13               | 0.035            | under 0.01 |
| **Total**                   |                        | **26.549**       | **2.01**   |

At about KES 0.076 per credit, that is roughly KES 2 for six calls of six different kinds. The two most expensive calls, the image and the transcription, are also the ones priced as a flat amount per unit: one image, one minute. Measured in October 2026, through Tchavi, on a test instance at public prices.

## What it changes, and what it does not

What you gain: one key to protect, one balance to watch, one log where everything appears, and a single error response format. For a shop that sends a voice confirmation, a product thumbnail and a WhatsApp reply, it is one account to top up with mobile money.

What you do not gain: a token price lower than the providers', or an identical API for every model. Each model keeps its own parameters: a parameter it does not accept returns a `400` error that names it. For a general comparison, read [What is an AI API gateway, and when do you need one](https://tchavi.com/en/blog/what-is-an-ai-api-gateway).

## FAQ

### Is one key enough for text, image, voice and video?

Yes. You send the same key in the `Authorization` header for every endpoint. Only the path and the `model` field change.

### How does video generation by API work?

It is asynchronous. You submit the request, receive a job ID, then poll `GET /v1/jobs/:id` until `completed` or `failed`. The video is deleted 24 hours after it is generated.

### How much does an image, voice or transcription call cost?

It depends on the model. In October 2026, an image at low quality cost 11 credits, 70 characters of voice 2.24 credits and a transcription 13 credits. Each model's page gives the current price.

### Do I have to change SDK to go from text to image?

No, if you use an OpenAI-compatible SDK with the base `https://tchavi.com/api/v1`. The examples in this article simply use `fetch`.

### What happens if a video fails?

The credits charged are refunded automatically, and the job shows `failed` with an error code.

### Can I use embeddings with the same credits?

Yes. Every call draws on the same credit balance, and each response shows its cost in `X-Credits-Used`.

[create your account and top up KES 250](https://tchavi.com/en/register?next=%2Fdashboard%2Fbilling%3Famount%3D250%26currency%3DKES%26from%3Dblog-one-api-en)

> _This article is part of the series "Building an AI product from Africa". Tchavi is an OpenAI-compatible AI API gateway: one key gives access to over 100 text, image, audio and video models, and you pay for it with mobile money, no bank card ([countries and operators](https://tchavi.com/en/docs/payments)). More guides on [the blog](https://tchavi.com/en/blog). Follow us: [LinkedIn](https://www.linkedin.com/company/tchavi) and [X](https://x.com/tchaviai)._

