uncloseai.

C Examples - Free LLM & TTS AI Service

C Examples

This page demonstrates how to use the uncloseai. API endpoints with C using libcurl. All examples use the OpenAI-compatible API interface, making it easy to switch between different models and endpoints.

Available Endpoints:

Both chat hostnames currently serve the same model, turboderp/Qwen3.8-27B-exl3 (read live from /v1/models). Hostnames are routing labels, not model names: every example below asks the endpoint which model it serves instead of hard-coding an id, so a model swap on the cards never breaks your code. The served model reasons before it answers (the reasoning arrives in a separate reasoning field), so give it room: the samples ask for 600 tokens because at 150 the budget ran out before the answer began.

Many endpoints from environment variables

Our clients (uncloseai-cli, uncloseai.js, opencompletion and the downloadable implementation for every language on this site) share one convention, so an endpoint is configuration, never code:

VariableMeaning
MODEL_ENDPOINT_NBase URL of the N-th OpenAI-compatible endpoint, ending in /v1. The only required variable. Scan N from 0 upward and skip gaps: uncloseai-cli counts from 0, the per-language implementations from 1.
MODEL_API_KEY_NBearer key for endpoint N. Unset means no key, which is all our free relay needs; a cloud endpoint sets one.
MODEL_ID_NModel id to send. Unset means ask /v1/models and take the first id, the rule used by every sample on this page.
MODEL_LABEL_NA display name for endpoint N; defaults to its hostname.
TTS_ENDPOINT_NThe same shape for text-to-speech endpoints.
export MODEL_ENDPOINT_0="https://hermes.ai.unturf.com/v1"
export MODEL_ENDPOINT_1="https://qwen.ai.unturf.com/v1"
# export MODEL_API_KEY_1="sk-..."      # only for an endpoint that needs one
export TTS_ENDPOINT_0="https://speech.ai.unturf.com/v1"

The first endpoint is the default; the rest fail over, in order. The C implementation under /languages/ reads these variables and discovers each endpoint's models at startup.

Setup & Dependencies

Install libcurl development libraries:

# Ubuntu/Debian
sudo apt-get install libcurl4-openssl-dev

# macOS
brew install curl

# Alpine
apk add curl-dev

Compile with:

gcc -o chat chat.c -lcurl

Non-Streaming Examples

Non-streaming mode waits for the complete response before returning. This is simpler to use but provides no intermediate feedback during generation.

Using hermes.ai.unturf.com (RTX 4090)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

struct Memory {
    char *response;
    size_t size;
};

static size_t write_callback(void *data, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct Memory *mem = (struct Memory *)userp;
    char *ptr = realloc(mem->response, mem->size + realsize + 1);
    if (!ptr) return 0;
    mem->response = ptr;
    memcpy(&(mem->response[mem->size]), data, realsize);
    mem->size += realsize;
    mem->response[mem->size] = 0;
    return realsize;
}

int main(void) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct Memory chunk = {.response = malloc(1), .size = 0};
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    // Ask the endpoint which model it serves; never hard-code an id.
    curl_easy_setopt(curl, CURLOPT_URL, "https://hermes.ai.unturf.com/v1/models");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
    if (curl_easy_perform(curl) != CURLE_OK) return 1;

    char model[256] = {0};
    const char *id = strstr(chunk.response, "\"id\":\"");
    if (!id) return 1;
    id += 6;
    snprintf(model, sizeof model, "%.*s", (int)strcspn(id, "\""), id);
    chunk.size = 0;  // reuse the buffer for the chat response

    char payload[1024];
    snprintf(payload, sizeof payload,
        "{"
        "\"model\": \"%s\","
        "\"messages\": [{\"role\": \"user\", \"content\": \"Give a Python Fizzbuzz solution in one line of code?\"}],"
        "\"max_tokens\": 600"
        "}", model);

    curl_easy_setopt(curl, CURLOPT_URL, "https://hermes.ai.unturf.com/v1/chat/completions");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);

    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK) {
        printf("Response:\n%s\n", chunk.response);
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(chunk.response);
    return 0;
}

Using qwen.ai.unturf.com (RTX 3090)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

// ... same write_callback and Memory struct as above ...

int main(void) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct Memory chunk = {.response = malloc(1), .size = 0};
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    // Ask the endpoint which model it serves; never hard-code an id.
    curl_easy_setopt(curl, CURLOPT_URL, "https://qwen.ai.unturf.com/v1/models");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
    if (curl_easy_perform(curl) != CURLE_OK) return 1;

    char model[256] = {0};
    const char *id = strstr(chunk.response, "\"id\":\"");
    if (!id) return 1;
    id += 6;
    snprintf(model, sizeof model, "%.*s", (int)strcspn(id, "\""), id);
    chunk.size = 0;  // reuse the buffer for the chat response

    char payload[1024];
    snprintf(payload, sizeof payload,
        "{"
        "\"model\": \"%s\","
        "\"messages\": [{\"role\": \"user\", \"content\": \"Explain quicksort in C with code\"}],"
        "\"max_tokens\": 500"
        "}", model);

    curl_easy_setopt(curl, CURLOPT_URL, "https://qwen.ai.unturf.com/v1/chat/completions");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);

    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK) {
        printf("Response:\n%s\n", chunk.response);
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    free(chunk.response);
    return 0;
}

Streaming Examples

Streaming mode returns chunks of the response as they are generated, providing real-time feedback. This is ideal for interactive applications and long responses.

Streaming with hermes.ai.unturf.com (RTX 4090)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

// ... same write_callback and Memory struct as the non-streaming example ...

static size_t stream_callback(void *data, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    // libcurl hands over bytes, not a C string: strtok needs a NUL-terminated
    // copy. (An SSE line split across two callbacks is skipped here; the
    // downloadable uncloseai.c buffers partial lines.)
    char *chunk = malloc(realsize + 1);
    if (!chunk) return 0;
    memcpy(chunk, data, realsize);
    chunk[realsize] = '\0';

    // Process SSE data lines
    char *line = strtok(chunk, "\n");
    while (line) {
        if (strncmp(line, "data: ", 6) == 0) {
            const char *json = line + 6;
            if (strcmp(json, "[DONE]") != 0) {
                // Extract content from: {"choices":[{"delta":{"content":"..."}}]}
                const char *content_start = strstr(json, "\"content\":\"");
                if (content_start) {
                    content_start += 11;
                    const char *content_end = strchr(content_start, '"');
                    if (content_end) {
                        fwrite(content_start, 1, content_end - content_start, stdout);
                        fflush(stdout);
                    }
                }
            }
        }
        line = strtok(NULL, "\n");
    }
    free(chunk);
    return realsize;
}

int main(void) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    struct Memory chunk = {.response = malloc(1), .size = 0};
    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    // Ask the endpoint which model it serves; never hard-code an id.
    curl_easy_setopt(curl, CURLOPT_URL, "https://hermes.ai.unturf.com/v1/models");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
    if (curl_easy_perform(curl) != CURLE_OK) return 1;

    char model[256] = {0};
    const char *id = strstr(chunk.response, "\"id\":\"");
    if (!id) return 1;
    id += 6;
    snprintf(model, sizeof model, "%.*s", (int)strcspn(id, "\""), id);
    free(chunk.response);

    char payload[1024];
    snprintf(payload, sizeof payload,
        "{"
        "\"model\": \"%s\","
        "\"messages\": [{\"role\": \"user\", \"content\": \"Write a short poem about coding\"}],"
        "\"max_tokens\": 200,"
        "\"stream\": true"
        "}", model);

    curl_easy_setopt(curl, CURLOPT_URL, "https://hermes.ai.unturf.com/v1/chat/completions");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, stream_callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);

    printf("Streaming response:\n");
    curl_easy_perform(curl);
    printf("\n");

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}

Text-to-Speech Examples

Generate audio from text using the TTS endpoint.

Basic TTS

#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>

static size_t write_file(void *data, size_t size, size_t nmemb, void *userp) {
    return fwrite(data, size, nmemb, (FILE *)userp);
}

int main(void) {
    CURL *curl = curl_easy_init();
    if (!curl) return 1;

    FILE *fp = fopen("output.mp3", "wb");
    if (!fp) {
        curl_easy_cleanup(curl);
        return 1;
    }

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    const char *payload =
        "{"
        "\"model\": \"tts-1-f5\","
        "\"input\": \"Hello! This is a test of text to speech.\","
        "\"voice\": \"foxhop\""
        "}";

    curl_easy_setopt(curl, CURLOPT_URL, "https://speech.ai.unturf.com/v1/audio/speech");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_file);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK) {
        printf("Audio saved to output.mp3\n");
    }

    fclose(fp);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}

Available voices: foxhop, aria, clara, elena, grace, hazel, iris, luna and 33 more, listed live at speech.ai.unturf.com/v1/voices; pass any id as voice.

Full SDK Implementation

For a complete SDK with model discovery and advanced features, see the full implementations in the source repository: