Publishing flow
Publish HTML and assets or a supported app folder from your local harness.
Load load_secondpage_skill({ "topic": "core" }) first, or check the exact installed skill version. Use topic: "publishing" for complete upload/export examples. A stale installed version returns fresh instructions.
Choose the local input
Tell your agent, “Publish this to SecondPage,” and identify the file or folder.
HTML and assets follow the static Page flow below. A supported server project
uses a reviewed secondpage.json declaration and the downloadable
self-contained Node helper.
Verify its bytes against the distribution manifest.
Node 20.9 or later is required; no SecondPage checkout or private package is needed. It builds the certified node-http shape and packages bounded output;
package.json alone does not establish support. Next.js static exports can use
static publishing; arbitrary Next.js servers and other backends are not certified.
Remote MCP cannot read a local directory or run the build.
People can also publish a built app folder themselves from the app's Upload
button; see Publish an app. Either way, the
static files in output_dir (minus the server entry and its source map) are the
Page visitors see, so build the frontend into output_dir with an
index.html.
Load the publishing skill topic for the exact local command and existing
runtime upload, certification and activation APIs. Uploading code is not proof
that it is running. The agent returns the Page URL with honest active, pending,
missing-configuration or unavailable status. Unsupported projects fail before
activation. The existing Page identity and sharing model remain in place.
Private keys
Authorize one explicit local .env file, one app/environment and selected declared names. The private local helper parses bounded data without shell evaluation or interpolation, and sends values directly through scoped authenticated writes. Never paste private values into chat or ordinary tool arguments. Keep the file outside build inputs; it is never an artifact. Publishing permission alone does not grant key management or authorization for code to use keys.
Settings → App API keys lets owners/admins import, replace or explicitly remove keys later, without uploading code again. Preview and production stay separate; production uses the verified code with its own declaration and approval. A save verifies and applies an immutable candidate automatically within unchanged approved scope. Omitted names remain unchanged. Failed replacements preserve the current deployment; removing a required key stops that environment. Optional third-party testing is explicit and may incur cost or side effects.
Secret-backed execution stays disabled until migration, key configuration and provider isolation/projection checks are complete. A disabled gate is an unavailable capability, not a working app.
Create a static Page
Search with list_pages({ query }) before creating a duplicate; follow next_cursor with the same query until has_more is false. list_pages takes scope ("pages", "shared_with_me", or "all", default "all"), folder_id (a UUID, or null for the root), include_folders and folder_cursor. Each Page row includes owned_by_me, has_shortcut, in_pages, in_shared_with_me and access_reasons ("direct_share", "organization", "public_visit"), and the reply adds folders ([{ id, title, updated_at }]) and folders_next_cursor. If the workspace lacks the required scopes, list_pages returns page_collection_scope_not_enabled. Use create_page for new work. create_page and update_page have no folder input; a person moves Pages into folders in the app. Matching runs before publication or signed upload URLs. target_review_required needs review of the returned evidence; use read/export/update for a revision or create_separate only after the user confirms a distinct Page.
Choose exactly one content input:
html: one self-contained document, up to 512 KiB of UTF-8 bytes; becomes index.html.upload_files: a complete file inventory when the agent can make HTTP requests. PUT missing files, then complete with upload_session_id.files: the complete bundle sent in the call. Each file carries one source:content_textfor a text file,urifor adata:URI or a publichttps:URL that SecondPage fetches itself,content_base64for binary bytes the agent holds, orsha256alone to reuse a file the Page's current version already holds (or one the organization uploaded through an upload session).
Use one mode per call. The one pairing allowed is files with upload_session_id: when a signed PUT is refused, which is common in sandboxed cloud sessions such as Claude cloud and Cowork because their network allowlists block direct uploads, the agent sends the session's bytes in the call, in as many calls as needed, until no file is reported missing. That path needs no network allowlist change. Organizations that want agents to upload directly from such a sandbox can allow secondpage-artifacts.r2.cloudflarestorage.com instead. A manifest is optional for the default index.html route. For a custom entrypoint or several routes, supply a manifest with schema_version "1.0", entrypoint and route-to-file mappings. File sizes/hashes come from upload_files; manifest.files is optional and must match the edited bytes if supplied.
For slide decks, prepare_deck assembles title, slides[] (1 to 200 slides) and optional shared_css, assets (up to 249 assets) and canvas (default 1280x720) into files without publishing, up to a 2 MiB response (prepared_deck_response_too_large above that). create_page accepts presentation: { intent: "auto" | "deck" | "page", policy?: "auto" | "off" } (intent defaults to "auto"), and update_page accepts presentation: { intent } only (sending policy on update_page returns presentation_policy_not_supported). Omit presentation normally and do not send explicit "auto".
Page context brief
create_page and update_page take an optional context object (see Page context). The schema is strict and rejects unknown keys:
- State fields replace the stored value when sent (an empty value clears):
purpose(up to 600 characters),audience(up to 300 characters),sources(up to 12 items of up to 300 characters each) andnotes(up to 4,000 characters). - Logs only append:
decisions,rejectedandopen_questions, each up to 50 entries of up to 300 characters. resolve_open_questionscloses questions by entry ID or exact text.base_revision(a positive integer) is required when changing a state field or resolving a question. Pure appends never conflict.
A stale or missing revision returns context_conflict or context_base_revision_required and nothing is published. That error reply carries base_revision, current_revision, updated_by, updated_at and context. Write replies return context_revision, and read_page returns context with its revision. On upload, send context with the completion call, not the initiation.
Revise safely
Read the current Page to obtain base_version_id. If file_bundle is null, use export_page with export_type:html and retain the exported version_id and manifest so the files, base, entrypoint and routes match. Repeat that manifest on upload initiation and completion. Custom routes require upload mode; hand off the upload if the client cannot PUT files. Every update_page call requires base_version_id and replaces the entire bundle; omitting it fails with "base_version_id is required. Call read_page or export_page, reapply your edit, and use that version_id as the base." (REST also requires base_version_id on PATCH /api/pages/:id when files publish and on .../upload-sessions/:sessionId/complete.) On version_conflict, the error includes base_version_id, current_version_id, conflict_id, next_action, current_version ({ version_id, version_number, published_at, published_by, update_reason }) and a message naming who published which version and when ("{by} published version {n} at {at} (\"reason\"). Keep their changes when you reapply yours, and tell the person you are working for."). Read/export again, reconcile the new changes, and reapply the edit. Do not resend old files with a newer base without checking them.
Both opening and completing an update upload session require the same base. Keep the original requested metadata on completion, including title, source_meta and update_reason. A rename with unchanged files still updates the title. A fully identical request returns no_change without creating a version.
New Pages default to private (owner and invited people only). Choose organization explicitly to share with organization members. Pick the audience on create; on updates omit visibility or repeat its current value. On Pages this agent created, call read_page_access first, then set_page_visibility for the audience, add_page_people to invite people, change_page_roles for existing roles, or remove_page_people to remove them. Each tool requests one kind of change. Unless an admin turned agent sharing off, a new request returns human_approval_required with an approval_url; give it to the person you work for. Nothing changes before approval. Sharing refusals return codes such as agent_sharing_disabled, page_sharing_forbidden, recipient_outside_organization or share_rate_limited. Read access again afterwards, and return the Page URL and confirmed audience. Ask a person to change any other Page.
On Pages this agent created, set_page_link sets a custom link (at least 3 words) on Pro and Enterprise plans, and the old link forwards. delete_page takes page_id and an optional reason and moves this agent's Page to Trash.
Upload and media
Upload only files marked upload_required, using the returned URL and required_headers. If a session expires, open a new one. After an intervening revision, reconcile and start a new update session. Include every referenced asset in the replacement bundle.
For direct media, call create_media_upload, PUT the bytes, then complete_media_upload and use asset_url. Explicit media_asset_ids are supported on inline/html publication; signed upload modes include assets in their file inventory.
Feedback and failures
Read comments with page_id and queued_only:true. Apply the requested change to a complete fresh bundle, then reply with add_comment and parent_comment_id. Inspect warnings before claiming the Page is finished. Check isError and the stable error code; use recovery or next_action and respect retry_after_seconds. Queued work is pending, not completion.
See Page exports, Live Data, and Page capacity.
Still have a question?
Ask in your own words, or write to us at support@secondpage.cc.