Developer guide
Existing HTTP, SSE and WebSocket services can keep their paths and payloads. LinkCattle adds an authenticated endpoint; a client App is optional. Build and sign local releases with each repository's python3 scripts/release.py entry point.
Repository boundaries
linkcattle-gateway owns the platform and website. linkcattle-sdk owns versioned contracts and libraries. linkcattle-client owns CLI, Android, iOS and computer execution. linkcattle-app owns business Apps; linkcattle-skills owns curated Skills; linkcattle-plugins owns Agent Plugin packages and marketplace artifacts. Each repository builds its own outputs from fixed dependencies.
Connect an API
Start the service on a fixed loopback HTTP address. Write a route file:
{
"business_api": {"id": "example.notes.api", "version": "1.0.0"},
"routes": [
{"methods": ["GET"], "path": "/notes/**", "requires": ["notes.read"]},
{"methods": ["POST"], "path": "/notes", "requires": ["notes.write"]},
{"methods": ["GET"], "path": "/events", "requires": ["notes.read"]},
{"methods": ["GET"], "path": "/ws", "requires": ["notes.read"], "websocket": true}
]
}
linkcattle connect api 127.0.0.1:8080 --routes /absolute/notes-routes.json
linkcattle list
linkcattle access INSTANCE_ID --capabilities notes.read --output /absolute/private/access.json
The exported mode-600 file contains a short-lived endpoint and access_token. Clients append their existing path and send Relay-Authorization: Bearer <access_token>. This header is removed before forwarding, preserving the business Authorization header. Keep the file outside source/build directories and do not print its contents.
Routes match exactly unless an explicit trailing /** permits descendants. Unknown routes, ambiguous paths and missing capabilities are rejected. Upstreams cannot be caller-controlled destinations. Continue resource-level and field-level authorization in the business service; an SDK does not sandbox the backend.
The lower-level rg-connector CONFIG.json supports custom application identity and proxy routes. See linkcattle-sdk/crates/gateway-sdk/src/bin/rg-connector.rs for configuration. trusted_gateway: true explicitly accepts platform TLS termination; TLS passthrough instead uses a protected endpoint_config containing hostname, certificate path and private-key path. Keep certificate validation enabled in both modes.
Streams and failures
Browser credentials bind exact allowed origins. SSE uses a fetch stream because browser EventSource cannot set the required header. Native WebSocket clients can send the header directly. Browsers first call POST /__linkcattle/ws-tickets with {"path":"/ws"}, then connect to that exact WebSocket path with ?__relay_ticket=.... Tickets expire after 30 seconds and are single-use; never place a long-lived access token in a URL.
A forwarded write may return 409 outcome_unknown after a disconnect. Query the business result or use a business idempotency key. Do not automatically retry writes or paid model tasks. A stream reconnect does not imply cancellation or replay of the original operation.
Build a App
Host API 1.5 supports deferred context delivery for Apps whose compatibility range excludes API 1.4. Verified local pages can appear before live authorization and peer discovery finish. A pending #relay-context has authorization_pending: true and no API credential; await the runtime's GET /__linkcattle/context using X-LinkCattle-Runtime before business requests. Live package eligibility, grants, routes and expiry checks remain required. Fast local opening must also match the selected service ID, application type and cached business API contract. Missing or incompatible metadata uses normal compatible-release resolution before opening. Compatible older Apps retain synchronous context delivery.
Apps live in the separate linkcattle-app repository. A package has a manifest.json, local Web assets and license/source notices. It declares application types, business API compatibility, host API compatibility, routes, required/optional capabilities, storage version and security revision. Use Rust SemVer range syntax, such as >=1.0.0, <2.0.0.
Hosts load verified local assets in a restricted Web container. External scripts, workers, Service Workers, arbitrary native programs and install scripts are prohibited. Storage is isolated by platform, account, instance, App and schema version. Required native permissions that the host does not implement are denied. Declare and verify each supported OS; package signatures do not establish runtime compatibility.
Public external dependencies belong on the calling client, not the connected computer. Host API 1.7 Apps may declare external_network: "allowlist", external_origins: ["https://api.github.com"] and a host range starting at 1.7. Each origin is exact HTTPS without ports, paths, credentials, wildcards or private hosts. The host adds these origins only to CSP connect-src and the native resource allowlist; navigation and remote code restrictions remain. Domain changes require renewed App consent. #relay-context.external_http supplies a client-local endpoint and the allowed origins, even while computer authorization is pending. For APIs without CORS, GET that endpoint with url and optional timeout_ms (250–12000), using only the local X-LinkCattle-Runtime proof. The JSON response contains the upstream status and original UTF-8 body, or a separate transport_error; it is limited to 1MiB, six concurrent reads and no redirects. Native requests never forward App, account or cookie credentials. Computer installation and execution still use their existing authorized routes.
Host API 1.4.0 adds summary.share and file.download. Apps declaring either permission, including as optional, must require host_api_range: ">=1.4.0, <2.0.0": older hosts reject unknown permission names. Catalog selection retains earlier compatible releases rather than requiring the newest App version. Existing WebSocket Apps without these permissions can keep their 1.3.0 minimum.
Flutter Web packages use --csp --no-web-resources-cdn --no-source-maps with local renderers and fonts. Do not enable arbitrary CDNs or unsafe-eval to make a package work. Read the linkcattle-sdk/crates/gateway-market/src/lib.rs and a current linkcattle-app manifest before adding fields.
A successful file.download reply is returned only after the host has saved the complete file without replacing an existing download. Current Apps show a temporary completion notice with a link to the account's download list. The host observes its own save result; a App cannot declare a successful download on its behalf. Keep download-list navigation separate from downloading a file. In an ordinary browser without host saving, report that the download was handed to the browser rather than claiming it appears in LinkCattle's list. Capability-negotiated UI actions do not grant new file routes or expose account credentials.
Apps declaring external.open may request a system-browser authorization page with postMessage({type: 'relay-host-open-link', request_id, url, external: true}, '*'). The host requires HTTPS, current package eligibility and a separate user confirmation; it injects no App context into that browser.
Owner-approved computer components use the trusted App's generic execution plan above the Web container. install_web_pinned with package_receipt: true returns the verified package's public path, manifest, hashes and current user/computer identifiers. It never returns account credentials. The client pins the reviewed version and queries an uncertain operation by its original ID; package installation does not itself run component code or grant execution rights.
Publish
The signed-in App opens the developer console with a short-lived, market-only session. Register a publisher and application type, create a signed immutable release, upload its bytes, submit for independent review, then publish. A market session does not grant access to business instances. Paid distribution is not implemented.
python3 scripts/release.py --product all --plan
python3 scripts/release.py --product all --signing-config /absolute/private/app-signing.json --market-bin /absolute/tools/rg-market
Packing emits the package and .release.json signed release statement. Keep private keys in protected storage. Every content change needs a new version; the same App ID/version cannot be replaced with different bytes. Reviewers cannot approve their own publisher or release. Private-channel membership and service grants remain separate.
Publishing is asynchronous: publishing becomes published only after the signer uploads and verifies package/TUF objects. The host verifies the pinned market root, TUF expiry/version/hash checks and publisher signature. Revocation blocks new runtime leases; it cannot erase code already downloaded.
Keep the TUF root key offline and online role keys available only to the separate signer. Tools include rg-market rotate-prepare / rotate-apply for dual-signed root rotation, publisher-rotation for normal publisher rotation, and publisher-recovery / publisher-recovery-sql for reviewed recovery requiring owner, independent reviewer and new-key proofs. See the linkcattle-sdk/crates/gateway-market/src/main.rs for arguments; never reinitialize an existing trust root as a recovery shortcut.
Agent Plugins
Agent Plugins are separate from business Apps. linkcattle-plugins maintains the standard root plugin.json, optional mcp.json and skills/, plus .agents/plugins/marketplace.json. The platform serves the built marketplace artifact; it does not build plugin source. LinkCattle Client installs and runs approved packages on the selected computer. The phone starts human OAuth in the system browser; the provider grant and callback flow are handled by the computer-owned plugin host. Provider credentials remain on that computer. Provider registration and real-account acceptance are separate from offline package checks.
Run a platform
Configuration examples are in linkcattle-gateway/config/; deployment templates are in linkcattle-gateway/deploy/. Provide separate MySQL, Redis and S3-compatible storage, persistent public market metadata, protected platform identities and a separate market signer. Keep market objects private and distinct from public application downloads. Reuse existing identities during upgrades and review templates before applying them to your own infrastructure.
A verified local renderer may paint while current package eligibility and instance authorization are pending. This is not a privileged runtime lease: private storage, host operations, peer transport and business requests remain blocked until live eligibility succeeds; API credentials also require live grants, route checks and unchanged user-confirmed transport trust. Cached service metadata is a renderer selection hint, never authorization. Release performance acceptance measures the full host dispatch and visible editable page with delayed eligibility and authorization, not only the inner renderer constructor.