{
  "schemaVersion": 8,
  "name": "Report Desk agent publishing contract",
  "canonicalUrl": "https://reports.bynhat.com/post/agent-publishing.json",
  "humanGuide": "https://reports.bynhat.com/agent-access/#publishing",
  "managementConsole": "https://reports.bynhat.com/agent-access/",
  "apiBase": "https://reports.bynhat.com",
  "authentication": {
    "type": "clerk-organization-api-key",
    "headerTemplate": "Authorization: Bearer ${REPORTDESK_API_KEY}",
    "secretEnvironmentVariable": "REPORTDESK_API_KEY",
    "organization": "The single invite-only Report Desk Clerk organization",
    "availableScopes": [
      "reports:read",
      "reports:write",
      "reports:publish"
    ],
    "browserSessions": "Humans authenticate with their own Clerk session at /workspace/. API keys are for non-browser agent and CLI clients only.",
    "instructions": [
      "The signed-in key owner can create or reveal a scoped key at /agent-access/ and connect a Codex machine with the public setup utility. No repository checkout is required.",
      "Use reports:read for catalog inspection, reports:write for project, report, and file mutations, and reports:publish for publishing.",
      "A key can be reused across its owner's agents. Separate keys are recommended for per-agent attribution and independent revocation.",
      "A key stops authenticating if its owner is no longer a member of the Report Desk organization.",
      "Keep it in memory or a secret environment variable; never place it in a URL, report package, source file, commit, log, screenshot, or final response.",
      "Never print, echo, summarize, or otherwise disclose the API key."
    ]
  },
  "keyManagement": {
    "path": "/agent-access/",
    "visibility": "Signed-in members can list, create, reveal, and revoke only their own agent keys.",
    "presets": {
      "publish": [
        "reports:read",
        "reports:write",
        "reports:publish"
      ],
      "draft": [
        "reports:read",
        "reports:write"
      ],
      "read": [
        "reports:read"
      ]
    },
    "reuse": "The owner may use one key with multiple agents.",
    "recommendation": "Create a separate key for each agent when per-agent attribution or independent revocation is important.",
    "revocation": "Revoking a reused key immediately disconnects every agent that uses it."
  },
  "localSetup": {
    "automationUrl": "https://reports.bynhat.com/post/setup-reportdesk-agent.mjs",
    "downloadCommand": "curl --fail --silent --show-error --location https://reports.bynhat.com/post/setup-reportdesk-agent.mjs --output setup-reportdesk-agent.mjs",
    "setupCommand": "node setup-reportdesk-agent.mjs",
    "statusCommand": "node setup-reportdesk-agent.mjs --check",
    "supportedPreset": "publish",
    "agentInstruction": "Copy the setup instruction from Agent access and paste it to the agent on the machine being connected. The agent fetches the hosted utility with curl, runs it, then runs --check. The key owner enters the credential only through a private attached TTY hidden prompt; the agent must never request or receive the key in chat.",
    "agentSetupCommand": "setup_dir=\"$(mktemp -d)\" && setup_path=\"$setup_dir/setup-reportdesk-agent.mjs\" && trap 'rm -f \"$setup_path\"; rmdir \"$setup_dir\"' EXIT && curl --fail --silent --show-error --location https://reports.bynhat.com/post/setup-reportdesk-agent.mjs --output \"$setup_path\" && node \"$setup_path\" && node \"$setup_path\" --check",
    "repositoryRequired": false,
    "credentialFile": "$CODEX_HOME/.env when CODEX_HOME is configured; otherwise ~/.codex/.env",
    "behavior": "The setup command accepts a Publish reports key through a hidden prompt, proves read, write, and publish scopes without mutating Report Desk, and stores it with owner-only permissions. Restart Codex once after setup. Draft and Read keys are for limited custom clients and are intentionally rejected by this publishing setup.",
    "futureSessions": "Check REPORTDESK_API_KEY first. If present, publish without asking the owner for the key again.",
    "rotation": "After revocation, repeat the hosted setup flow with the replacement key. Explicit prompt or standard-input setup ignores any stale inherited REPORTDESK_API_KEY."
  },
  "authority": {
    "scope": "organization-and-operation-scoped-publisher",
    "warning": "An API key can act only within its Clerk organization and granted report scopes. API keys can never delete Report Desk data.",
    "defaultBehavior": "Create new content only. Do not overwrite, move, or unpublish existing content without an explicit owner instruction for the exact target."
  },
  "transport": {
    "recommended": "Direct server-side HTTPS requests from the agent runtime.",
    "humanWorkspace": "https://reports.bynhat.com/workspace/",
    "note": "Non-browser agents send direct HTTPS requests and omit the Origin header. Human browser requests use a Clerk session on the Report Desk origin."
  },
  "workflow": [
    {
      "step": 1,
      "action": "Inspect the current management catalog",
      "request": {
        "method": "GET",
        "path": "/api/manage/catalog"
      },
      "result": "Use projects, reports, and current revisions to avoid collisions or stale writes."
    },
    {
      "step": 2,
      "action": "Create the project only when it does not already exist",
      "request": {
        "method": "PUT",
        "path": "/api/manage/projects",
        "contentType": "application/json",
        "exampleBody": {
          "id": "example-project",
          "name": "Example Project",
          "summary": "Reports for this project.",
          "isPublished": true
        }
      },
      "result": "A published parent project is required for the final report URL to resolve."
    },
    {
      "step": 3,
      "action": "Create report metadata as a draft",
      "request": {
        "method": "PUT",
        "path": "/api/manage/reports",
        "contentType": "application/json",
        "exampleBody": {
          "id": "example-report",
          "projectId": "example-project",
          "sourcePath": "",
          "title": "Example Report",
          "date": "8 August 2026",
          "kind": "Research report",
          "description": "A concise description of the report.",
          "tags": [
            "example"
          ],
          "isPublic": false,
          "expectedRevision": 0
        }
      },
      "result": "Read report.revision from the response. Creation requires expectedRevision 0. Reports are workspace-only unless isPublic is explicitly true."
    },
    {
      "step": 4,
      "action": "Upload every package file one at a time",
      "request": {
        "method": "PUT",
        "pathTemplate": "/api/manage/files?reportId={reportId}&path={urlEncodedRelativePath}&expectedRevision={currentRevision}",
        "contentType": "The file's actual MIME type",
        "body": "Raw file bytes"
      },
      "result": "After every successful upload, replace currentRevision with file.revision from the response. Upload a root index.html before publishing."
    },
    {
      "step": 5,
      "action": "Publish the completed report",
      "request": {
        "method": "POST",
        "path": "/api/manage/publish",
        "contentType": "application/json",
        "exampleBody": {
          "id": "example-report",
          "expectedRevision": "Use the revision returned by the final file upload"
        }
      },
      "result": "A successful response returns status published and the final revision. The canonical URL is https://reports.bynhat.com/{reportId}/."
    },
    {
      "step": 6,
      "action": "Verify the canonical report URL",
      "request": {
        "method": "GET",
        "pathTemplate": "/{reportId}/"
      },
      "result": "Confirm the page loads, local assets resolve, and interaction still works."
    }
  ],
  "revisionProtocol": [
    "Every permitted report metadata, file, and publish mutation requires expectedRevision.",
    "Use 0 only when creating a new report.",
    "Read the new revision from each successful response; never guess after a failed request.",
    "On HTTP 409 revision_conflict, stop, refresh /api/manage/catalog, and reconcile with the owner instead of overwriting concurrent work."
  ],
  "visibility": {
    "field": "isPublic",
    "default": false,
    "private": "Workspace-only reports require an authenticated Clerk organization member for the canonical URL and every package asset.",
    "public": "A published report with isPublic true and a published parent project is readable without signing in at its canonical URL, including its local package assets and legacy alias.",
    "catalog": "Public access applies only to the selected report package. Workspace, project, search, and catalog routes remain private.",
    "endpoint": {
      "method": "POST",
      "path": "/api/manage/visibility",
      "contentType": "application/json",
      "exampleBody": {
        "id": "example-report",
        "isPublic": true
      }
    },
    "updateRule": "Omitting isPublic from a metadata update preserves the current value. A visibility-only request preserves publication state and takes effect immediately without republishing. It does not require expectedRevision. The revision advances only when the visibility value changes; always use the returned report.revision for any later revision-checked mutation."
  },
  "packageRules": {
    "requiredRootFile": "index.html",
    "reportIdPattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
    "paths": "Relative forward-slash paths only; no leading slash, trailing slash, empty segment, dot segment, traversal, backslash, or control character.",
    "maximumFileBytes": 26214400,
    "maximumTrackedStorageBytes": 5368709120,
    "maximumTags": 50,
    "remoteAssets": "Prefer local package assets. Preserve the report's own HTML, CSS, JavaScript, images, and interaction."
  },
  "unsupportedApiKeyOperations": [
    {
      "method": "DELETE",
      "path": "/api/manage/reports",
      "body": {
        "id": "report-id",
        "expectedRevision": "current revision"
      },
      "rule": "Forbidden for API keys regardless of scope. An authorized human workspace administrator must perform this action."
    },
    {
      "method": "DELETE",
      "pathTemplate": "/api/manage/files?reportId={reportId}&path={urlEncodedRelativePath}&expectedRevision={currentRevision}",
      "rule": "Forbidden for API keys regardless of scope. An authorized human workspace administrator must perform this action."
    },
    {
      "method": "DELETE",
      "path": "/api/manage/projects",
      "body": {
        "id": "project-id"
      },
      "rule": "Forbidden for API keys regardless of scope. An authorized human workspace administrator must perform this action; a project containing reports cannot be deleted."
    }
  ],
  "errorShape": {
    "example": {
      "error": {
        "code": "revision_conflict",
        "message": "The report revision does not match expectedRevision.",
        "details": {
          "currentRevision": 4
        }
      }
    }
  },
  "completionReport": [
    "Project ID and report ID",
    "Canonical report URL",
    "Final revision",
    "Number of uploaded files",
    "Verification performed",
    "Any warning or unresolved issue",
    "Never include the API key"
  ]
}
