1. Big picture
- Every exported
elements_...function is a thin wrapper around one HTTP request. - Requests are async and callback-based (GameMaker’s HTTP async event).
- Auth is handled via named tokens (
"auth_bearer","session_secret") that you store once and which are then injected into all requests that need them. - Request bodies are formatted based on a Content-Type → converter mapping.
- Responses go through an optional hook system and then into your callback.
Once you understand that pattern, every endpoint behaves the same way.
2. Calling an endpoint
All endpoints follow this shape:
function some_elements_endpoint(arg1, arg2, ..., _callback = undefined)
{
// validate arguments
// build URL
// (optionally) build query params
// define required security schemes
return _elements_create_request(_url, _params, _method, _body, _content_type, _security, _callback, _GMFUNCTION_);
}
Example: simple GET without body
elements_get_application("my-app-id", function(_code, _data, _request) {
if (_code == 200) {
show_debug_message("App: " + string(_data));
} else {
show_debug_message("Error: " + string(_code));
}
});
-
elements_get_applicationwill: -
Validate its arguments.
- Build URL:
"{rest_url}/application/my-app-id". - Require security:
[ "auth_bearer", "session_secret" ]. - Create an
ElementsRequestandsend()it. - It returns the HTTP request id from
http_request(in case you want to track/cancel).
Example: POST with body
var _body = new ElementsCreateAppleIapReceipt(_receipt_base64, "SANDBOX");
elements_upload_apple_iap_receipt(_body, function(_code, _data, _request) {
if (_code == 200) {
// _data is whatever the server returns (likely JSON parsed to a struct/array)
show_debug_message("Reward issuances: " + string(_data));
} else {
show_debug_message("IAP error: " + string(_code));
}
});
When a body is a struct generated by these constructors, the wrapper calls
<Type>_validate()before sending to ensure types are correct.
3. Authentication model
3.1. How tokens are stored
Auth tokens live in the obj_elements_core singleton, in its auth_tokens map:
// set
_elements_request_auth_set_token("auth_bearer", some_token);
_elements_request_auth_set_token("session_secret", some_other_token);
// get
var bearer = _elements_request_auth_get_token("auth_bearer");
The singleton is created lazily:
function _elements_get_singleton(_where)
{
static instance = instance_create_depth(0, 0, 0, obj_elements_core);
with (instance) return self;
}
So you don’t manually create it; using any of the helper functions will.
3.2. How tokens are injected into requests
Endpoints declare which auth schemes they need:
var _security = [ "auth_bearer", "session_secret" ];
Then ElementsRequest.send() calls _apply_auth for each scheme:
_self._apply_auth(_header, _params, security[_i], where);
_apply_auth does:
case "auth_bearer":
var _token = _elements_request_auth_get_token("auth_bearer");
if (is_undefined(_token)) { /* log missing */ break; }
_header[? "Authorization"] = "Bearer " + _token;
break;
case "session_secret":
var _token = _elements_request_auth_get_token("session_secret");
if (is_undefined(_token)) { /* log missing */ break; }
_header[? "Elements-SessionSecret"] = _token;
break;
So:
- If you don’t set a token, that scheme is simply skipped (no header added).
- If you do set it, it will be applied automatically on all endpoints that list that scheme.
3.3. What you must do after login
From your example:
elements_create_username_password_session(_session_request, function(_code, _data, _request) {
if (_code == 200) {
obj_game.session_data = _data;
var _session_secret = _data.sessionSecret;
_elements_request_auth_set_token("auth_bearer", _session_secret);
// (optionally) also:
// _elements_request_auth_set_token("session_secret", _session_secret);
}
show_debug_message("elements_create_username_password_session :: " + string(_data));
});
Pattern:
- Call a login/auth endpoint.
-
In its callback:
-
Extract token(s) from
_data. - Store them using
_elements_request_auth_set_token. - After that, all other endpoints needing those schemes will automatically send the right headers.
That’s the main thing a user of this API needs to understand about auth.
4. The async HTTP flow (callbacks)
Here’s the lifecycle:
- You call an
elements_*function. - It creates an
ElementsRequestand calls.send(). -
send(): -
Builds headers and URL.
- Calls
http_request(...), gets an HTTP id. - Stores itself in
obj_elements_core.requests[? id] = self. - Later, GameMaker fires an Async HTTP event on
obj_elements_corewithasync_load.
Inside that event (your snippet):
var _async_id = async_load[? "id"];
var _request = requests[? _async_id];
if (_request == undefined) exit; // unknown request
var _status = async_load[? "status"];
if (_status == 1) exit; // still in progress
var _code = async_load[? "http_status"];
var _data = async_load[? "result"];
4.1. Response hooks (optional)
Before your callback is run, hooks are checked:
var _hook = response_hooks[? _code];
if (is_callable(_hook) && _hook(_code, _data, _request) == true) {
ds_map_delete(requests, _async_id);
return;
}
- A hook is a global handler you can register per HTTP status code.
- If it returns
true, it “consumes” the response and the per-request callback is skipped.
You register a hook like:
_elements_request_response_set_hook(401, function(_code, _data, _request) {
// global unauthorized handler, maybe redirect to login screen
// return true to stop further handling
return true;
});
4.2. Calling your callback
If no hook stops it, your callback is called:
var _callback = _request.get_callback();
if (is_callable(_callback)) {
try {
_data = json_parse(_data);
} catch(_ex) { /* if not JSON, leave _data as string */ }
_callback(_code, _data, _request);
}
ds_map_delete(requests, _async_id);
So:
_codeis the HTTP status (200, 400, 500, …).-
_datais: -
Parsed JSON → struct/array when possible.
- Raw string if
json_parsefails. _requestis theElementsRequestinstance (you can inspect method, URL and even retry the request).
Your callbacks should always be ready to handle both success and error codes.
5. Content types & body conversion
5.1. How content_type is used
Every request passes a _content_type string into ElementsRequest:
- Some endpoints fix it internally:
static _content_type = "application/json";
...
_elements_create_request(_url, _params, "POST", _body, _content_type, ...);
- Some let you override it:
function elements_update_product_bundle_for_application_configuration(..., _body = undefined, _content_type = "*/*", _callback = undefined)
{
...
return _elements_create_request(_url, undefined, "PUT", _body, _content_type, _security, _callback, _GMFUNCTION_);
}
Internally, when an ElementsRequest is created:
if (!is_undefined(_body)) {
_body = _process_body(_body, _content_type, _where);
if (!is_string(_body)) {
__.body_type = 1; // buffer
__.body = buffer_base64_encode(_body, 0, -1);
} else {
__.body = _body;
}
}
So:
-
_content_typecontrols: -
The
Content-Typeheader. - Which converter function is used to turn your struct/array into a wire format.
5.2. Body converters
Converters are registered globally:
_elements_request_body_set_converter("application/json", function(value) {
return json_encode(value);
});
At send time, _process_body does:
var _body_converter = _elements_request_body_get_converter(_content_type);
if (!is_callable(_body_converter)) {
show_error(_where + " :: No converter for '" + _content_type + "'.", true);
}
_body = _body_converter(_body); // must be string or buffer
This means:
-
For application/json:
-
You’re expected to have a converter that
json_encodes your struct/array. -
For other content types:
-
You can define your own converters (e.g. multipart, binary, etc.).
-
If you pass a content type that has no converter registered:
-
The request hard-errors (
show_error).
5.3. Example: overriding content_type
For the product bundle endpoint:
var bundles = [ /* array of ElementsProductBundle structs */ ];
// Let’s say your converter expects JSON when content_type is "application/json"
elements_update_product_bundle_for_application_configuration(
"my-app",
"my-config",
bundles,
"application/json",
function(_code, _data, _request) { ... }
);
If you pass "*/*" and no converter is registered for "*/*", you’ll get an error. So in practice, you nearly always want "application/json" unless you’ve set up something custom.
6. Using the schema structs
The ElementsXXX constructors (ElementsItem, ElementsUser, ElementsRewardIssuance, etc.) are data containers. Each of them has a corresponding ElementsXXX_validate that can be used for validation.
Example:
var _user = new ElementsUser("user-id", "USER", "username");
var _item = new ElementsItem("sword", "Sword of Testing", "A test sword.", "FUNGIBLE");
var _issuance = new ElementsRewardIssuance(
"reward-id",
_user,
"ISSUED",
"my-context",
"NON_PERSISTENT",
_item,
10
);
// Before sending:
ElementsRewardIssuance_validate(_issuance, _GMFUNCTION_); // endpoints do this for you
Key points:
ElementsXXX_validate()checks all fields and willshow_errorif something is wrong (wrong type, missing required fields).
The user of the API mostly needs to know:
- You don’t need to call
ElementsXXX_validate()yourself – the endpoint does it – but you can call it early to fail fast.
7. Retrying requests
ElementsRequest tracks how many times it was sent:
attempts = 0;
static retry = function() { return send(); }
- The
send()method incrementsattemptseach time it’s called. - If you keep a reference to
_request(e.g. via_requestin your callback), you can manually call_request.retry()to resend it. - This is useful if you want a per-request retry after a specific error, rather than a global hook.
8. Putting it all together – typical usage flow
- Login / acquire tokens
elements_create_username_password_session(_session_request, function(_code, _data, _request) {
if (_code == 200) {
var _session_secret = _data.sessionSecret;
_elements_request_auth_set_token("auth_bearer", _session_secret);
_elements_request_auth_set_token("session_secret", _session_secret);
}
});
- Optionally set global response hooks
_elements_request_response_set_hook(401, function(_code, _data, _request) {
show_debug_message("Unauthorized, redirecting to login...");
// change room, clear saved session, whatever
return true; // don’t call the per-request callback
});
- Call other endpoints normally
elements_get_application_profiles("my-app", 0, 20, "", function(_code, _data, _request) {
if (_code == 200) {
// use _data (already JSON-parsed)
} else {
// handle error
}
});
- When sending bodies, use the right struct types + content_type
var _receipt = new ElementsCreateAppleIapReceipt(receipt_base64, "PRODUCTION");
elements_upload_apple_iap_receipt(_receipt, function(_code, _data, _request) {
// ...
});