SDK guides
Java
Use api14 from Java with the openai-java SDK, with setup, chat completion, streaming and error handling examples you can run.
Use the official openai-java library with api14. Set baseUrl on the client builder and pass model ids (for example openai/gpt-5-mini) as plain strings.
Install
Gradle:
implementation("com.openai:openai-java:4.+")
Maven:
<dependency>
<groupId>com.openai</groupId>
<artifactId>openai-java</artifactId>
<version>LATEST</version>
</dependency>
Then set API14_API_KEY=sk-api14-... in your environment.
Configure
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
OpenAIClient client = OpenAIOkHttpClient.builder()
.baseUrl("https://api14.com/api/v1")
.apiKey(System.getenv("API14_API_KEY"))
.build();
OpenAIOkHttpClient.fromEnv() reads OPENAI_BASE_URL and OPENAI_API_KEY instead, so you can switch environments without code changes:
export OPENAI_BASE_URL="https://api14.com/api/v1"
export OPENAI_API_KEY="sk-api14-..."
Chat
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model("openai/gpt-5-mini")
.addUserMessage("Explain vector databases in two sentences.")
.build();
ChatCompletion completion = client.chat().completions().create(params);
completion.choices().get(0).message().content().ifPresent(System.out::println);
Streaming
import com.openai.core.http.StreamResponse;
import com.openai.models.chat.completions.ChatCompletionChunk;
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model("anthropic/claude-sonnet-4.5")
.addUserMessage("Write a haiku about APIs.")
.build();
try (StreamResponse<ChatCompletionChunk> stream = client.chat().completions().createStreaming(params)) {
stream.stream()
.flatMap(chunk -> chunk.choices().stream())
.flatMap(choice -> choice.delta().content().stream())
.forEach(System.out::print);
}
See Streaming.
Tool calling
Describe a tool as a class; the SDK derives its JSON Schema and parses the arguments back into it:
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.openai.models.chat.completions.*;
import java.util.Collection;
@JsonClassDescription("Current weather for a city")
static class GetWeather {
public String city;
}
ChatCompletionCreateParams.Builder builder = ChatCompletionCreateParams.builder()
.model("openai/gpt-5-mini")
.addTool(GetWeather.class)
.addUserMessage("What's the weather in Istanbul?");
while (true) {
ChatCompletionMessage message = client.chat().completions().create(builder.build())
.choices().get(0).message();
builder.addMessage(message);
if (message.toolCalls().map(Collection::isEmpty).orElse(true)) {
message.content().ifPresent(System.out::println);
break;
}
for (ChatCompletionMessageToolCall call : message.toolCalls().get()) {
GetWeather args = call.asFunction().function().arguments(GetWeather.class);
builder.addMessage(ChatCompletionToolMessageParam.builder()
.toolCallId(call.asFunction().id())
.contentAsJson(getWeather(args.city)) // your function
.build());
}
}
The builder keeps the whole conversation: each loop adds the assistant message with its tool calls, then one tool message per call, and sends everything again. Fields of the tool class become the schema's properties; add @JsonPropertyDescription to a field to tell the model what it means. Cap the number of iterations in production code so a model that keeps calling tools cannot loop forever. See Tool calling.
Structured output
Pass a class to responseFormat; the SDK generates a strict JSON Schema from it and parses each choice's content into an instance. Use a model that lists structured_outputs or response_format in its supported_parameters:
static class Person {
public String name;
public int born;
public String city;
}
StructuredChatCompletionCreateParams<Person> params = ChatCompletionCreateParams.builder()
.model("openai/gpt-5-mini")
.responseFormat(Person.class)
.addUserMessage("Extract: Ada Lovelace, born 1815 in London.")
.build();
client.chat().completions().create(params).choices().stream()
.flatMap(choice -> choice.message().content().stream())
.forEach(person -> System.out.println(person.name + " " + person.born));
See Structured outputs.
Image input
import java.util.List;
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model("google/gemini-2.5-flash")
.addUserMessageOfArrayOfContentParts(List.of(
ChatCompletionContentPart.ofText(ChatCompletionContentPartText.builder()
.text("What is in this picture?").build()),
ChatCompletionContentPart.ofImageUrl(ChatCompletionContentPartImage.builder()
.imageUrl(ChatCompletionContentPartImage.ImageUrl.builder()
.url("https://example.com/photo.jpg") // or a data:image/png;base64,... URL
.build())
.build())))
.build();
See Vision & files.
Extra parameters
Gateway fields without a typed setter (reasoning, provider, models) go through putAdditionalBodyProperty:
import com.openai.core.JsonValue;
import java.util.Map;
ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
.model("anthropic/claude-sonnet-4.5")
.addUserMessage("Prove there are infinitely many primes.")
.putAdditionalBodyProperty("reasoning", JsonValue.from(Map.of("effort", "high")))
.build();
Errors
Non-2xx responses throw subclasses of OpenAIServiceException, which expose the status and headers:
import com.openai.errors.OpenAIServiceException;
try {
client.chat().completions().create(params);
} catch (OpenAIServiceException e) {
if (e.statusCode() == 402) {
System.err.println("Balance too low, add funds (insufficient_balance)");
} else if (e.statusCode() == 429) {
System.err.println("Rate limited, retry after " + e.headers().values("Retry-After") + "s");
} else {
throw e;
}
}
See Errors.
Timeouts and retries
The client retries connection errors, 408, 409, 429 and 5xx twice by default with exponential backoff. Configure the count and timeout on the builder, or per request:
import com.openai.core.RequestOptions;
import java.time.Duration;
OpenAIClient client = OpenAIOkHttpClient.builder()
.baseUrl("https://api14.com/api/v1")
.apiKey(System.getenv("API14_API_KEY"))
.maxRetries(4)
.timeout(Duration.ofSeconds(60))
.build();
client.chat().completions().create(params,
RequestOptions.builder().timeout(Duration.ofMinutes(5)).build());
The builder timeout applies to each attempt. Long reasoning requests can take minutes, so give them a longer per-request timeout or stream them. Retrying does not help with daily_limit_reached or insufficient_balance; catch RateLimitException and check the error code before scheduling another attempt. See Rate limits.
All capabilities
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api14.com/api/v1",
apiKey: process.env.API14_API_KEY,
});
const result = await client.chat.completions.create({
"model": "openai/gpt-5-mini",
"messages": [
{
"role": "user",
"content": "Explain vector databases in two sentences."
}
]
});
console.log(result.choices[0].message.content);