Core API Reference
Complete reference for all types, methods, status codes, and function pointers available to modules. Include one header: portal/portal.h
Single header. Everything a module needs is exposed through portal/portal.h. The core API is intentionally small — every function pointer in portal_core_t exists because a module cannot do its job without it.
1. Types
portal_header_t — Key-Value Pair
The fundamental unit for message metadata. Headers carry auxiliary information alongside the body.
typedef struct {
char *key;
char *value;
} portal_header_t;
portal_labels_t — Label Set
Labels control access to paths. A path can require one or more labels; only authenticated users (or modules) whose token carries matching labels can reach it.
typedef struct {
char labels[PORTAL_MAX_LABELS][PORTAL_MAX_LABEL_LEN];
int count;
} portal_labels_t;
| Function | Signature | Description |
portal_labels_add | (portal_labels_t *set, const char *label) | Add a label to the set |
portal_labels_remove | (portal_labels_t *set, const char *label) | Remove a label from the set |
portal_labels_has | (portal_labels_t *set, const char *label) | Check if label exists in set |
portal_labels_intersects | (portal_labels_t *a, portal_labels_t *b) | Check if two sets share any label |
portal_labels_clear | (portal_labels_t *set) | Remove all labels from the set |
portal_msg_t — The Universal Message
Every request, command, event, and subscription flows through this single structure. This is Law 2 in code.
typedef struct {
uint64_t id;
char *path;
uint8_t method;
uint16_t header_count;
portal_header_t *headers;
void *body;
size_t body_len;
portal_ctx_t *ctx; // auth + trace
} portal_msg_t;
| Function | Signature | Description |
portal_msg_alloc | (void) | Allocate a new empty message |
portal_msg_free | (portal_msg_t *msg) | Free message and all owned memory |
portal_msg_set_path | (portal_msg_t *msg, const char *path) | Set the target path |
portal_msg_set_method | (portal_msg_t *msg, uint8_t method) | Set the method (GET, SET, CALL...) |
portal_msg_set_body | (portal_msg_t *msg, const void *data, size_t len) | Set the body (copies data) |
portal_msg_add_header | (portal_msg_t *msg, const char *key, const char *value) | Append a key-value header |
portal_resp_t — Response
Returned by synchronous send calls. Carries a status code, optional headers, and a body.
typedef struct {
uint16_t status;
uint16_t header_count;
portal_header_t *headers;
void *body;
size_t body_len;
} portal_resp_t;
| Function | Signature | Description |
portal_resp_alloc | (void) | Allocate a new empty response |
portal_resp_free | (portal_resp_t *resp) | Free response and all owned memory |
portal_resp_set_status | (portal_resp_t *resp, uint16_t status) | Set the status code |
portal_resp_set_body | (portal_resp_t *resp, const void *data, size_t len) | Set the response body (copies data) |
portal_ctx_t — Message Context
Attached to every message. Carries authentication credentials and distributed tracing information. The core populates this automatically — modules read it but never forge it.
typedef struct {
portal_auth_t auth; // user + token + labels
portal_trace_t trace; // trace_id, parent_id, timestamp_us, hops
char *source_node;
char *source_module;
} portal_ctx_t;
2. Methods
Seven methods cover every possible interaction pattern. Each message carries exactly one.
| Constant | Value | Description |
PORTAL_METHOD_GET | 0x01 | Read a resource, no side effects |
PORTAL_METHOD_SET | 0x02 | Create or update a resource |
PORTAL_METHOD_CALL | 0x03 | Execute an action |
PORTAL_METHOD_EVENT | 0x04 | Fire and forget notification |
PORTAL_METHOD_SUB | 0x05 | Subscribe to events on a path |
PORTAL_METHOD_UNSUB | 0x06 | Unsubscribe from events |
PORTAL_METHOD_META | 0x07 | Query path metadata (access mode, labels, owner) |
3. Status Codes
Inspired by HTTP but reduced to the essentials. Every response carries exactly one status code.
| Code | Constant | Meaning |
200 | PORTAL_OK | Success |
201 | PORTAL_CREATED | Resource created |
202 | PORTAL_ACCEPTED | Request accepted, processing async |
400 | PORTAL_BAD_REQUEST | Malformed message or invalid parameters |
401 | PORTAL_UNAUTHORIZED | Authentication required or token invalid |
403 | PORTAL_FORBIDDEN | Authenticated but insufficient labels |
404 | PORTAL_NOT_FOUND | Path does not exist |
409 | PORTAL_CONFLICT | Resource locked or version conflict |
500 | PORTAL_INTERNAL_ERROR | Module-side failure |
503 | PORTAL_UNAVAILABLE | Module not loaded or unreachable node |
4. Core API (portal_core_t)
Every module receives a portal_core_t pointer on load. This is the module's only interface to the system. All interaction with the core, other modules, events, storage, and configuration goes through these function pointers.
Law 3 in practice. The core does nothing — but it gives modules everything they need. Every function pointer below exists because a module cannot do its job without it.
Path Management
| Function Pointer | Signature | Description |
path_register | (core, const char *path, const char *module_name) | Register a path owned by this module |
path_unregister | (core, const char *path) | Release a previously registered path |
path_set_access | (core, const char *path, uint8_t mode) | Set access mode: READ, WRITE, or RW (Law 8) |
path_add_label | (core, const char *path, const char *label) | Require this label to access the path |
path_remove_label | (core, const char *path, const char *label) | Remove a label restriction from the path |
Message Routing
| Function Pointer | Signature | Description |
send | (core, portal_msg_t *msg, portal_resp_t *resp) | Send a message to any path, local or remote. Blocks until response. |
Module Queries
| Function Pointer | Signature | Description |
module_loaded | (core, const char *name) | Check if a module is currently loaded and available |
Event Loop
| Function Pointer | Signature | Description |
fd_add | (core, int fd, uint32_t events, callback, userdata) | Register a file descriptor for async I/O (Law 13) |
fd_del | (core, int fd) | Remove a file descriptor from the event loop |
timer_add | (core, uint64_t interval, callback, userdata) | Register a periodic timer (interval in milliseconds) |
Events
| Function Pointer | Signature | Description |
event_register | (core, const char *path, const char *description, portal_labels_t *labels) | Declare an event that this module can emit |
event_unregister | (core, const char *path) | Remove an event declaration |
event_emit | (core, const char *path, const void *data, size_t len) | Fire an event to all subscribers (Law 10) |
Pub/Sub
| Function Pointer | Signature | Description |
subscribe | (core, const char *pattern, handler, userdata) | Subscribe to events matching a path pattern |
unsubscribe | (core, const char *pattern, handler) | Unsubscribe a previously registered handler |
Storage
| Function Pointer | Signature | Description |
storage_register | (core, portal_storage_provider_t *provider) | Register a storage backend (key-value, SQL, etc.) |
Config
| Function Pointer | Signature | Description |
config_get | (core, const char *module, const char *key) | Read a configuration value for a module (Law 11) |
Logging
| Function Pointer | Signature | Description |
log | (core, uint8_t level, const char *module, const char *fmt, ...) | Write a log entry at the specified level |
Log levels:
| Constant | Value | Use |
PORTAL_LOG_ERROR | 0 | Failures that require immediate attention |
PORTAL_LOG_WARN | 1 | Unexpected conditions that are recoverable |
PORTAL_LOG_INFO | 2 | Normal operational messages |
PORTAL_LOG_DEBUG | 3 | Detailed diagnostic information |
PORTAL_LOG_TRACE | 4 | Per-message tracing, high volume |
Resource Locking
Physical resources (serial ports, GPIO, IoT devices) require exclusive access. These functions implement Law 14 — auto-lock on first write, keepalive, auto-release after inactivity.
| Function Pointer | Signature | Description |
resource_lock | (core, const char *resource, const char *owner) | Acquire exclusive lock on a resource |
resource_unlock | (core, const char *resource, const char *owner) | Release a held lock |
resource_keepalive | (core, const char *resource, const char *owner) | Reset the 60-second inactivity timer |
resource_locked | (core, const char *resource) | Check if a resource is currently locked |
resource_owner | (core, const char *resource) | Return the current lock owner (or NULL) |
5. Constants
Limits
| Constant | Value | Description |
PORTAL_MAX_PATH_LEN | 1024 | Maximum path length in bytes |
PORTAL_MAX_MODULE_NAME | 64 | Maximum module name length |
PORTAL_MAX_MODULES | 256 | Maximum modules loaded simultaneously |
PORTAL_MAX_HEADERS | 32 | Maximum headers per message |
PORTAL_MAX_EVENTS | 64 | Maximum events per module |
PORTAL_MAX_LABELS | 32 | Maximum labels per set |
PORTAL_MAX_LABEL_LEN | 64 | Maximum label string length |
Access Modes
| Constant | Value | Description |
PORTAL_ACCESS_READ | 0x01 | Path accepts GET and META only |
PORTAL_ACCESS_WRITE | 0x02 | Path accepts SET and CALL only |
PORTAL_ACCESS_RW | 0x03 | Path accepts all methods |
6. Internal Core Paths
The core registers these paths on startup. They are always available, even with no modules loaded.
/core — Core Status
| Path | Method | Description |
/core/status | GET | Returns uptime, version, loaded module count |
/core/modules | GET | List all loaded modules and their state |
/core/modules/{name} | GET | Details of a specific module |
/core/modules/{name}/load | CALL | Load a module at runtime (Law 5) |
/core/modules/{name}/unload | CALL | Unload a module at runtime |
/core/modules/{name}/reload | CALL | Reload a module (unload + load) |
/core/paths | GET | List all registered paths with access modes |
/core/config/{module}/{key} | GET | Read a module configuration value |
/core/config/{module}/{key} | SET | Update a module configuration value |
/core/log/level | GET | Current global log level |
/core/log/level | SET | Change global log level at runtime |
/auth — Authentication
| Path | Method | Description |
/auth/login | CALL | Authenticate with user + password, returns token |
/auth/logout | CALL | Invalidate current token |
/auth/verify | CALL | Verify a token and return its labels |
/users — User Management
| Path | Method | Description |
/users | GET | List all users |
/users/{id} | GET | Get user details |
/users/{id} | SET | Create or update a user |
/users/{id}/labels | GET | Get user label set |
/users/{id}/labels | SET | Replace user label set |
/groups — Group Management
| Path | Method | Description |
/groups | GET | List all groups |
/groups/{id} | GET | Get group details and member list |
/groups/{id} | SET | Create or update a group |
/groups/{id}/members | GET | List group members |
/groups/{id}/members | SET | Add or remove members |
/events — Event Registry
| Path | Method | Description |
/events | GET | List all registered events |
/events/{path} | GET | Get event description, labels, and subscriber count |
/events/{path} | SUB | Subscribe to an event |
/events/{path} | UNSUB | Unsubscribe from an event |
7. Communication Patterns
Request / Response
The standard synchronous pattern. A module sends a message and blocks until the target module responds. Used for GET, SET, CALL, and META.
portal_msg_t *msg = portal_msg_alloc();
portal_resp_t *resp = portal_resp_alloc();
portal_msg_set_path(msg, "/local/db/users/1");
portal_msg_set_method(msg, PORTAL_METHOD_GET);
int rc = core->send(core, msg, resp);
if (rc == PORTAL_OK && resp->status == PORTAL_OK) {
// resp->body contains the user record
process_user(resp->body, resp->body_len);
}
portal_msg_free(msg);
portal_resp_free(resp);
Fire and Forget (Events)
An event producer emits data to a path. All subscribers receive it asynchronously. The producer does not wait and does not know who (or if anyone) received it. Used with PORTAL_METHOD_EVENT.
// Producer: emit an event
const char *payload = "{\"temp\":22.5,\"unit\":\"C\"}";
core->event_emit(core, "/local/iot/sensor/temperature",
payload, strlen(payload));
// Consumer: subscribe to events (registered once, usually in module_load)
static void on_temperature(const char *path,
const void *data, size_t len,
void *userdata) {
core->log(core, PORTAL_LOG_INFO, "dashboard",
"temperature update: %.*s", (int)len, (const char *)data);
}
core->subscribe(core, "/local/iot/sensor/*", on_temperature, NULL);
Pattern or exact match. The subscribe function accepts glob patterns. /local/iot/sensor/* matches any event under that prefix. Exact paths also work.