В этой статье
Если программа на C должна отправить запрос в OpenAI API, ей не нужен SDK на другом языке. Достаточно HTTPS-клиента, JSON-библиотеки и небольшого слоя, который собирает ответ в памяти. В этом примере запрос идёт напрямую к Responses API через libcurl, а JSON разбирается библиотекой cJSON. Это тот же прямой HTTP-подход, который описан в официальном quickstart для API, только без SDK.
Сначала важное разделение. ChatGPT — пользовательский продукт с подпиской и интерфейсом, а OpenAI API — HTTP-сервис для программ. Подписка ChatGPT сама по себе не выдаёт приложению право делать API-запросы и не заменяет API-ключ. Для API нужен ключ проекта; правила передачи ключа и заголовка описаны в официальном quickstart. Хранить его следует в переменной окружения, а не в исходнике и тем более не в клиентском приложении, которое получает пользователь.
Если задача на самом деле сводится к пользовательскому доступу к AI-сервису, а не к программному ключу и балансу API, варианты оформления можно посмотреть в каталоге услуг доступа к AI-сервисам Amber Market. Это не пополнение OpenAI API: перед заказом нужно открыть карточку, проверить условия и итоговую сумму, затем оформить выбранный вариант через подтверждение email и доступный способ оплаты. Для кода ниже нужен именно API-ключ.
Что понадобится
Нужны:
- компилятор с поддержкой C99;
- libcurl с HTTPS-поддержкой;
- cJSON;
- API-ключ в переменной
OPENAI_API_KEY; - идентификатор доступной модели.
Перед запуском проверьте установленные версии и доступность pkg-config:
curl-config --version
pkg-config --modversion libcurl
pkg-config --modversion libcjson 2>/dev/null || pkg-config --modversion cjson
Название пакета cJSON зависит от дистрибутива. Важно не конкретное число версии в статье, а то, что заголовок и библиотека находятся в системе и собираются одним toolchain. Идентификатор модели также сверяйте с официальным списком моделей перед запуском: доступность модели зависит от проекта и может измениться.
Минимальная программа на C99
Сохраните код в main.c. Значение gpt-4.1-mini — пример; при необходимости замените его в JSON-теле на актуальный идентификатор из документации и доступный вашему проекту.
#include <curl/curl.h>
#include <cjson/cJSON.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct response_buffer {
char *data;
size_t size;
};
static size_t write_callback(void *contents, size_t size, size_t count,
void *userp)
{
size_t bytes = size * count;
struct response_buffer *buffer = userp;
char *new_data;
if (bytes > (size_t)-1 - buffer->size - 1) {
return 0;
}
new_data = realloc(buffer->data, buffer->size + bytes + 1);
if (new_data == NULL) {
return 0;
}
buffer->data = new_data;
memcpy(buffer->data + buffer->size, contents, bytes);
buffer->size += bytes;
buffer->data[buffer->size] = '\0';
return bytes;
}
static const char *find_response_text(const cJSON *root)
{
const cJSON *output = cJSON_GetObjectItemCaseSensitive(root, "output");
const cJSON *item;
if (!cJSON_IsArray(output)) {
return NULL;
}
cJSON_ArrayForEach(item, output) {
const cJSON *content = cJSON_GetObjectItemCaseSensitive(item, "content");
const cJSON *part;
if (!cJSON_IsArray(content)) {
continue;
}
cJSON_ArrayForEach(part, content) {
const cJSON *type = cJSON_GetObjectItemCaseSensitive(part, "type");
const cJSON *text = cJSON_GetObjectItemCaseSensitive(part, "text");
if (cJSON_IsString(type) && strcmp(type->valuestring, "output_text") == 0 &&
cJSON_IsString(text)) {
return text->valuestring;
}
}
}
return NULL;
}
int main(void)
{
const char *api_key = getenv("OPENAI_API_KEY");
const char *request_json =
"{\"model\":\"gpt-4.1-mini\","
"\"input\":\"Reply with exactly: hello from C\"}";
char authorization[4096];
CURL *curl = NULL;
CURLcode curl_result;
long http_code = 0;
struct curl_slist *headers = NULL;
struct response_buffer response = {0};
cJSON *root = NULL;
const char *text = NULL;
if (api_key == NULL || api_key[0] == '\0') {
fprintf(stderr, "OPENAI_API_KEY is not set\n");
return EXIT_FAILURE;
}
int header_length = snprintf(authorization, sizeof(authorization),
"Authorization: Bearer %s", api_key);
if (header_length < 0 || (size_t)header_length >= sizeof(authorization)) {
fprintf(stderr, "API key is too long for the authorization header\n");
return EXIT_FAILURE;
}
if (curl_global_init(CURL_GLOBAL_DEFAULT) != 0) {
fprintf(stderr, "curl_global_init failed\n");
return EXIT_FAILURE;
}
curl = curl_easy_init();
if (curl == NULL) {
fprintf(stderr, "curl_easy_init failed\n");
curl_global_cleanup();
return EXIT_FAILURE;
}
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, authorization);
if (headers == NULL) {
fprintf(stderr, "could not allocate HTTP headers\n");
curl_easy_cleanup(curl);
curl_global_cleanup();
return EXIT_FAILURE;
}
curl_easy_setopt(curl, CURLOPT_URL, "https://api.openai.com/v1/responses");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_result = curl_easy_perform(curl);
if (curl_result != CURLE_OK) {
fprintf(stderr, "HTTPS request failed: %s\n",
curl_easy_strerror(curl_result));
goto cleanup;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
printf("HTTP status: %ld\n", http_code);
if (response.data == NULL) {
fprintf(stderr, "server returned an empty body\n");
goto cleanup;
}
root = cJSON_Parse(response.data);
if (root == NULL) {
fprintf(stderr, "invalid JSON response near: %s\n",
cJSON_GetErrorPtr() != NULL ? cJSON_GetErrorPtr() : "unknown");
goto cleanup;
}
if (http_code < 200 || http_code >= 300) {
const cJSON *error = cJSON_GetObjectItemCaseSensitive(root, "error");
const cJSON *message = error != NULL
? cJSON_GetObjectItemCaseSensitive(error, "message")
: NULL;
if (cJSON_IsString(message)) {
fprintf(stderr, "API error: %s\n", message->valuestring);
} else {
fprintf(stderr, "API returned HTTP %ld\n", http_code);
}
goto cleanup;
}
text = find_response_text(root);
if (text == NULL) {
fprintf(stderr, "valid JSON did not contain output_text\n");
goto cleanup;
}
printf("Response: %s\n", text);
cleanup:
cJSON_Delete(root);
free(response.data);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return (curl_result == CURLE_OK && http_code >= 200 && http_code < 300 &&
text != NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
}
В коде есть одна намеренно скучная, но полезная граница. curl_easy_perform сообщает о проблеме транспорта: DNS, TLS, таймауте или разрыве соединения. HTTP-код проверяется отдельно, потому что успешно установленное HTTPS-соединение ещё не означает успешный API-запрос. Например, неверная модель или ключ дадут JSON с ошибкой и кодом 4xx.
Функция callback вызывается частями, поэтому нельзя рассчитывать на один готовый буфер; именно такую настройку callback и других параметров описывает документация libcurl. Она расширяет динамический массив, добавляет завершающий нулевой байт и возвращает число принятых байт. После запроса память освобождается через free, JSON-дерево — через cJSON_Delete, как требует README cJSON, а easy handle и список заголовков — средствами libcurl.
Сборка и запуск
На Linux сначала установите dev-пакеты libcurl и cJSON средствами своего дистрибутива. Названия могут отличаться; нужны заголовки и библиотеки, а не только команда curl. Затем соберите программу:
cc -std=c99 -Wall -Wextra -O2 main.c \
$(pkg-config --cflags --libs libcurl libcjson) \
-o openai_c
Если ваш пакет cJSON зарегистрировал модуль под именем cjson, замените последний аргумент:
cc -std=c99 -Wall -Wextra -O2 main.c \
$(pkg-config --cflags --libs libcurl cjson) \
-o openai_c
На macOS тот же подход работает после установки libcurl и cJSON через используемый менеджер пакетов. Если pkg-config не видит формулу libcurl, передайте компилятору пути, которые показывает curl-config --cflags и curl-config --libs, а для cJSON — include- и library-path из установки cJSON.
Ключ задайте только в текущем сеансе оболочки:
export OPENAI_API_KEY='ваш_ключ_проекта'
./openai_c
Ожидаемый успешный результат выглядит примерно так:
HTTP status: 200
Response: hello from C
Точный текст зависит от модели, но код ответа должен быть в диапазоне 200–299, а в JSON должен находиться элемент output_text внутри output.
Если запрос не проходит
Проверяйте проблему по слоям — иначе легко чинить JSON, когда на самом деле не настроен TLS.
OPENAI_API_KEY is not setозначает, что процесс C не унаследовал переменную окружения. Проверьте её наличие в том же терминале и не выводите значение ключа в лог.- Ошибка
HTTPS request failedотносится к libcurl: сеть, DNS, сертификаты, прокси или таймаут. Это ещё не ответ API. - HTTP 401 обычно означает проблему с ключом или заголовком
Authorization. Ключ должен передаваться какBearer <token>. - HTTP 400 с сообщением о модели требует проверить
model, доступность модели для проекта и формат тела запроса. - HTTP 429 указывает на ограничение частоты или квоты. Повторять запрос вслепую не стоит: для операций с побочным эффектом повторная попытка требует отдельной стратегии идемпотентности.
- Ошибка разбора JSON означает, что серверный ответ не удалось представить как JSON, либо буфер был повреждён. Поэтому программа сначала проверяет результат
cJSON_Parse, а не обращается к полям наугад.
Для сравнения общей схемы первого API-запроса можно посмотреть вариант на Python, а для быстрой проверки endpoint без программы — отдельную проверку через curl. Но в C ответственность остаётся на вашем коде: он сам владеет буфером ответа, проверяет транспорт и HTTP, разбирает JSON и освобождает ресурсы.
Источники
- Developer quickstartOpenAI
- Create a model responseOpenAI
- cJSON READMEDave Gamble / cJSON contributors
- curl_easy_setoptThe curl project