AI API Integration Examples
AILANG v0.3.9 introduces first-class support for calling AI APIs with HTTP headers, JSON encoding, and structured error handling.
Overview
Features:
- ✅ Custom HTTP headers (Authorization, API keys, etc.)
- ✅ JSON encoding with type-safe ADT construction
- ✅ Result-based error handling
- ✅ Security features (header validation, HTTPS enforcement)
- ✅ Support for OpenAI, Anthropic, Google, and other AI APIs
Quick Start
1. Claude (Anthropic) API
-- Example: Real Claude Haiku API call using AILANG
-- Demonstrates HTTP headers, JSON encoding, and Result-based error handling
--
-- Usage:
-- export ANTHROPIC_API_KEY="sk-ant-..."
-- ailang run --caps Net,IO --entry main examples/claude_haiku_call.ail
--
-- Requirements:
-- - ANTHROPIC_API_KEY environment variable must be set
-- - Net and IO capabilities required
--
-- Note: This example uses a placeholder API key. To run it:
-- 1. Get an API key from https://console.anthropic.com/
-- 2. Replace the placeholder in the main() function with your actual key
-- 3. Or modify the code to read from environment variables (when getEnv() is available)
module examples/experimental/claude_haiku_call
import std/json (encode, jo, ja, kv, js, jnum)
import std/net (httpRequest, NetError, Transport, DisallowedHost, InvalidHeader, BodyTooLarge)
import std/io (println)
-- chatClaude sends a message to Claude Haiku and returns the response
-- Parameters:
-- prompt: The message to send to Claude
-- apiKey: Your Anthropic API key (from console.anthropic.com)
-- Returns: String containing the full JSON response from the API
export func chatClaude(prompt: string, apiKey: string) -> string ! {Net, IO} {
let url = "https://api.anthropic.com/v1/messages";
-- Set up required headers for Anthropic API
let headers = [
{name: "x-api-key", value: apiKey},
{name: "anthropic-version", value: "2023-06-01"},
{name: "content-type", value: "application/json"}
];
-- Build JSON request body using the Json ADT
let body = encode(
jo([
kv("model", js("claude-3-5-haiku-20241022")),
kv("max_tokens", jnum(100.0)),
kv("messages", ja([
jo([
kv("role", js("user")),
kv("content", js(prompt))
])
]))
])
);
println("Calling Claude Haiku API...");
-- Make the HTTP POST request with Result-based error handling
match httpRequest("POST", url, headers, body) {
Ok(resp) =>
if resp.ok then {
println(concat_String("✓ Status: ", show(resp.status)));
resp.body
} else {
concat_String("HTTP error: ", show(resp.status))
}
Err(err) => match err {
Transport(msg) => concat_String("Network error: ", msg)
DisallowedHost(host) => concat_String("Blocked host: ", host)
InvalidHeader(hdr) => concat_String("Invalid header: ", hdr)
BodyTooLarge(size) => concat_String("Body too large: ", size)
}
}
}
export func main() -> () ! {Net, IO} {
println("===========================================");
println("AILANG + Claude Haiku API Example");
println("===========================================");
println("");
println("This example demonstrates:");
println(" • HTTP POST with custom headers");
println(" • JSON encoding for API requests");
println(" • Result-based error handling");
println(" • Real AI API integration");
println("");
-- IMPORTANT: Replace this placeholder with your actual API key
-- Get your key from: https://console.anthropic.com/
let apiKey = "<YOUR_ANTHROPIC_API_KEY>";
let prompt = "Write a haiku about functional programming";
println(concat_String("Prompt: ", prompt));
println("");
let response = chatClaude(prompt, apiKey);
println("");
println("Response from Claude:");
println("---");
println(response);
println("---");
println("");
println("✓ Example completed successfully!")
}
Usage:
# Get API key from https://console.anthropic.com/
export ANTHROPIC_API_KEY="sk-ant-..."
# Replace placeholder in the file, then run:
ailang run --caps Net,IO examples/experimental/claude_haiku_call.ail
Example Output:
===========================================
AILANG + Claude Haiku API Example
===========================================
Prompt: Write a haiku about functional programming
Calling Claude Haiku API...
✓ Status: 200
Response from Claude:
---
{"model":"claude-3-5-haiku-20241022","id":"msg_...",
"content":[{"type":"text","text":"Pure functions flow by\n
Immutable data glides smooth\nCode without side paths"}],
"usage":{"input_tokens":14,"output_tokens":98}}
---
✓ Example completed successfully!
2. OpenAI API
-- AILANG OpenAI API Integration Example
-- Demonstrates HTTP headers + JSON encoding for AI API calls
--
-- Usage:
-- ailang run --caps IO,Net --net-allow=api.openai.com --net-timeout=30s examples/ai_call.ail
--
-- Setup:
-- Get API key at: https://platform.openai.com/api-keys
-- Replace <YOUR_API_KEY> below with your actual key
--
-- What this demonstrates:
-- - httpRequest with custom headers (Authorization, Content-Type)
-- - JSON encoding with convenience helpers (jo, kv, js)
-- - Result matching (transport errors vs HTTP errors)
-- - Error handling (truncated error bodies, status codes)
module examples/experimental/ai_call
import std/json (encode, jo, ja, kv, js, jnum)
import std/io (println)
import std/net (httpRequest, NetError, Transport, DisallowedHost, InvalidHeader, BodyTooLarge)
-- Call OpenAI's GPT-4o-mini with a simple prompt
export func chatOpenAI(prompt: string, apiKey: string) -> string ! {Net} {
let url = "https://api.openai.com/v1/chat/completions";
-- Build headers
let headers = [
{name: "Authorization", value: concat_String("Bearer ", apiKey)},
{name: "Content-Type", value: "application/json"}
];
-- Build JSON request body
let body = encode(
jo([
kv("model", js("gpt-4o-mini")),
kv("messages", ja([
jo([
kv("role", js("user")),
kv("content", js(prompt))
])
])),
kv("max_tokens", jnum(100.0))
])
);
-- Make request and handle Result
match httpRequest("POST", url, headers, body) {
Ok(resp) =>
if resp.ok then
resp.body
else
-- HTTP error (4xx, 5xx) - would show status and body preview in production
concat_String("HTTP error: ", show(resp.status))
Err(err) =>
-- Transport error (DNS, timeout, TLS, allowlist)
match err {
Transport(msg) => concat_String("Transport error: ", msg)
DisallowedHost(host) => concat_String("Blocked: ", host)
InvalidHeader(hdr) => concat_String("Invalid header: ", hdr)
BodyTooLarge(size) => concat_String("Response too large: ", size)
}
}
}
export func main() -> () ! {IO, Net} {
_io_println("AILANG + OpenAI GPT-4o-mini Demo");
_io_println("=================================");
_io_println("");
_io_println("NOTE: Replace <YOUR_API_KEY> below with your actual OpenAI API key");
_io_println("Get your key at: https://platform.openai.com/api-keys");
_io_println("");
-- IMPORTANT: Replace with your actual API key
let apiKey = "<YOUR_API_KEY>";
_io_println("Calling OpenAI API...");
let response = chatOpenAI("Say 'Hello from AILANG!' in a creative way", apiKey);
_io_println("");
_io_println("Response:");
_io_println(response)
}
Usage:
# Get API key from https://platform.openai.com/api-keys
export OPENAI_API_KEY="sk-..."
ailang run --caps Net,IO examples/experimental/ai_call.ail
JSON Encoding
AILANG provides a type-safe JSON ADT for constructing JSON payloads:
Json ADT
type Json =
| JNull -- null
| JBool(bool) -- true/false
| JNumber(float) -- 42.0, 3.14
| JString(string) -- "hello"
| JArray(List[Json]) -- [1, 2, 3]
| JObject(List[{key: string, value: Json}]) -- {"a": 1}
Convenience Helpers
import std/json (jn, jb, jnum, js, ja, jo, kv, encode)
-- Primitives
jn() -- JNull
jb(true) -- JBool(true)
jnum(42.0) -- JNumber(42.0)
js("hello") -- JString("hello")
-- Collections
ja([jnum(1.0), jnum(2.0)]) -- JArray([JNumber(1.0), JNumber(2.0)])
jo([kv("x", jnum(1.0))]) -- JObject([{key: "x", value: JNumber(1.0)}])
-- Encoding
encode(jo([kv("name", js("Alice"))])) -- {"name":"Alice"}
Complex Example
-- Build a complex API request
let request = jo([
kv("model", js("gpt-4")),
kv("temperature", jnum(0.7)),
kv("messages", ja([
jo([
kv("role", js("system")),
kv("content", js("You are a helpful assistant"))
]),
jo([
kv("role", js("user")),
kv("content", js("Hello!"))
])
])),
kv("functions", ja([
jo([
kv("name", js("get_weather")),
kv("parameters", jo([
kv("type", js("object")),
kv("properties", jo([
kv("location", jo([kv("type", js("string"))]))
]))
]))
])
]))
]);
let json = encode(request);
-- Result: {"model":"gpt-4","temperature":0.7,"messages":[...],...}
HTTP Request Function
Signature
httpRequest(
method: string,
url: string,
headers: List[{name: string, value: string}],
body: string
) -> Result[HttpResponse, NetError] ! {Net}
Types
type HttpResponse = {
status: int,
headers: List[{name: string, value: string}],
body: string,
ok: bool -- true if status 200-299
}
type NetError =
| Transport(string) -- Network/connection error
| DisallowedHost(string) -- Domain not in allowlist
| InvalidHeader(string) -- Blocked header name
| BodyTooLarge(string) -- Response > 5MB
Error Handling
match httpRequest("POST", url, headers, body) {
Ok(resp) =>
if resp.ok then {
-- Success: status 200-299
processResponse(resp.body)
} else {
-- HTTP error: 4xx or 5xx
handleHTTPError(resp.status)
}
Err(err) => match err {
Transport(msg) => {
-- Network error: DNS, timeout, TLS, etc.
logError(concat_String("Network: ", msg))
}
DisallowedHost(host) => {
-- Security: domain not in allowlist
logError(concat_String("Blocked: ", host))
}
InvalidHeader(name) => {
-- Security: dangerous header blocked
logError(concat_String("Invalid header: ", name))
}
BodyTooLarge(size) => {
-- Response exceeded 5MB limit
logError(concat_String("Too large: ", size))
}
}
}
Security Features
1. Header Validation
Blocked headers (hop-by-hop, dangerous):
Connection,Proxy-Connection,Keep-AliveTransfer-Encoding,Upgrade,Trailer,TEHost(prevents Host header injection)Accept-Encoding(prevents decompression issues)Content-Length(calculated automatically)
-- ✅ Allowed
let headers = [
{name: "Authorization", value: "Bearer token"},
{name: "Content-Type", value: "application/json"},
{name: "User-Agent", value: "MyApp/1.0"}
];
-- ❌ Blocked (returns InvalidHeader error)
let badHeaders = [
{name: "Connection", value: "close"}, -- Blocked!
{name: "Host", value: "evil.com"} -- Blocked!
];
2. Cross-Origin Authorization Stripping
Authorization headers are automatically removed on cross-origin redirects:
-- Initial request to api.example.com with Authorization header
-- Redirect to cdn.example.com
-- → Authorization header automatically stripped
3. Method Whitelist
Only GET and POST are allowed in v0.3.9:
httpRequest("GET", url, headers, body) -- ✅ Allowed
httpRequest("POST", url, headers, body) -- ✅ Allowed
httpRequest("PUT", url, headers, body) -- ❌ Returns InvalidMethod error
4. Other Security Features
- ✅ HTTPS enforced (http:// requires
--net-allow-httpflag) - ✅ DNS rebinding prevention
- ✅ Private IP blocking (localhost, 10.x, 192.168.x, 172.16-31.x)
- ✅ Body size limit (5MB default)
- ✅ Domain allowlist support
- ✅ Redirect validation (max 5 redirects)
Common Patterns
1. Retry on Failure
func callWithRetry(prompt: string, apiKey: string, retries: int) -> string ! {Net, IO} {
match chatClaude(prompt, apiKey) {
Ok(resp) => resp
Err(Transport(msg)) =>
if retries > 0 then {
println("Retrying...");
callWithRetry(prompt, apiKey, retries - 1)
} else {
concat_String("Failed after retries: ", msg)
}
Err(other) => show(other) -- Don't retry non-transient errors
}
}
2. Response Parsing
-- Note: JSON decoding is planned for v0.4.0
-- For now, parse the JSON string manually or use external tools
func extractContent(jsonBody: string) -> string ! {} {
-- Manual parsing (simplified)
-- In practice, you'd use a proper JSON parser
jsonBody
}
3. Streaming Responses
Streaming is not yet supported in v0.3.9. The entire response is buffered:
-- ❌ Not yet supported
-- func streamChat(prompt: string) -> Stream[string] ! {Net}
-- ✅ Current approach (full response)
func chat(prompt: string) -> string ! {Net} {
-- Returns complete response after request finishes
}
Troubleshooting
"InvalidHeader" Error
Error: Err(InvalidHeader("connection"))
Cause: Attempting to use a blocked header Solution: Remove hop-by-hop headers (Connection, Transfer-Encoding, etc.)
"DisallowedHost" Error
Error: Err(DisallowedHost("evil.com"))
Cause: Domain not in allowlist (if configured) Solution: Add domain to allowlist or remove allowlist restriction
"Transport" Error with TLS
Error: Err(Transport("tls: certificate verify failed"))
Cause: Invalid TLS certificate Solution: Check the API endpoint URL is correct
HTTP 401 Unauthorized
HTTP error: 401
Cause: Invalid or missing API key Solution: Verify your API key is correct and properly formatted
API-Specific Examples
Google Gemini
func chatGemini(prompt: string, apiKey: string) -> string ! {Net, IO} {
let url = concat_String(
"https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=",
apiKey
);
let headers = [{name: "Content-Type", value: "application/json"}];
let body = encode(
jo([
kv("contents", ja([
jo([kv("parts", ja([jo([kv("text", js(prompt))])]))])
]))
])
);
match httpRequest("POST", url, headers, body) {
Ok(resp) => resp.body
Err(err) => show(err)
}
}
Generic REST API
func callAPI(endpoint: string, payload: Json, apiKey: string) -> string ! {Net, IO} {
let headers = [
{name: "Authorization", value: concat_String("Bearer ", apiKey)},
{name: "Content-Type", value: "application/json"}
];
match httpRequest("POST", endpoint, headers, encode(payload)) {
Ok(resp) => resp.body
Err(err) => show(err)
}
}
Next Steps
- JSON Decoding (planned v0.4.0): Parse JSON responses into AILANG values
- Streaming (planned v0.4.0): Stream API responses for real-time output
- Environment Variables (planned):
getEnv("API_KEY")for safer key management - More HTTP Methods (planned): PUT, DELETE, PATCH support
- Custom Timeout (planned): Per-request timeout configuration
Related Documentation
See the AILANG repository for more examples and documentation:
/examples/- Complete example files/stdlib/- Standard library implementations/docs/LIMITATIONS.md- Known limitations and workarounds