Quick Start
This page walks through the core of C3Http: making requests, reading responses, handling failures, and setting per-request options. Every snippet targets a live public test API (JSONPlaceholder or httpbin), so you can paste it into a project and run it as-is.
It assumes the addon is already installed — see Installation if not.
Your first request
Attach this script to any node and run the scene:
extends Node
func _ready() -> void:
var res := await C3Http.request("https://jsonplaceholder.typicode.com/todos/1")
if not res.ok:
push_error(str(res.error))
return
print(res.status)
print(res.text)
Output:
A few things worth noticing:
C3Http.request()is a static function — there is noHTTPRequestnode to add, configure, or free. It works from any script, includingRefCountedclasses that aren't in the scene tree.awaitsuspends the coroutine while the request runs; the game keeps rendering frames. When the response arrives, execution resumes on the next line.- One
if not res.okcheck covers everything: DNS failures, TLS errors, timeouts, and non-2xx statuses all land in the same place.
Reading the response
The Response object gives you the body in whichever form you need:
var res := await C3Http.request("https://jsonplaceholder.typicode.com/todos/1")
if not res.ok:
push_error(str(res.error))
return
print(res.status) # 200
print(res.headers) # PackedStringArray of "Name: value" strings
print(res.body) # raw bytes (PackedByteArray)
print(res.text) # body decoded as UTF-8 (lazy, cached)
# res.json parses the body as JSON on first access and caches the result.
# It is null if the body isn't valid JSON.
var todo: Variant = res.json
if todo is Dictionary:
print(todo["title"]) # delectus aut autem
Responses with Content-Encoding: gzip are decompressed transparently — body, text, and json always see the decoded payload.
Handling errors
When anything goes wrong, res.ok is false and res.error holds a typed RequestError. A non-2xx status is one kind of error:
var res := await C3Http.request("https://jsonplaceholder.typicode.com/todos/9999")
print(res.ok) # false
print(res.status) # 404
print(res.error) # [http] status=404 Request failed with status 404.
print(res.error.kind == C3Http.RequestError.Kind.HTTP) # true
Note that the response body, headers, and status are still populated on HTTP errors — you can inspect an error payload from the server just like a success body.
res.error.kind tells you what class of failure occurred, so you can branch on it when it matters:
| Kind | Meaning |
|---|---|
HTTP |
The server responded with a non-2xx status |
TRANSPORT |
DNS, connection, or TLS failure — no response arrived |
TIMEOUT |
Options.timeout elapsed |
CANCELLED |
The request's CancellationToken was cancelled |
BODY_SIZE_LIMIT_EXCEEDED |
The body exceeded Options.body_size_limit |
CLIENT |
A local usage error, such as a malformed URL |
Sending data: POST with JSON
Pass the method, headers, and body as additional arguments:
var res := await C3Http.request(
"https://jsonplaceholder.typicode.com/posts",
PackedStringArray(["Content-Type: application/json"]),
HTTPClient.METHOD_POST,
JSON.stringify({"title": "hello", "body": "world", "userId": 1})
)
print(res.status) # 201
print(res.json) # { "title": "hello", "body": "world", "userId": 1, "id": 101 }
Methods come straight from Godot's native HTTPClient.Method enum (HTTPClient.METHOD_POST, HTTPClient.METHOD_PUT, HTTPClient.METHOD_DELETE, …) — the same values HTTPRequest uses.
The body parameter of request() is a String. To send raw bytes (a file upload, protobuf, etc.), use request_raw(), which is identical except the body is a PackedByteArray sent as-is:
var res := await C3Http.request_raw(
"https://httpbin.org/post",
PackedStringArray(["Content-Type: application/octet-stream"]),
HTTPClient.METHOD_POST,
PackedByteArray([0xDE, 0xAD, 0xBE, 0xEF])
)
Per-request options
Everything beyond the URL, headers, method, and body lives in an Options object passed as the fifth argument. Options are per-call — nothing is global, so one request's settings never leak into another:
var opts := C3Http.Options.new()
opts.timeout = 0.5 # seconds; 0.0 (the default) means no timeout
var res := await C3Http.request(
"https://httpbin.org/delay/3", # httpbin waits 3 s before responding
PackedStringArray(),
HTTPClient.METHOD_GET,
"",
opts
)
print(res.ok) # false
print(res.error) # [timeout] Timed out waiting for response.
Redirects are followed automatically up to Options.max_redirects (default 8), so res reflects the final response the chain lands on.
Signatures
For reference, the full signatures of the two entry points:
static func request(
url: String,
custom_headers: PackedStringArray = PackedStringArray(),
method: HTTPClient.Method = HTTPClient.METHOD_GET,
request_data: String = "",
options: Options = null
) -> Response
static func request_raw(
url: String,
custom_headers: PackedStringArray = PackedStringArray(),
method: HTTPClient.Method = HTTPClient.METHOD_POST,
request_data_raw: PackedByteArray = PackedByteArray(),
options: Options = null
) -> Response
Going further
That's the core. Options also unlocks the advanced features — each is a property away:
- Server-Sent Events — set
on_sse_eventto consume atext/event-streamresponse incrementally. - Connection reuse (keep-alive) — set
sessionto a sharedSessionto pool connections across calls to the same host. - Download to file — set
download_fileto stream the body straight to disk instead of memory. - Download progress — set
on_progressto track(bytes_received, total_bytes)as the body arrives. - Cancellation — set
cancellation_tokenand callcancel()on it from anywhere to abort an in-flight request. - Background threads — set
use_threadsto run the request loop off the main thread. - Testing without a network — install
C3Http.Mockin your tests to stub responses and assert on outgoing calls.
For runnable demos of all of the above, see the demo project in the repository, and the API reference for the complete surface.