Skip to main content

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: null moves it to the end).
  • The app must ask for write_functions and 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:

CheckWhat it asks
MANIFEST_INVALIDThe stored manifest and package are still valid and agree with the version.
APP_FORBIDDEN_SCOPEEvery scope exists, is available to apps today, and grants nothing apps may never hold.
SCOPE_JUSTIFICATION_MISSINGEvery requested scope has a reason.
FUNCTION_CONTRACT_FAILEDEvery function compiles and, on ARC's own sample inputs, returns operations the hook accepts.
CONFORMANCE_FAILEDThe API version is supported, and appUrl and the webhook endpoint use HTTPS.
LISTING_URL_INVALIDThe support and privacy URLs are HTTPS and on the app's own domain (the host of appUrl).
DPA_NOT_ACCEPTEDThe listing carries the current developer agreement.
NO_LLM_TOKEN_NOT_ATTESTEDYou 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.