A small, fast C library for OpenAI’s HTTP APIs built on an evented cURL layer. It provides a builder-style request API, streaming sinks, and helpers for /v1/responses, /v1/chat/completions, and /v1/embeddings—plus utilities for structured outputs (JSON Schema).
Non‑affiliation: This project is an independent library for interacting with the OpenAI API. It is not affiliated with, endorsed by, or sponsored by OpenAI. “OpenAI” and related marks are trademarks of OpenAI OpCo, LLC.
- Event‑loop friendly: built on
a-curl-library
’scurl_event_loop_t
. - Builder API per endpoint (init → configure → submit).
- Streaming via SSE with simple callbacks (text deltas, events, usage, errors).
- Structured Outputs: request validated JSON per schema; strict mode supported.
- Chaining: pass
previous_response_id
across calls. - Includes: opt into specific streamed envelopes (e.g., text deltas, tool args).
- Typed message parts: text, image URLs, file IDs.
- Utilities: parsing helpers for usage, errors, and incomplete details.
-
CMake ≥ 3.20
-
C compiler with C23 (examples set
C_STANDARD 23
) -
Dependencies (as separate libraries):
a-curl-library
(event loop, requests, resources)a-json-library
(JSON builder/parser)a-memory-library
(pool/buffer utilities)
-
Environment:
OPENAI_API_KEY
must be set
If you’ve built and installed the library (and its deps) with CMake packages:
find_package(a_curl_openai_plugin CONFIG REQUIRED)
add_executable(app main.c)
target_link_libraries(app PRIVATE a_curl_openai_plugin::a_curl_openai_plugin)
See
examples/
for complete, working CMake projects.
#include "a-curl-library/curl_event_loop.h"
#include "a-curl-library/curl_event_request.h"
#include "a-curl-library/curl_resource.h"
#include "a-curl-openai-plugin/plugins/v1/responses.h"
#include "a-curl-openai-plugin/sinks/v1/responses.h"
#include <stdio.h>
#include <stdlib.h>
static void done(void*, curl_event_request_t*, bool ok,
const char* txt, int p, int c, int t) {
if (!ok) { fputs("request failed\n", stderr); return; }
puts(txt);
fprintf(stderr, "(prompt=%d completion=%d total=%d)\n", p, c, t);
}
int main(void) {
const char* key = getenv("OPENAI_API_KEY");
if (!key || !*key) { fputs("OPENAI_API_KEY?\n", stderr); return 1; }
curl_event_loop_t* loop = curl_event_loop_init(NULL,NULL);
curl_event_res_id kid = curl_event_res_register(loop, strdup(key), free);
curl_event_request_t* r =
openai_v1_responses_init(loop, kid, "gpt-4o-mini");
openai_v1_responses_sink(r, done, NULL);
openai_v1_responses_input_text(r, "Explain quantum tunnelling briefly.");
openai_v1_responses_submit(loop, r, 0);
curl_event_loop_run(loop);
curl_event_loop_destroy(loop);
}
#include "a-curl-openai-plugin/sinks/v1/responses_stream.h"
static void on_delta(void*, const char* utf8, size_t n) {
fwrite(utf8, 1, n, stdout); fflush(stdout);
}
static void on_done(void*) { puts("\n[done]"); }
void example_stream(curl_event_loop_t* loop, curl_event_res_id kid) {
curl_event_request_t* r = openai_v1_responses_init(loop, kid, "gpt-4o-mini");
openai_v1_responses_set_stream(r, true);
openai_v1_responses_include_stream_text_minimal(r); /* helper preset */
openai_v1_responses_stream_callbacks_t cbs = {
.on_text_delta = on_delta,
.on_message_done = on_done
};
openai_v1_responses_stream_sink_init(r, &cbs, NULL);
openai_v1_responses_input_text(r, "Write a haiku about autumn.");
openai_v1_responses_submit(loop, r, 0);
}
#include "a-json-library/ajson.h"
#include "a-memory-library/aml_pool.h"
/* Ask for a strict object: { city, tempC, conditions } */
static const char *SCHEMA =
"{"
" \"type\":\"object\","
" \"properties\":{"
" \"city\":{\"type\":\"string\"},"
" \"tempC\":{\"type\":\"number\"},"
" \"conditions\":{\"type\":\"string\"}"
" },"
" \"required\":[\"city\",\"tempC\",\"conditions\"],"
" \"additionalProperties\":false"
"}";
static void done_struct(void*, curl_event_request_t*, bool ok,
const char* txt, int, int, int) {
if (!ok || !txt) { fputs("fail\n", stderr); return; }
aml_pool_t* pool = aml_pool_init(1024);
ajson_t* obj = ajson_parse_string(pool, txt); /* txt is JSON text */
puts(ajson_stringify(pool, obj));
aml_pool_destroy(pool);
}
/* ... create loop/kid ... */
curl_event_request_t* r = openai_v1_responses_init(loop, kid, "gpt-4o-mini");
openai_v1_responses_sink(r, done_struct, NULL);
openai_v1_responses_set_structured_output(r, "weather", SCHEMA, /*strict*/true);
openai_v1_responses_input_text(r, "Return today's Paris weather per schema.");
openai_v1_responses_submit(loop, r, 0);
/v1/responses
(builder):
openai_v1_responses_init(loop, api_key_id, model_id)
- Inputs:
openai_v1_responses_input_text
, message builder (begin_message
,message_add_text
,message_add_image_url
,message_add_file_id
,end_message
) - Core knobs:
set_temperature
,set_top_p
,set_max_output_tokens
,set_instructions
,set_top_logprobs
,set_truncation
- Streaming & includes:
set_stream
,set_stream_options_json
,add_include
,set_includes
, presets likeinclude_stream_text_minimal
,include_reasoning_encrypted
,include_refusal
- Structured outputs:
set_structured_output(name, schema_json, strict)
,set_structured_output_json
- Tools:
add_tool_json
,set_tool_choice_json
; parallel control (set_parallel_tool_calls
,set_max_tool_calls
) - Chaining & deps:
chain_previous_response
,add_dependency
- Other:
set_metadata_kv
,set_prompt_cache_key
,set_service_tier
,set_store
,set_background
,set_reasoning_json
,set_safety_identifier
, prompt templates (set_prompt
,set_prompt_vars_json
,set_prompt_var
) - Submit:
openai_v1_responses_submit(loop, req, priority)
Sinks (responses):
- Blocking summary:
openai_v1_responses_sink(req, complete_cb, arg)
→ aggregated text + token usage. - Streaming:
openai_v1_responses_stream_sink_init(req, &callbacks, arg)
withon_text_delta
,on_event
,on_usage
,on_error
,on_completed
.
Companions:
openai_v1_responses_get/delete/cancel(loop, api_key_id, response_id, sink)
.
Chat Completions (/v1/chat/completions
):
openai_v1_chat_completions_init
,*_add_message
, knobs (set_temperature
,set_top_p
,set_max_tokens
, penalties,stream
,set_user
), sink:openai_v1_chat_completions_sink
.
Embeddings (/v1/embeddings
):
openai_v1_embeddings_init
, inputs (add_text
,add_texts
), params (set_dimensions
,set_encoding_format
,set_user
), sink:openai_v1_embeddings_sink
.
Parsing helpers:
openai_responses_parse_output(pool, raw_json, &out)
→ items, roles, aggregated text, usage.- Optionals:
openai_responses_parse_error
,openai_responses_parse_incomplete
,openai_responses_parse_usage
.
Two sets of ready‑to‑run samples:
-
Basic text:
examples/responses_01_basic_text
Small blocking calls, temperature sweep, logprobs, chaining, cache key, etc. -
Structured output:
examples/responses_02_structured_output
Single object/arrays, nested shapes, validation errors, streaming entities, and chain/fan‑out demos.
Build any set:
cd examples/responses_01_basic_text
./build.sh
cd ../responses_02_structured_output
./build.sh
-
The blocking responses sink returns
success
and token counts(prompt, completion, total)
. -
For raw JSON access (e.g., to inspect HTTP errors), use
a-curl-library
’smemory_sink
and parse with:openai_responses_parse_error(...)
openai_responses_parse_incomplete(...)
openai_responses_parse_usage(...)
- Keep
OPENAI_API_KEY
out of source control. - Consider
openai_v1_responses_set_store(req, false)
if you do not want server‑side storage of responses. - Use
set_safety_identifier
and refusal includes/presets to inspect safety outcomes.
Apache-2.0. See LICENSE
.
Copyright © 2025 Andy Curtis.
Non‑affiliation: This project is an independent library for interacting with the OpenAI API. It is not affiliated with, endorsed by, or sponsored by OpenAI. “OpenAI” and related marks are trademarks of OpenAI OpCo, LLC.