Skip to content

App API Protocol — Infra & App Teams ​

Updated: 2026-09-17. Contract checked against the current repository implementation.

Scope: App-facing startup APIs and Infra access requirements. The App uses one prod System Admin entry by default and a dev System Admin entry when developer mode is enabled. Staging is a server deployment environment; it is not a third App entry.

Status: Native v2, Bundle v2, and Backend Discovery v1 APIs are implemented in the repository. The System Admin contract no longer has a production/beta channel dimension. Bundle candidates are discovered from version folders containing bundle.zip; manifest.json is optional and ignored.

1. Architecture and startup order ​

The Capacitor App has two independent version identifiers:

  • Native: the iOS/Android Store package's increasing build integer. Sysadmin publishes a minimum supported build per platform.
  • Bundle: the running JS/assets package's immutable version identifier. Sysadmin publishes the selected shared ZIP and its download URL.

All organizations use the standard publication for the requested appId and platform. There is no organization-specific Bundle selection in this contract.

On each native launch the App must first confirm/apply any Capacitor Updater pending Bundle, then proceed to the remote checks. The full order is: pending Bundle → Native minimum → Bundle resolve → hospital backend discovery → login.

The App connects directly to the published download URL and selected business backend. Sysadmin does not proxy these requests. The App team owns updater integration, application/restart timing, readiness confirmation, recovery, and Store navigation.

2. Base URL and public access ​

The App team stores the prod and dev System Admin base URLs and switches between them with developer mode. The server's process NODE_ENV controls URL transport policy:

NODE_ENVApp backend / Native update / Bundle download
productionHTTPS only
development, test, or stagingHTTP or HTTPS

The URL policy follows the process mode even when the deployment is described as dev or staging. HTTP remains subject to public-DNS, no-credentials, no-port, no-query, no-fragment, redirect, timeout, and destination-safety checks. The App must separately allow cleartext HTTP when a non-production URL uses it.

Infra exposes POST and OPTIONS for these exact paths:

text
/api/public/v2/app-versions/check
/api/public/v2/app-bundles/resolve
/api/public/v1/backend-discovery/resolve
  • Use Content-Type: application/json; do not send Authorization or cookies. Browser/WebView requests use credentials: "omit".
  • Configure the actual Web/WebView origins used by the App. Do not assume the download hostname is the request origin.
  • Do not add query parameters or trailing slashes to these API paths.
  • Keep administrator pages and APIs restricted separately.
  • Native/Bundle v2 accept only ios and android. Do not send platform: "web".

3. Check Native minimum version ​

POST {baseUrl}/api/public/v2/app-versions/check

Request ​

json
{
  "appId": "hereby-works",
  "platform": "ios",
  "nativeReleaseNumber": 110
}
FieldContract
appIdConfigured app identifier, currently hereby-works
platformios or android
nativeReleaseNumberPositive integer, maximum 2147483647; embedded in the installed Native package

App/CI must agree on a monotonically increasing Native integer for each platform. This is not the display version, Git SHA, or Bundle version.

Response — 200 ​

json
{
  "result": "allowed",
  "publicationRevision": 8,
  "minimumNativeReleaseNumber": 110
}

update_required blocks normal use and directs the user to update the Store package. allowed continues to Bundle resolve. A missing policy is 404 NATIVE_POLICY_NOT_FOUND; it must not be treated as allowed.

4. Resolve the published Bundle ​

POST {baseUrl}/api/public/v2/app-bundles/resolve

Request ​

json
{
  "appId": "hereby-works",
  "platform": "ios",
  "nativeReleaseNumber": 110,
  "currentBundleVersion": "1.2.2"
}

currentBundleVersion is required but nullable: send the identifier embedded in the Bundle actually running, or null if unavailable. Do not send a downloaded-but-not-applied version.

Bundle versions are strings of 1–64 characters matching ^[A-Za-z0-9][A-Za-z0-9._+-]*$. Release tags such as 1.2.3 and short Git SHAs such as 332dc10 are accepted.

Response — update available, 200 ​

json
{
  "result": "update_available",
  "publicationRevision": 8,
  "minimumNativeReleaseNumber": 110,
  "bundle": {
    "version": "332dc10",
    "downloadUrl": "https://mobile.dev.hereby.me/releases/332dc10/bundle.zip",
    "checksumAlgorithm": "sha256",
    "checksum": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "sizeBytes": 12000000,
    "releaseNotes": "안정성을 개선했어요."
  }
}

up_to_date includes the published Bundle object and requires no download. native_update_required includes bundle: null and starts the Native Store flow. A different identifier or null produces update_available; the service compares identifiers by equality rather than SemVer ordering.

The server returns SHA-256 hex and byte size for the shared ZIP. The App must verify the artifact, apply it with Capacitor Updater, restart, and confirm readiness. A missing target is 404 BUNDLE_POLICY_NOT_FOUND; it is not up_to_date.

The App calls notifyAppReady immediately on every native launch so Capacitor Updater can confirm that JavaScript loaded. The running Bundle version sent to this endpoint always comes from CapacitorUpdater.current(), never from local storage. After a successful set(), the updater reloads the App into that Bundle; the next launch only confirms current() and does not reload again.

Public lookup returns the published snapshot, not the newest object currently in S3. Uploading a ZIP alone does not change App responses.

5. Resolve the hospital business backend ​

POST {baseUrl}/api/public/v1/backend-discovery/resolve

Request ​

json
{ "code": "OKBEST" }

The hospital code is trimmed and normalized to uppercase: 3–32 letters, digits, or hyphens.

Response — 200, illustrative ​

json
{
  "code": "OKBEST",
  "displayName": "테스트 병원",
  "endpointId": "11111111-1111-4111-8111-111111111111",
  "backendInstanceId": "approved-instance-id",
  "apiBaseUrl": "https://api.customer.example",
  "mappingVersion": 1,
  "publicationRevision": 1
}

Confirm the hospital name with the user, then set the business SDK base URL to apiBaseUrl. Do not append /trpc; the SDK handles it. Never send a token saved for another backend to this target.

6. Errors and failure handling ​

Application errors use:

json
{
  "code": "BUNDLE_POLICY_NOT_FOUND",
  "message": "게시된 Bundle 정책이 없어요.",
  "requestId": "server-generated-request-id"
}
HTTP statusMeaning
400Invalid or missing JSON request; schemas reject unknown fields
403Origin or preflight denied
404Unknown path or unpublished policy
413 / 415Request too large / unsupported content type
429Rate limit; respect Retry-After
500 / 503Server error / temporary unavailability

Responses use Cache-Control: no-store; immutable versioned ZIP files may use normal CDN caching. A network or proxy error is neither a mandatory-update result nor proof that the App is up to date. The App team must document retry/offline/fallback behavior before release.

7. Acceptance checklist ​

Before declaring an environment ready:

  • Infra confirms the deployed System Admin base URLs, public routes, CORS, and reachability of the returned download URLs.
  • Admin confirms one app-level source and publication with the intended iOS/Android minimums and shared bundle.zip candidate.
  • App confirms the Native integer mapping, running Bundle identifier, and verification of returned checksum and size.
  • App tests Native blocking, unchanged Bundle, different Bundle, immediate application/restart, readiness confirmation, failure recovery, environment switching, and hospital discovery/login on real iOS and Android devices.
  • Record unresolved acceptance gaps explicitly. A successful Docker build alone is not end-to-end acceptance.