# ORION OS integration guide

Contract reference: App SDK 1.6.0 / protocol v1; IDC Operations API v1.0.0. Clarified 21 October 2026. This guide documents ORION's supported interfaces, not every internal web route or another product's current capabilities. Release and installation evidence is recorded separately in the handover pack.

**ORION operates independently of HorizonOS.** IDC's contract lifecycle, Civitas settlement and HLN Group's industry/mission workflows remain in ORION. External adapters are optional. HorizonOS does not currently handle the contract, bidding, delivery or Dispatch functions described in API examples; no HorizonOS changes or game-log feed are required. Apex's public Atlas/Alerts APIs are separate services, and private Apex crew access would require its own separately agreed integration.

## Choose the correct connection

| Purpose | Interface | Authorization |
| --- | --- | --- |
| Install a browser app in ORION, follow appearance, open an approved native destination, share one selected file or create a private file | ORION App SDK | Reviewed manifest, personal installation, current OPE permissions and member approval for each action |
| Optionally connect an external service's supported capabilities to IDC requests, bids, delivery reports or status events | IDC Operations API | Registered organization Ed25519 public key plus an expiring delegation from an authorized member |
| Attach an actual Convoy, Dispatch or Materials record from ORION | Native IDC bridge | Local session, linked UCI identity, current OPE/source access, configured server client and the same member's delegation |
| Read published exchange summaries and public Starcomm advisories for a broadcast | Public financial feed | No private authorization; only the deliberately published projection |
| Use private Apex responder/dispatcher features | Apex Dispatch API | Separate Apex-audience player UCI token, manually registered client and current Apex operator grant |

These authorities are independent. A host display context is not identity proof. SDK installation never grants a server financial authority. IDC credentials cannot authorize Apex calls. No supported integration supplies database credentials or ORION session tokens to an app.

## 1. App SDK

Get the complete versioned [SDK ZIP](https://www.orionos.app/downloads/orion-app-sdk-1.6.0.zip) and its [SHA-256 file](https://www.orionos.app/downloads/orion-app-sdk-1.6.0.zip.sha256). The ZIP includes JavaScript, TypeScript declarations, manifest and submission schemas, vanilla/React starters, artwork tools, local preview, examples and tests. It is a downloadable/local package; do not assume an npm-registry package exists.

### Create and test

Use Node.js 20 or newer. From the extracted `orion-app-sdk` folder:

```sh
npm run dev
# Synthetic display host: http://localhost:4173
node bin/orion-app.mjs create ../orion-companion
npm install
node bin/orion-app.mjs bundle ../orion-companion
```

Replace example identity, artwork, support/privacy information and testing instructions before submitting. React projects must be built locally before packaging their output. The developer's local build does not execute on ORION's server.

### Connect to the host

Import from the SDK package in a bundler, or copy **all** `dist` files together and import `index.js` for plain browser JavaScript:

```js
import {createOrionClient} from './sdk/index.js';
const hostOrigin = window.__ORION_PACKAGE_HOST__ || 'https://www.orionos.app';
const client = createOrionClient(hostOrigin, {
  onStatus(status) { showConnectionStatus(status); },
  onContext(context) {
    applyTheme(context.theme);
    showOrganization(context.organization); // display only
  },
});
// Call client.disconnect() when the component unmounts or the app leaves.
```

The uploaded-package host supplies only its public origin through `__ORION_PACKAGE_HOST__`. Externally hosted apps must use developer-controlled exact-origin configuration. Never choose a trusted origin from arbitrary query parameters, referrers or incoming messages. `https://orionos.app` and `https://www.orionos.app` are different origins.

`connectOrionHost(origin, onContext, options?)` is the lower-level display-only connection. `createOrionClient` adds approved actions and draft lifecycle. Local `allowLocalhost` is for loopback development only. Connection states are `standalone`, `connecting`, `connected` and `unavailable`; allow a useful fallback when the host is absent.

Host context contains `channel: 'orion-app-sdk-v1'`, `type: 'orion:init'`, a launch `nonce`, `theme`, nullable public organization `{slug,name}`, advertised `capabilities`, and optional `lifecycleVersion: 1`. The nonce identifies a launch, not a user or credential. The library verifies exact parent frame, origin and pinned launch nonce. Use the library rather than recreating those checks.

### Methods, scopes and results

Declare only needed capabilities in the reviewed manifest. `theme` and `organization.public` provide display context. The six action methods are:

| Method | Capability | Parameters | Result |
| --- | --- | --- | --- |
| `profile.read` | `profile.basic` | `{}` | `{name, callsign}`; callsign may be null |
| `documents.open` | `documents.read.selected` | `{kind:'write'|'grid'|'present'}` | `{id,kind,title,body,revision}` for one owned file chosen in the host picker |
| `documents.create` | `documents.create` | `{kind,title,body}` | `{id,kind,title,revision}` for a newly created private file |
| `clipboard.write` | `clipboard.write` | `{text}` up to 20,000 characters | `{ok:true}` |
| `notifications.show` | `notifications.show` | `{text}` up to 280 characters | `{ok:true}`; notice stays in this app window |
| `shell.open` | `shell.open` | `{appId}` | `{ok:true}` after permission-checked navigation |

Allowed native destinations: `orion-write`, `orion-grid`, `orion-present`, `calendar`, `my-work`, `link-library`. There is no arbitrary app-navigation or URL-proxy method.

```js
// Run from an explicit member action, after connection:
try {
  const file = await client.request('documents.open', {kind:'present'});
  renderSlidesAsText(file.body.slides);
} catch (error) {
  showActionError(error.message); // decline, missing permission, expiry, timeout
}
```

One request may be pending at a time. The SDK waits up to 125 seconds and rejects pending requests on disconnect. A timed-out creation may have succeeded: inspect its outcome before deliberately trying again. Do not retry writes automatically.

Each host approval binds member, organization, app, reviewed manifest, launch, method and parameter digest; it expires after two minutes and is single-use. Server authority is checked again at execution. Limits are 12 prepared actions/minute and 200/day per member, organization and app; messages and file content are bounded to 300 KB. Unsupported-scope attempts and excess requests create review flags. OPE Admin is never inherited by an SDK app.

### Work Suite formats

Portable envelope: `{version:1,kind,title,body}`. Do not add ownership, permissions, database IDs or revisions to portable files.

| Kind | Body and limits |
| --- | --- |
| `write` | `{text:string}`, up to 200,000 characters |
| `grid` | `{cells:string[][]}`, rectangular 1–200 rows, 1–26 columns, up to 2,000 characters/cell |
| `present` | `{slides:[{id,title,body,notes}]}`, 1–100 slides; unique IDs up to 80 characters, titles 200, body/notes 8,000 each |

Document titles are 1–160 characters. Portable import limit is 2 MB; the bridge's smaller message bound still applies to SDK actions. Render content as text; never execute spreadsheet cells or embedded scripts. Presenter notes can be private: explicitly remove them from student/public exports. ORION Present portable files are not PowerPoint `.pptx` files. The SDK cannot overwrite/delete existing documents or alter sharing.

### Draft lifecycle

Call `client.setDraft(true)` on changes and `false` only after confirmed saving. It returns whether the host supports draft protection (`lifecycleVersion:1`). A supported close dialog offers Save, Don't Save or Cancel; the host waits up to ten seconds for a save acknowledgment.

An optional `onSave` callback must finish without opening a new ORION approval dialog. If saving needs `documents.create`, use an explicit in-app Save/Export button and omit `onSave`. Process crashes and forced termination are outside this protection. Do not persist private selected content by default.

### Submission, review and distribution

Uploaded apps require no public hosting. `bundle` produces a self-contained `.orion-app` with up to 2 MB runnable HTML / 3 MB complete package, immutable build SHA-256, listing metadata and supported embedded resources. Runtime is a restricted browser frame, not a server/native process. Remote resources/network calls, workers, nested frames, popups, forms and persistent origin storage are restricted. An uploaded app cannot call the IDC server directly; use an independently authorized server integration or supported host actions.

In **App Store → Developers → My apps**, upload, save, inspect automatic screening, run the isolated synthetic preview and submit. Screening blocks obvious secrets/unsupported runtimes and flags suspicious patterns; it is not a malware guarantee. Curators claim a frozen build, review functionality/privacy/security/accessibility and approve, request changes or deny with a recorded reason. Organization administrators cannot publish globally solely through their organization role.

Approved apps are personally installable by members with App Store access and open from Applications as their own app windows. Updates with a new launch origin or broader capabilities pause existing installs for member acceptance. Suspension or an organization block prevents access; retirement removes installations. Reports are available through About this app; policy staff use **Policy Management → App safety**.

Externally hosted HTTPS apps remain supported and require origin-control verification. They can contact their own server under their own privacy/security policy; neither cookies nor `fetch('/api/...')` inside their frame form an ORION API bridge.

## 2. External IDC Operations API

Base: `https://hlncorp.space/api/v1/idc`. Detailed [signing and action reference](https://hlncorp.space/developers/idc/README.md), [OpenAPI](https://hlncorp.space/api/v1/idc/openapi), [Node client](https://hlncorp.space/developers/idc/client.mjs).

An administrator registers an active organization, source tenant, Ed25519 public JWK, permitted scopes and expiry in **IDC Brokerage → Access & integrations**. The integrating server retains the private key. An authorized member delegates a subset of those scopes for the matching Civitas institution for up to 90 days. Every call checks current personnel status, policy acceptance, organizational authority, registration, delegation, expiry and scope. Revocation immediately removes future access.

| Scope | Supported actions |
| --- | --- |
| `contracts.read` | List visible requests; read a current-party contract |
| `tenders.write` | `tender_create`, `edit_draft`, `publish`, cancel an unfunded request |
| `bids.write` | `bid`, `withdraw_bid` for the delegated provider organization |
| `deliveries.write` | `deliver` for the accepted provider |
| `records.write` | `attest_link` with fresh authorized source provenance |
| `events.read` | Poll durable, resumable organization events |

There is no external scope for bid selection, broker award, accepting contract terms, funding approval, payment, delivery acceptance, settlement, dispute decisions, underwriting, claims payout or personnel administration. These remain in the authenticated workspace with independent approval and actual available funds.

### Signing and reliable execution

Sign UTF-8 with Ed25519 over LF-separated lines with **no final newline**:

```text
ORION-IDC-1
METHOD
/api/v1/idc?exact=query&original=order
ISO-UTC-TIMESTAMP
NONCE-UUID
DELEGATION-UUID
LOWERCASE-SHA256-OF-EXACT-BODY
```

Headers: `X-Orion-Client`, `X-Orion-Delegation`, `X-Orion-Timestamp`, `X-Orion-Nonce`, `X-Orion-Signature` (unpadded base64url). GET signs an empty body. Clock tolerance is 120 seconds, nonces are single-use per client, and bodies are bounded to 40,000 bytes. Do not follow redirects.

POST `{key,action,data}` uses a durable UUID business instruction ID, distinct from the signature nonce. Store the instruction before sending. Retry an uncertain timeout/5xx only with unchanged key and body but a fresh timestamp/nonce/signature. Existing-record changes require current `revision`; a 409 requires a fresh read and review. Money uses integer minor HCC. No client floats or account-supplied payout recipients are accepted.

GET `?view=contracts&after=UUID` returns up to 50 `{items,nextCursor}`; null cursor ends the list. GET `?view=contract&tender=UUID` returns one authorized projection. GET `?view=events&after=INTEGER` returns up to 100 events; persist `nextCursor` only after handling the batch and deduplicate event IDs. Events contain `id,type,tenderId,occurredAt`; they are committed with the operation, not inferred from notifications. Poll events around every 10–15 seconds with jitter; no webhook transport is promised.

List cursors are UUID order, not change cursors. Bootstrap by listing requests then replaying events from zero with deduplication. Re-fetch affected requests after events. An empty event batch can keep its prior cursor; do not spin in a tight loop. Ignore unknown event types gracefully. Public tender briefs are discoverable to admitted providers, but a full private contract requires a current party.

401 means invalid authentication, 403 inactive authority/scope, 409 conflict/replay, 429 admitted-client limit (120/minute; honor `Retry-After`), and 503 temporary service failure. Errors use `application/problem+json`; successful responses carry `X-Request-Id`. Keep private responses out of shared caches. Do not log private keys, signatures or full private bodies.

## 3. Native ORION records

The native **IDC Brokerage** action in Convoy, Dispatch and Materials opens the central workspace and, when configured, attaches a current authorized source. ORION administrators configure `ORION_IDC_CLIENTS` in the server environment as a tenant-keyed map of `{clientId,privateKey}`. That value is never supplied to an App Store iframe or downloadable guide. Use a separate client per organization.

Corporate organization IDs and ORION tenant slugs are not interchangeable: Corp `hln` normally maps to ORION `hln-corp`; Corp `hln-group` maps to ORION `hln`; SATO maps to `sato`. The client registration's explicit source tenant must match. Do not infer affiliation from a caller-supplied slug.

The member supplies their own expiring IDC delegation. Local UCI subject must equal its holder. The server checks OPE, source access and the actual organization, then re-reads the source. A source digest and observation time older than five minutes or changed record is rejected. Only source UUID, kind, selected state and digest are attested; private crew positions, medical threads and stock contents are omitted. Source attestation never accepts delivery or releases money. Native Dispatch references contracted work records, not private Apex emergency beacons.

## 4. Public media feed

GET `https://hlncorp.space/api/omb-financial-feed` is read-only and intentionally public. `schemaVersion` is `1.0.0`; `generatedAt` is real UTC and `settingYear` is a separate fictional-calendar display value. Fields include source availability, published instruments, aggregated order-book levels and unexpired public alerts. Prices are integer minor units in the indicated currency; quantity is the published instrument quantity.

Cache/poll at least 15 seconds. `status: PARTIAL` means a source is unavailable or stale; missing datasets are `null`, not fabricated zeroes. HTTP 503 means neither source is usable. Check every alert's `expiresAt`. The feed exposes no private bids, balances, customer identities, crew positions or insurance claims and publishes no fabricated NAV drivers, bond return or guaranteed yield.

## 5. Separate Apex services

Public documentation: [Alerts](https://apexstarcomm.space/developers/alerts), [Atlas](https://apexstarcomm.space/developers/atlas), [Dispatch](https://apexstarcomm.space/developers/dispatch), [Access](https://apexstarcomm.space/developers/access).

Public API base is `https://api.apexstarcomm.space`. Alerts use ETag/304, roughly 15-second polling and server-clock expiry. Empty active alerts is a normal result. Atlas provides dataset/versioned location references, not live ship positions. Honor 429 and `Retry-After`.

Private Dispatch requires a **player-specific UCI RS256 token with audience `https://apexstarcomm.space`**, the manually approved client and a current responder/dispatcher grant. Do not forward an ORION-audience token, IDC signing key or generic service credential. The documented crew API exposes one Apex queue, not organization queues, and does not offer third-party beacon creation or a public alert-write endpoint. Closed private positions/threads have a seven-day purge boundary; ORION does not copy them into contracts.

The optional handover lists the facts to confirm with the Apex operator only if that private connection is separately requested. Public Atlas/Alerts reads need no HorizonOS adapter. IDC runs entirely within ORION without either a HorizonOS client or private Apex crew access. No game-log collection is needed for these workflows.

## Acceptance checklist

- Verify exact origins, SDK connection and light/dark updates; never trust display context as identity.
- Test approved, denied, expired and revoked actions, compact windows, keyboard navigation and unfinished-work closure.
- Test an unchanged instruction after an intentionally lost response, a stale revision, a repeated nonce and organization/actor mismatch.
- Test event restart from a saved cursor; deduplicate and tolerate source outage/429.
- Confirm no private bid, account, position or document leaks into public feeds or logs.
- Use synthetic records for integration acceptance. Real fund allocation, policy activation and payments need the existing explicit human authorizations.

## Calendar contract

Member-facing dates use the same calendar day with the real year plus 930 (20 October 2026 displays as 20 October 2956). Date-entry controls label the in-game calendar and convert once on submission. Store timestamps, signatures, expiry comparisons and API date-time fields in real UTC; never send a year-2956 timestamp to an API that expects real UTC. The public feed exposes the separate `settingYear` for presentation. Relative durations are unchanged.
