Review, the catalogue and functions
After you draft a public app on your dev tenant, three steps make it installable by other ARC organizations: Growmax review, publishing to the public catalogue, and the install itself. A public app can also ship functions: small pieces of JavaScript that ARC runs inside its own sandbox to change what buyers see at checkout.
1. Ship a function (optional)
A function is declared in the manifest and its source travels in the version's package. Today there is one
hook, delivery.options.filter. It may hide or move the delivery options a buyer is offered on the
customer portal and the buyer app. It never sees or changes a fee, and staff screens are never filtered.
{
"name": "Courier filter",
"version": "1.0.0",
"apiVersion": "2027-01",
"appUrl": "https://filter.example.com",
"redirectUrls": ["https://filter.example.com/oauth/callback"],
"scopes": ["read_orders", "write_functions"],
"functions": [
{
"hook": "delivery.options.filter",
"handle": "hide-slow",
"path": "functions/hide-slow.js",
"inputContractVersion": 1
}
]
}
The package for that version (publishPublicAppVersion, package argument):
{
"format": "arc-h3-package",
"formatVersion": 1,
"files": {
"functions/hide-slow.js": "function run(input) { return { operations: input.options.filter(function (o) { return o.carrierName === \"Slow Courier\"; }).map(function (o) { return { op: \"hide\", optionId: o.optionId }; }) }; }"
}
}
- A function defines
run(input)and returns{ operations: [...] }:{ op: "hide", optionId }or{ op: "move", optionId, beforeOptionId }(beforeOptionId: nullmoves it to the end). - The app must ask for
write_functionsand the hook's own read scope (read_orders). The consent screen tells the admin plainly that the app "changes your checkout". - One function per hook and per handle, at most four per version. Every function must compile when you publish the version, or the version is refused.
- Limits. Each call gets a 5 ms CPU budget, 50 ms wall clock, 10 MB of memory and 64 KB of input. A function that runs out of time, throws, or returns something the hook does not accept contributes nothing: buyers see ARC's own list, in ARC's order. A function that hides every option is ignored the same way, so a buyer is never left with no delivery. Five failures in a row pause that installation's functions for a while (a circuit breaker).
2. Run the checks and submit
On your dev tenant, check a version without submitting it, then submit it. Give a reason for every scope the version asks for (at least 20 characters each), and confirm that the app never hands an ARC token to an AI model or agent.
query Checks($input: SubmitAppForReviewInputGql!) {
appReviewChecks(input: $input) { code passed reasons }
}
mutation Submit($input: SubmitAppForReviewInputGql!) {
submitAppForReview(input: $input) { status submittedVersionId }
}
{
"input": {
"appId": "<app id>",
"versionId": "<version id>",
"attestNoLlmTokens": true,
"scopeJustifications": [
{ "scope": "read_orders", "justification": "The delivery.options.filter hook reads the options on an order." },
{ "scope": "write_functions", "justification": "Runs one function that hides slow couriers from buyers." }
]
}
}
A submission is refused, with each failing check's reasons, unless every check passes:
| Check | What it asks |
|---|---|
MANIFEST_INVALID | The stored manifest and package are still valid and agree with the version. |
APP_FORBIDDEN_SCOPE | Every scope exists, is available to apps today, and grants nothing apps may never hold. |
SCOPE_JUSTIFICATION_MISSING | Every requested scope has a reason. |
FUNCTION_CONTRACT_FAILED | Every function compiles and, on ARC's own sample inputs, returns operations the hook accepts. |
CONFORMANCE_FAILED | The API version is supported, and appUrl and the webhook endpoint use HTTPS. |
LISTING_URL_INVALID | The support and privacy URLs are HTTPS and on the app's own domain (the host of appUrl). |
DPA_NOT_ACCEPTED | The listing carries the current developer agreement. |
NO_LLM_TOKEN_NOT_ATTESTED | You confirmed the no-AI-token rule. |
The sample inputs cannot catch everything. A function that only misbehaves on real checkouts still passes review, and the runtime limits above are what protect buyers from it.
3. Review
A Growmax reviewer reads the submission: the listing, who accepted the developer agreement, the scopes and
reasons, each function's hook and source hash, and the checks run again. They either approve the version or
send it back with notes (CHANGES_REQUESTED). Only Growmax staff can review, and never an app of their own
developer account.
An approved version becomes the reviewed version. It is the only version other organizations can install, and its redirect URLs become the only ones your OAuth client accepts.
4. Publish
mutation { publishAppListing(appId: "<app id>") { status publishedAt } }
The app now appears in the public catalogue of every organization whose plan includes the app marketplace
(off by default; Growmax switches it on per organization). Admins find it in Settings → Apps → Browse
(appCatalogue, appCatalogueApp).
delistAppListing takes it out of the catalogue again. Nobody new can install it, and installs that are
already running keep their version and keep working.
5. Install
An admin of another organization installs the app through your OAuth connect flow, exactly like a private
app (resource=https://<tenant host>/api/apps). The install pins your reviewed version, never a newer one,
and each of its functions is switched on straight away. The admin can switch a function off on the app's
installation page.
A newer version that asks for more scopes or changes a function waits for review before any installation is offered it.
Not covered here
- Billing for paid apps.
- Hooks other than
delivery.options.filter.