The mobile application begins the conversation by sending the first packet. This will contain the function name "setConnection" along with some detail about the device being used.
{
"method": "setConnection",
"bundleID": "xpedite.software",
"uuid": "mobile phone UUID",
"bounds": "screen dimensions in logical pixels",
"xpediteVersion": 1
}
When receiving a packet we recommend extracting the "method" entry — it identifies the local
function you should run. In this first packet the method will always be "setConnection".
Create a function called setConnection, call it, and pass the array. The setConnection
function typically initialises a session by creating a GUID which will be included in all
successive packets as the "token".
NOTE: The mobile app can remain running long after your web server session
expires, so implement your own session management using the GUID you have created. It will
be included in every packet you receive as the "token" entry.
The packet you send back must include at least: a return code, a version number, the token, and a components entry indicating what to display next.
{
"rc": 0,
"version": 1,
"token": "08829e57-56ec-42a8-8b84-061981c054c8",
"components": [{"type": "label", "text": "Hello world"}]
}
Every response you send must include rc and components.
The token is required after the initial setConnection exchange.
{
"rc": 0,
"token": "08829e57-56ec-42a8-8b84-061981c054c8",
"components": [{"type": "label", "text": "Hello world"}]
}
Every packet you receive will include the method and the token.
{
"method": "addCustomer",
"token": "08829e57-56ec-42a8-8b84-061981c054c8"
}
Each response renders a completely fresh screen. All components listed in the
components array are stacked top-to-bottom in the order they appear.
0 indicates success.
Any other integer causes an error dialog to be displayed.
{"rc": 0}
{"token": "08829e57-56ec-42a8-8b84-061981c054c8"}
{"version": 1}
{"title": "Customer List"}
{"background": "#F5F5F5"}
disposition for page caching.
Also used as a duplicate-component guard within a single page.
{"id": "customer-list"}
"permanent" to cache indefinitely,
or an expiry dict to cache for a fixed number of seconds. Requires id.
When the server is unreachable, a cached page is served automatically.
{"disposition": "permanent"}
{"disposition": {"type": "expiry", "seconds": 3600}}
{
"setDefaults": {
"font": "Helvetica",
"fontsize": 16,
"background": "#FFFFFF"
}
}
id (used to reference the image in components), a url
(HTTPS), and optionally a load priority.
"load": "critical" — downloaded before the screen renders."load": "background" — downloaded after rendering (default).
{
"images": [
{"id": "logo", "url": "https://my.app/images/logo.png", "load": "critical"},
{"id": "banner", "url": "https://my.app/images/banner.png", "load": "background"}
]
}
{
"components": [
{"type": "label", "text": "Welcome"},
{"type": "button", "text": "Continue", "method": {"function": "home"}}
]
}
Buttons, table rows, and navigation entries all call the server using the same method dict. Every method call renders a completely fresh screen by default.
{"function": "listCustomers"}
{"function": "viewCustomer", "parameters": {"id": "123"}}
Full method dict example:
{
"function": "viewCustomer",
"parameters": {"id": "123"}
}
Most components accept a "size" dict. Omitting it uses a sensible default
(typically full width). Components are stacked top-to-bottom automatically; the layout
engine tracks the bottom edge of each placed component.
{"size": {"width%": 80, "height": 44, "centrex": 0}}
{"size": {"x+": 10, "width%": 50, "relative-height%": 100}}
text (required), color, background,
font, fontsize, align, size, id.
{
"type": "label",
"text": "Hello world",
"color": "#333333",
"fontsize": 20,
"align": "centre",
"size": {"width%": 100, "height": 44}
}
id to reference a preloaded image, or
url to download one at render time. If only width is specified,
height is derived from the image aspect ratio.
{"type": "image", "id": "logo", "size": {"width%": 50}}
{"type": "image", "url": "https://...", "size": {"width%": 100, "height": 200}}
method.
If the method dict contains "formdata": true, the current screen's
captured input is sent with the call.
text, style, color,
background, font, fontsize,
align, icon, method, size, id.
"style": "plain" — text only (default)."style": "button" — bordered, rounded corners.
{
"type": "button",
"text": "Save",
"style": "button",
"size": {"width%": 60, "height": 44, "centrex": 0},
"method": {"function": "save", "navigation": "root"}
}
name key. Can also be used standalone on a screen.
name (required), title, placeholder,
value, keyboard, returnkey,
onEnter, autotext, color,
font, fontsize, align,
focus, size, id.
{
"type": "textbox",
"name": "email",
"placeholder": "you@example.com",
"keyboard": "email",
"returnkey": "next"
}
{"type": "password", "name": "pwd", "placeholder": "Enter password"}
components
array. If height is omitted the scroll view sizes itself to fit its content.
components, background, size, id.
{
"type": "scrollview",
"size": {"width%": 100, "height%": 60},
"components": [{"type": "label", "text": "Long content..."}]
}
web (URL), content (HTML string),
background, size, id.
{"type": "web", "web": "https://example.com", "size": {"width%": 100, "height%": 80}}
{"type": "web", "content": "<h1>Hello</h1>", "size": {"width%": 100, "height": 400}}
data (required), cell, header,
clickRow, refresh, background,
size, id.
{
"type": "table",
"data": {"dynamic": "listCustomers"},
"refresh": "Pull to refresh",
"header": [
{"text": "Name", "width%": 60, "font": "headline"},
{"text": "Score", "width%": 40, "align": "right", "scroll": "off"}
],
"cell": [
{"field": "name", "width%": 60},
{"field": "score", "width%": 40, "align": "right", "color": "#007AFF"}
],
"clickRow": {"function": "viewCustomer", "selected": "id"}
}
cell / header column keys:
field — data field name from each row record (cell only).text — static label text (header only).width% — column width as a percentage of table width. Omit to share remaining width equally.align — left | centre | right. Default: left.font — text style name (headline, body, footnote, etc.) or a font name.fontsize — point size override (integer).color — text colour (hex).lines — number of lines; 0 = unlimited. Default: 1.scroll — "on" (header scrolls with data, default) | "off" (header fixed above table). All header entries must agree.Data response (from the dynamic function):
{"rc": 0, "data": [{"name": "Alice", "score": 42}, ...], "max": 50}
data — array of row records.max — include when returning a partial batch (more rows available). Omit on the final batch.clickRow sends the selected row's field value to the server:
{"function": "viewCustomer", "selected": "id"}
components array. On save, all captured field values are sent to the
server under formdata.{formId}.
style, title, save,
saveNavigation, cancel, cancelNavigation,
components, size, id (required for save data).
"style": "inline" — optional title bar + Cancel/Save footer buttons."style": "dialog" — modal style with ✕ (cancel) and ✓ (save) in the header bar.
{
"type": "form",
"id": "customer-form",
"style": "inline",
"title": "Add Customer",
"save": {"function": "saveCustomer"},
"saveNavigation": "Save",
"cancel": {"function": "home"},
"cancelNavigation": "Cancel",
"components": [...]
}
Form field types (used inside a form's components array):
section — creates a section header row:
{"type": "section", "title": "Personal Details"}
textbox — single-line text entry:
{"type": "textbox", "name": "firstname", "title": "First name", "placeholder": "Enter name", "value": "Alice"}
password — masked text entry:
{"type": "password", "name": "pwd", "title": "Password"}
textview — multi-line text entry:
{"type": "textview", "name": "notes", "title": "Notes", "placeholder": "Enter notes..."}
checkbox — boolean toggle:
{"type": "checkbox", "name": "active", "title": "Active customer", "value": "1"}
date — date picker. Value format: yyyy-MM-dd:
{"type": "date", "name": "dob", "title": "Date of birth", "value": "1990-06-15"}
select — picker wheel. Options must use string keys:
{
"type": "select",
"name": "country",
"title": "Country",
"value": "AU",
"options": {"AU": "Australia", "NZ": "New Zealand", "US": "United States"}
}
radio — vertical radio button group. Options must use string keys:
{
"type": "radio",
"name": "gender",
"title": "Gender",
"value": "M",
"options": {"M": "Male", "F": "Female", "X": "Other"}
}
implementation dict.
implementation (required), components,
effects, id.
implementation keys:
style — "button" | "slidefrom" (required).position — "left" | "right" (required).icon — SF Symbol name for the bar button, e.g. "ellipsis.circle", "plus".image — local image ID for the bar button (takes precedence over icon).style = "slidefrom" — edge swipe reveals a slide-in panel. Swipe back or tap the shade overlay to close:
{
"type": "navigation",
"id": "main-menu",
"implementation": {"style": "slidefrom", "position": "left"},
"effects": {"shade%": 40},
"components": [
{"type": "menuEntry", "text": "Customers", "icon": "customers", "method": {"function": "listCustomers"}},
{"type": "menuEntry"},
{"type": "menuEntry", "text": "Logout", "method": {"function": "logout"}}
]
}
style = "button" — adds a UIBarButtonItem. Single menuEntry calls the method directly; multiple entries show a dropdown menu:
{
"type": "navigation",
"implementation": {"style": "button", "position": "right", "icon": "ellipsis.circle"},
"components": [
{"type": "menuEntry", "text": "Add", "symbol": "plus", "method": {"function": "addCustomer"}},
{"type": "menuEntry", "text": "Search", "symbol": "magnifyingglass", "method": {"function": "search"}}
]
}
{
"type": "navigation",
"implementation": {"style": "button", "position": "right", "icon": "plus"},
"components": [
{"type": "menuEntry", "method": {"function": "addCustomer"}}
]
}
menuEntry keys:
text — label. With method: interactive entry. Without: bold section header.method — method dict; makes the entry tappable.icon — local image ID shown left of text (slidefrom panel only).symbol — SF Symbol shown beside the item (button dropdown only).effects:
shade% — 0–100. Semi-transparent overlay behind the panel (slidefrom only). Tapping it closes the panel.Caching by id: If a navigation component includes an id, its full definition is cached on the device. On subsequent pages send only {"type":"navigation","id":"main-menu"} to reload it from cache.
left, right, centre,
center, justify.
{"align": "left"}
"disable" to turn off autocorrect and
autocapitalise (useful for usernames, codes, etc.).
{"autotext": "disable"}
{"background": "#FFFFFF"}
{"color": "#000000"}
{"type": "textbox", "name": "search", "focus": true}
"Helvetica", "Georgia", "Courier".
See developer.apple.com/fonts for the full list.
{"font": "Helvetica-Bold"}
{"fontsize": 16}
{"id": "customer-name-field"}
email, number, phone, url, default.
{"keyboard": "email"}
{"name": "customer_name"}
id of the next field to focus when the
return key is tapped, enabling keyboard navigation between fields.
{"onEnter": "password-field"}
select and radio field types.
A flat dict of "key": "Display Label" pairs. Keys must be strings.
Options are sorted by key before display.
{"options": {"1": "Option A", "2": "Option B", "3": "Option C"}}
{"placeholder": "Enter the customer's first name"}
{"refresh": "Pull to refresh"}
next, done, go, search, default.
{"returnkey": "next"}
{"size": {"width%": 80, "height": 44, "centrex": 0}}
{"time": 1744531200}
{"title": "Customer Details"}
"yyyy-MM-dd", checkboxes use "1" / "0",
select and radio use the option key string.
{"value": "1990-06-15"}
{"save": {"function": "saveCustomer"},
"cancel": {"function": "home"}}
"Cancel" and "Save".
{"saveNavigation": "Confirm", "cancelNavigation": "Discard"}