Skip to content

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:

200
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

A few things worth noticing:

  • C3Http.request() is a static function — there is no HTTPRequest node to add, configure, or free. It works from any script, including RefCounted classes that aren't in the scene tree.
  • await suspends 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.ok check 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_event to consume a text/event-stream response incrementally.
  • Connection reuse (keep-alive) — set session to a shared Session to pool connections across calls to the same host.
  • Download to file — set download_file to stream the body straight to disk instead of memory.
  • Download progress — set on_progress to track (bytes_received, total_bytes) as the body arrives.
  • Cancellation — set cancellation_token and call cancel() on it from anywhere to abort an in-flight request.
  • Background threads — set use_threads to run the request loop off the main thread.
  • Testing without a network — install C3Http.Mock in 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.