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:
- hermes.ai.unturf.com:
https://hermes.ai.unturf.com/v1- chat completions on an RTX 4090 - qwen.ai.unturf.com:
https://qwen.ai.unturf.com/v1- chat completions on an RTX 3090 - TTS:
https://speech.ai.unturf.com/v1- Text-to-speech generation
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:
| Variable | Meaning |
|---|---|
MODEL_ENDPOINT_N | Base 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_N | Bearer key for endpoint N. Unset means no key, which is all our free relay needs; a cloud endpoint sets one. |
MODEL_ID_N | Model id to send. Unset means ask /v1/models and take the first id, the rule used by every sample on this page. |
MODEL_LABEL_N | A display name for endpoint N; defaults to its hostname. |
TTS_ENDPOINT_N | The 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: