Docs

Candid speaks OpenAI's API. Any OpenAI client library works: point it at Candid's base URL and use a Candid key.

Quick start

  1. Add credit to your account.
  2. Create an API key.
  3. Make your first request:
from openai import OpenAI

client = OpenAI(base_url="https://candidapi.fun/v1", api_key="sk-cnd-…")

reply = client.chat.completions.create(
    model="candid-text",
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.choices[0].message.content)

Authentication

Send your key in the Authorization header as a Bearer token. Keys start with sk-cnd-. Each key can make up to 300 requests a minute.

Authorization: Bearer sk-cnd-…

Models

List every model with its type and price. This endpoint doesn't need a key.

curl https://candidapi.fun/v1/models
candid-textcandid-text-roleplaycandid-text-largecandid-text-longcandid-voicecandid-transcribecandid-imagecandid-image-hdcandid-videocandid-video-from-image

Text

POST /v1/chat/completions

OpenAI's chat completions format: messages, temperature, max_tokens, tools, response_format and the rest. Set "stream": true to receive tokens as they're written. You're charged for the tokens in and out, at the model's rates.

stream = client.chat.completions.create(
    model="candid-text",
    messages=[{"role": "user", "content": "Write the warlord's speech before he burns the village."}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")

Voice

POST /v1/audio/speech

Turns up to 4,096 characters of input into audio. Choose a voice (for example af_sky, am_adam or bf_emma), a speed from 0.5 to 2, and a response_format: mp3, opus, aac, flac, wav or pcm. Priced per character.

audio = client.audio.speech.create(
    model="candid-voice",
    voice="af_sky",
    input="You left your jacket at the bar last night.",
)
audio.write_to_file("speech.mp3")

Transcription

POST /v1/audio/transcriptions

Upload audio as multipart form data in file, up to 25 MB. Optionally set language (e.g. en), response_format (json or text) and timestamps. Priced per minute of audio.

with open("call.mp3", "rb") as f:
    text = client.audio.transcriptions.create(model="candid-transcribe", file=f)
print(text.text)

Images

POST /v1/images/generations

Send a prompt, and optionally n (1–4), size such as 1024x1024 or 832x1216, and output_format (png, jpeg or webp). You can also add negative_prompt, seed and steps. Images come back as base64 in data[].b64_json. Priced per image.

import base64

result = client.images.generate(
    model="candid-image",
    prompt="a neon-lit noir detective in the rain",
    size="832x1216",
)
open("image.png", "wb").write(base64.b64decode(result.data[0].b64_json))

Video

Videos take a minute or more, so you start a job and collect the result.

POST /v1/videos/quote

See the price first. Takes the same options as creating a video, and returns price_usd.

POST /v1/videos

Starts a video. Send model, prompt, and optionally seconds, resolution (e.g. 720p), aspect_ratio (e.g. 16:9) and negative_prompt. Image-to-video models also need image, as a URL or a base64 data URL. The price is charged when the video starts, and returned to your balance if it can't be made.

GET /v1/videos/{id}

Its status: queued, in_progress, completed or failed. Check every few seconds.

GET /v1/videos/{id}/content

The finished MP4. Videos are kept for 7 days.

curl https://candidapi.fun/v1/videos \
  -H "Authorization: Bearer $CANDID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "candid-video", "prompt": "a velvet curtain opens on an empty cabaret", "seconds": 5, "resolution": "720p"}'

# → {"id": "video_…", "status": "queued", "cost_usd": 1.23, …}

curl https://candidapi.fun/v1/videos/video_… -H "Authorization: Bearer $CANDID_API_KEY"
curl https://candidapi.fun/v1/videos/video_…/content -H "Authorization: Bearer $CANDID_API_KEY" -o video.mp4

Errors

Errors use OpenAI's shape, so client libraries raise them as usual:

{
  "error": {
    "message": "Your balance is empty. Add credit to continue.",
    "type": "insufficient_balance",
    "code": "insufficient_balance"
  }
}
400Something in the request isn't valid. The message says what.
401The API key is missing or not valid.
402Your balance is too low. Add credit.
404The model or video doesn't exist.
429Too many requests. Wait a moment and retry.
502, 503The model couldn't finish. Retry after a short wait.

Billing

Every request draws from your prepaid balance. You add credit by paying in USDG on Robinhood Chain from your billing page: link your wallet once, then each payment from it is added to your balance once the network confirms it. One USDG is one dollar of credit. A request that fails isn't charged. A long text request can take your balance slightly below zero; the next request waits until you top up.