Skip to content

App manifest

yaml
schemaVersion: 3
appId: 6f9619ff-8b86-4e6e-a62a-889950f42d3e
frontend:
  directory: frontend
  spa: true
runtime:
  sdk:
    version: 2.2.0
files:
  access: user
  maxUploadBytes: 52428800
migrations:
  - id: 0001_create_items
    file: migrations/0001_create_items.sql
functions:
  - name: summarize
    entrypoint: functions/summarize/index.ts
    access: user
  - name: hourly-summary
    entrypoint: functions/hourly-summary/index.ts
    access: system
  - name: receive-support
    entrypoint: functions/receive-support/index.ts
    access: system
  - name: receive-slack-message
    entrypoint: functions/receive-slack-message/index.ts
    access: system
  - name: receive-telegram-message
    entrypoint: functions/receive-telegram-message/index.ts
    access: system
  - name: receive-asana-events
    entrypoint: functions/receive-asana-events/index.ts
    access: system
  - name: process-report
    entrypoint: functions/process-report/index.ts
    access: system
queues:
  - name: reports
    function: process-report
    concurrency: 2
    maxAttempts: 3
    retryDelaySeconds: 5
    retryBackoff: true
    timeoutSeconds: 120
email:
  addresses:
    - name: support
      displayName: Example Support
      function: receive-support
notifications:
  webPush: true
  icon: /icons/notification.png
cron:
  - name: hourly-summary
    schedule: "0 * * * *"
    function: hourly-summary
    enabled: true
health:
  path: /
secrets:
  INTERNAL_SIGNING_KEY: generated
  AI_API_KEY: required
  ORGANIZATION_LABEL: optional
integrations:
  calendar:
    provider: google-calendar
    account: calling_user
    cardinality: one
    capabilities:
      - calendar.events.read
  bank:
    provider: gocardless-bank-account-data
    account: calling_user
    cardinality: many
    capabilities:
      - bank.accounts.read
      - bank.balances.read
      - bank.transactions.read
  team_chat:
    provider: slack
    account: app
    cardinality: many
    capabilities:
      - slack.messages.send
      - slack.messages.receive
    events:
      message:
        function: receive-slack-message
  support_chat:
    provider: telegram
    account: app
    cardinality: many
    capabilities:
      - telegram.messages.send
      - telegram.messages.receive
    events:
      message:
        function: receive-telegram-message
  work:
    provider: asana
    account: app
    cardinality: one
    capabilities:
      - asana.tasks.read
      - asana.tasks.update
      - asana.assignees.read
      - asana.assignees.write
      - asana.sections.read
      - asana.sections.move_tasks
      - asana.custom_fields.read
      - asana.custom_field_values.write
      - asana.attachments.read
      - asana.attachments.write
      - asana.events.receive
    events:
      function: receive-asana-events
observability:
  metrics:
    - name: items_created
      type: counter
      unit: items
      dimensions:
        actor_type:
          values: [member, admin]
    - name: overdue_items
      type: gauge
      unit: items
  alertRules:
    - id: overdue-items
      name: Overdue items detected
      metric: overdue_items
      aggregation: latest
      operator: gte
      threshold: 1
      window: 5m
      minimumSamples: 1
      severity: warning
      enabled: true

Fields

FieldRules
schemaVersionUse 3 for new manifests; schema 2 remains readable for immutable legacy artifacts
appIdAssigned UUID; must match the deploy target
frontend.directoryRelative directory containing index.html
frontend.spaEnables index.html fallback
runtime.sdk.versionExact installed SDK version pinned to this deployment
data.searchOptional named PostgreSQL searches; nonempty declarations require SDK 2.4.0
files.accessOptional managed Files capability; user (default) or app
files.maxUploadBytes1 byte through 100 MiB; defaults to 50 MiB
migrationsOrdered, append-only IDs and relative SQL files
functionsUnique logical names, entrypoints, and user/public/system access
routesSchema 3 only; up to 100 named asset aliases or Function routes; see App routes
queuesUp to 50 unique background queues targeting declared system Functions
cronUnique names, valid UTC schedules, declared system Function targets
email.addressesUp to 25 unique aliases; optional inbound function must be a declared system Function
notifications.webPushOptional Web Push capability; when present its value must be true
notifications.iconOptional same-origin default notification icon path; omitted uses the OpenCloud logo
health.pathSame-origin path beginning with /
secretsMap of uppercase names to generated, required, or optional
integrationsNamed provider slots with an account mode, one/many cardinality, and bounded capabilities
integrations.*.eventsOptional Slack/Telegram event handlers; message.function must name a system Function
integrationsNamed provider slots with an account mode, one/many cardinality, bounded capabilities, and optional provider events
observability.metricsOptional deployment-pinned counter/gauge catalog; at most 20 metrics
observability.alertRulesOptional deployment-pinned fixed-threshold rules; at most 20 rules, each referencing a declared metric

Schema 3 has no top-level release version. The publisher assigns a bounded app-name-YYYYMMDD-N release name while holding the app deployment lock and stores it separately on the deployment row. runtime.sdk.version is unrelated: it remains in the manifest because the exact SDK is part of the immutable artifact contract.

Routes are optional. Each has a unique id, an absolute path, and exactly one target: asset relative to frontend.directory, or function naming a declared user/public Function. Assets default to methods: [GET, HEAD]; Function methods must be explicit. Access defaults to inherit; only an exact literal asset alias may set access: public, including in a private app. Function routes require SDK 2.3.0. See the routing guide for optional parameters, wildcards, precedence, query input, and reserved paths. The pinned public CLI 3.10.3 validates and bundles routed schema-3 manifests. Custom domains are app settings, not manifest fields.

Schema-2 artifacts keep their embedded version so their verified bytes never change, but OpenCloud does not use that legacy field to name a newly submitted ordinary-app deployment. This lets an unchanged schema-2 source manifest be part of a valid update while the publisher still allocates a fresh database version.

An email alias without function is sending-only. Adding function makes it receive-capable, and OpenCloud uses the same allocated address for outbound From and inbound delivery. Function code selects the alias, never an arbitrary email address or provider credential.

notifications.webPush requires runtime.sdk.version: 2.1.0 or later. Apps pinned to 2.0.0 retain their original runtime-config shape and cannot declare Web Push. notifications.icon is the app-wide default. A same-origin icon supplied to an individual notifications.send call overrides it; if neither is supplied, the managed OpenCloud logo is used. Web app manifest icons remain a separate browser-installation concern. iOS and iPadOS currently ignore notification payload icon overrides and show that installed app icon; an apple-touch-icon takes precedence over manifest-declared icons when both are present.

Queue names use lowercase kebab-case. concurrency is 1–20 and defaults to 1; maxAttempts is 1–10 and defaults to 3; retryDelaySeconds is 1–3600 and defaults to 5; retryBackoff defaults to true; and timeoutSeconds is 1–900 and defaults to 900. maxAttempts includes the initial delivery. Every queue target must be a declared Function with access: system.

Custom metric names use lowercase snake_case. A metric can declare at most three dimensions, and each dimension must list at most 20 allowed values. Alert-rule IDs use lowercase kebab-case. Counter rules support sum and rate; gauge rules support latest, min, max, and avg. Rules are validated with the artifact and become active atomically with the deployment, before the new release can receive traffic. Operational rules created through the API or CLI remain separate. A same-ID operational rule overrides the active manifest definition until that override is deleted; list responses expose which origin is effective. Arbitrary strings, IDs, emails, URLs, and object keys are not valid dimensions. Counters support non-negative increments; gauges record a current value.

Do not write migration checksums. The CLI computes SHA-256 values and emits the canonical opencloud.json inside the archive.

Use an exact SDK version, never latest or a range. Publishing a newer SDK does not change an existing deployment. Frontends import the stable /_opencloud/sdk.js module; they do not derive a versioned path from the manifest.

Schema 2 intentionally rejects runtime.javascriptSdk, storage, and Function verifyJwt, and requiredSecrets. Replace them with runtime.sdk, files, Function access, and the declarative secrets map; do not keep both old and new fields. OpenCloud provisions generated values automatically, injects isolated synthetic values in development, blocks production when a required owner value is missing, and allows optional values to remain absent.

Only manifest-reachable files deploy. Reports, briefs, verifier specifications, and local tests remain outside the artifact.

Google Workspace providers are google-calendar, google-drive, google-sheets, google-docs, and google-slides. Slot names use lowercase snake_case. account: app resolves an owner-configured binding; account: calling_user resolves a separate binding for the signed-in caller. cardinality: one replaces the prior binding, while many permits multiple bindings and requires the Function to select an opaque binding ID.

Provider-scoped capabilities are calendar.events.read and calendar.events.create; drive.files.read and drive.files.write; sheets.spreadsheets.read and sheets.spreadsheets.write; docs.documents.read and docs.documents.write; and slides.presentations.read and slides.presentations.write. Calendar binds an exact calendar. The other providers bind a reusable Google account and accept bounded resource IDs through their typed Function clients.

Read-only Google reporting providers are google-analytics with analytics.reports.read, google-search-console with search.performance.read, and google-ads with ads.reporting.read. They require runtime SDK 2.2.0 or later, must use account: app, and bind one exact GA4 property, verified Search Console site, or directly accessible non-manager Ads customer. Functions use integrations.use(slot).call(operation, input) with the bounded analytics.reports.run, searchConsole.performance.query, or googleAds.campaigns.report operation; raw provider requests, GAQL, tokens, and unbound resource IDs are not part of the contract.

The Asana provider is asana. Every Asana binding selects one exact project. Its capabilities are asana.tasks.read, asana.tasks.create, asana.tasks.update, asana.assignees.read, asana.assignees.write, asana.sections.read, asana.sections.move_tasks, asana.custom_fields.read, asana.custom_field_values.write, asana.attachments.read, asana.attachments.write, asana.stories.read, asana.comments.write, and asana.events.receive. Functions use only the typed integrations.asana(slot) client. asana.tasks.create, asana.tasks.update, asana.assignees.write, asana.sections.move_tasks, and asana.custom_field_values.write also require asana.tasks.read because their methods return normalized current task state.

An Asana events.function must name a declared system Function. It requires asana.events.receive, asana.tasks.read, and account: app. Conversely, asana.events.receive requires events.function. Other providers currently reject events. Asana sections are project groupings (list headings or board columns), not independently connected resources.

The HubSpot CRM provider is hubspot-crm and must use account: app. Available capabilities are crm.contacts.read, crm.contacts.write, crm.companies.read, crm.companies.write, crm.deals.read, crm.deals.write, crm.owners.read, crm.pipelines.read, crm.notes.write, and crm.associations.write. Association writes must also declare the write capability for each source and target record type. Functions use integrations.crm(slot) for bounded contact, company, deal, owner, pipeline, note, and default-association actions. HubSpot slots do not expose deletes, raw provider requests, provider tokens, inbound events, or account: calling_user.

The open-banking provider is gocardless-bank-account-data. Its capabilities are bank.accounts.read, bank.balances.read, and bank.transactions.read. It binds exact accounts chosen through human, bank-hosted consent and exposes only bounded typed methods through integrations.bankAccountData(slot).

The technical provider behind Wise transactions is wise-balance-webhook, with the payments.received.reconcile capability. It must use account: app and binds exactly one owner-created Wise Account deposit events webhook. Functions use only integrations.receivedPayments(slot).bindings/reconcile. No Wise credential, raw transfer ID, or raw transaction feed enters an app.

The Slack provider is slack, with slack.messages.send and slack.messages.receive. It must use account: app. Receiving requires events.message.function, and that target must be a declared system Function. A human connects the reusable workspace, invites @OpenCloud, and binds exact channels in App Integrations. The per-binding mention or all_messages trigger mode is a human choice and does not appear in the manifest. Functions send through integrations.slack(slot).sendMessage; bot tokens, raw connection IDs, and arbitrary channel selection never enter the app.

The Telegram provider is telegram, with telegram.messages.send and telegram.messages.receive. It must use account: app. Receiving requires events.message.function, and that target must be a declared system Function. The app owner pairs an exact private chat or group with a one-time link, selects an owned paired chat in App Integrations, and chooses the per-binding directed or all_messages trigger mode. cardinality: one selects one chat at a time; many can bind several. Private chats do not require bot administration. Directed groups keep the bot as a member; all-message groups grant the bot minimal administrator access in that exact group while privacy mode stays enabled. Functions send through integrations.telegram(slot).sendMessage; bot tokens, raw chat IDs, and arbitrary chat selection never enter the app.

PostgreSQL search declarations

The unpublished SDK 2.4/platform candidate supports named Function searches:

yaml
runtime:
  sdk:
    version: 2.4.0
data:
  search:
    - name: document-chunks
      table: document_chunks
      select: [id, document_id, content]
      filterColumns: [document_id]
      fullText:
        column: search_text
        language: english
      vector:
        column: embedding
        dimensions: 128
        metric: cosine
        model: potion-128-v1

Declare at most 16 unique names and at least one of fullText or vector for each. SQL identifiers are unqualified lowercase identifiers of at most 63 bytes. select has at most 16 unique columns and must include id; filterColumns defaults to [] and allows at most eight unique scalar equality columns. Full-text columns must already be tsvector; language is english or simple. Vector columns must already be vector(n), with declared dimensions 1–2000, metric cosine and a nonempty model identity of at most 128 characters.

App migrations own columns, embeddings and indexes. Provisioning checks the declared base table, supported column types, primary key and forced RLS before activation. Changing a model requires re-embedding stored content; the model label checks query compatibility but cannot attest to stored vector provenance. Absent or empty data.search preserves older SDK compatibility. See Migration SQL and Function search for queries and ranking.

Self-hosted infrastructure for agent-built applications.