Documentation

Source-of-truth docs, references, plans, and product material across Harbor surfaces.

Product Surfaces

API Surface

Harbor APIs should be typed, boring, and explicit.

apicontractscloud
Source: API_SURFACE.md

API_SURFACE.md

API philosophy

Harbor APIs should be typed, boring, and explicit.

No raw-secret endpoints. No generic "proxy my arbitrary HTTP request with hidden creds" endpoints.

Harbor Node API starter surface

See HARBOR_AGENT_WORKFLOW.md for live PowerShell, curl, and Python examples. See HARBOR_RESPONSE_PIPELINES.md for compact response and Business pipeline guidance.

Health

  • GET /health
  • returns service, version, nodeId, nodeName, status, and timestamp

Status

  • GET /v1/status
  • returns persistent node identity plus version, ports, audit count, and timestamps

Ports

  • GET /v1/ports
  • POST /v1/ports
  • GET /v1/ports/:portId
  • PATCH /v1/ports/:portId
  • GET /v1/ports/:portId/actions
  • GET /v1/ports/:portId/oauth/status
  • POST /v1/ports/:portId/oauth/start
  • POST /v1/ports/:portId/oauth/refresh
  • POST /v1/ports/:portId/oauth/disconnect
  • GET /v1/ports/:portId/action-drafts
  • POST /v1/ports/:portId/action-drafts
  • POST /v1/ports/:portId/openapi-import
  • POST /v1/ports/:portId/pack-export

Integrations

  • POST /v1/integrations/import-url
  • POST /v1/integrations/import-manifest

OAuth callbacks

  • GET /v1/oauth/callback/google

Actions

  • GET /v1/actions/:actionId
  • POST /v1/actions/:actionId/execute
  • GET /v1/action/:actionRef
  • POST /v1/action/:actionRef/run
  • POST /v1/action/:actionRef/:operation

Action drafts

  • GET /v1/action-drafts
  • GET /v1/action-drafts/:draftId
  • POST /v1/action-drafts/:draftId/validate
  • POST /v1/action-drafts/:draftId/test
  • POST /v1/action-drafts/:draftId/request-publish
  • POST /v1/action-drafts/:draftId/publish
  • POST /v1/action-drafts/:draftId/reject

Audit

  • GET /v1/audit

Packs

  • POST /v1/packs/import

Approvals

  • GET /v1/approvals
  • GET /v1/approvals/:approvalId
  • POST /v1/approvals/:approvalId/approve
  • POST /v1/approvals/:approvalId/reject

Manager response pipelines

  • GET /v1/manager/response-pipelines
  • PUT /v1/manager/response-pipelines

Response artifacts

  • GET /v1/response-artifacts
  • GET /v1/response-artifacts/:artifactId

Agent execute compaction

Harbor keeps the full execute response as the default. Agent callers may opt into a smaller reply envelope on action execute routes.

Canonical request fields:

  • responseMode: "full" | "compact" | "ok"
  • responsePipelineId: string

Compact aliases:

  • rm: "f" | "c" | "o"
  • rp: "<pipeline-id>"

Compact response envelope:

json
{
  "ok": true,
  "s": "approved",
  "x": "exec_123",
  "o": {
    "summary": "small filtered payload"
  }
}

Fields:

  • ok: whether the action completed successfully
  • s: approved, pending_approval, or denied
  • x: execution id
  • a: optional approval id
  • as: optional approval status
  • o: optional output payload for compact
  • e: optional { c, m } error object

Saved response pipelines are Business-only in v1. They can project a filtered response artifact from the full execution output and reply to agents in either compact or ok mode.

What is real now

  • Harbor Node persists local state in SQLite
  • node identity is created on first start and reused on later starts
  • the built-in system-local port is seeded durably
  • configured webhook ports can be created locally and stored in SQLite
  • configured discord_webhook ports can be created locally with hidden Discord webhook URLs
  • configured http_api ports can be created locally with hidden auth config and stored action templates
  • http_api ports also support durable action drafts that can be created, validated, tested, published, and rejected
  • Harbor distinguishes local caller modes so operators manage ports while agents help with draft authoring
  • existing http_api ports can import a narrow OpenAPI subset into drafts
  • existing http_api ports can export safe Harbor Packs
  • Harbor can import a Harbor Pack into a new local http_api port shell plus drafts
  • Harbor can import Dock integration manifests by URL into a local http_api port and sync live actions onto it
  • imported Gmail http_api ports can use an operator-initiated local OAuth connect flow
  • Google OAuth provider metadata is served by the cloud API, but access and refresh tokens stay local to Harbor Node
  • configured webhook and discord_webhook ports can be updated safely without re-entering hidden values
  • built-in actions currently include system.echo, system.time, system.rotate-token, and system.shutdown
  • configured webhook ports expose fixed actions like <port-id>.echo-remote and <port-id>.send-event
  • configured Discord webhook ports expose <port-id>.send-message
  • configured HTTP API ports expose <port-id>.say-hello and <port-id>.echo
  • stored action records now carry durable metadata such as slug, method, path, requestBodyMode, resultMode, and enabled
  • stored HTTP API action paths may be fixed relative routes or bounded templates like /repos/{owner}/{repo}/issues
  • published actions can carry sourceDraftId so Harbor can trace the live action back to its originating draft
  • policy decisions are local and explicit: allow, deny, or require approval
  • approval-required actions create durable local approval records and do not execute until approved
  • audit events are recorded locally newest-first
  • hidden config such as auth tokens is stored locally but never returned through Harbor APIs, approval responses, or audit events
  • Discord webhook URLs are stored locally but never returned through Harbor APIs, approval responses, or audit events
  • HTTP API auth tokens are stored locally but never returned through Harbor APIs, approval responses, or audit events
  • hidden values pass through a dedicated storage abstraction so Harbor can adopt stronger local-at-rest protection later without changing the API surface
  • real outbound execution is bounded by a timeout and classified into safe failure kinds
  • draft test execution uses the same bounded Harbor execution path and does not make the draft live

What is still future work

  • real third-party Harbor Ports
  • broader custom action authoring for configured ports
  • richer action authoring UX beyond the first draft lifecycle
  • policy editing UI and advanced filtering
  • encryption-at-rest for hidden config
  • cloud sync and Harbor Fleet features

Configured webhook port request

json
{
  "kind": "webhook",
  "label": "Local Mock Webhook",
  "description": "Configured local verification port",
  "baseUrl": "http://127.0.0.1:11821/_mock/webhook/tenant-abc/opaque-xyz",
  "authToken": "super-secret-demo-token"
}

Call it with:

POST /v1/ports

Configured webhook port response

json
{
  "id": "<port-id>",
  "slug": "<port-slug>",
  "kind": "webhook",
  "name": "Local Mock Webhook",
  "description": "Configured local verification port",
  "status": "ready",
  "available": true,
  "actionCount": 2,
  "createdAt": "2026-03-23T18:30:00.000Z",
  "updatedAt": "2026-03-23T18:30:00.000Z",
  "configSummary": {
    "baseOrigin": "http://127.0.0.1:11821",
    "host": "127.0.0.1:11821",
    "hasPath": true,
    "authConfigured": true
  }
}

The hidden auth token is intentionally absent from the response. Harbor also omits the actual stored pathname and any embedded URL credentials from public responses. Once saved, hidden config remains write-only and is not re-shown by Harbor.

Configured Discord webhook port request

json
{
  "kind": "discord_webhook",
  "label": "Discord Alerts",
  "description": "Send a narrow plain text message to Discord",
  "webhookUrl": "https://discord.com/api/webhooks/123456789012345678/example-token"
}

Configured Discord webhook port response

json
{
  "id": "<discord-port-id>",
  "slug": "<discord-port-slug>",
  "kind": "discord_webhook",
  "name": "Discord Alerts",
  "description": "Send a narrow plain text message to Discord",
  "status": "ready",
  "available": true,
  "actionCount": 1,
  "createdAt": "2026-03-23T20:30:00.000Z",
  "updatedAt": "2026-03-23T20:30:00.000Z",
  "configSummary": {
    "baseOrigin": "https://discord.com",
    "host": "discord.com",
    "hasPath": true,
    "webhookConfigured": true
  }
}

The full Discord webhook URL is intentionally absent from the response.

Configured HTTP API port request

json
{
  "kind": "http_api",
  "label": "Local HTTP API",
  "description": "Authenticated API behind Harbor",
  "baseUrl": "http://127.0.0.1:18081",
  "authToken": "harbor-demo-key",
  "authHeaderName": "x-api-key",
  "authTokenPrefix": ""
}

Configured HTTP API port response

json
{
  "id": "<http-api-port-id>",
  "slug": "<http-api-port-slug>",
  "kind": "http_api",
  "name": "Local HTTP API",
  "description": "Authenticated API behind Harbor",
  "status": "ready",
  "available": true,
  "actionCount": 2,
  "createdAt": "2026-03-23T21:10:00.000Z",
  "updatedAt": "2026-03-23T21:10:00.000Z",
  "configSummary": {
    "baseOrigin": "http://127.0.0.1:18081",
    "host": "127.0.0.1:18081",
    "hasPath": false,
    "authConfigured": true,
    "authMode": "header_token",
    "authHeaderName": "x-api-key"
  }
}

The hidden auth token is intentionally absent from the response. Harbor also omits any stored path detail beyond the hasPath flag.

For OAuth-backed Gmail imports, the same safe config summary shape is used, but authMode becomes oauth_google and oauthProvider is google_gmail.

HTTP API action draft lifecycle

Harbor action drafts are durable candidate actions attached to an existing port. They are stored separately from live actions and do not appear in GET /v1/ports/:portId/actions until published.

Draft status values:

  • draft
  • validated
  • published
  • rejected

Caller modes:

  • operator
  • agent
  • script

Current local behavior:

  • only operator may create or edit ports
  • agent and script may read safe metadata and work with drafts under existing ports
  • agent and script may request publish
  • direct non-operator publish is disabled unless Harbor is started with HARBOR_ALLOW_NON_OPERATOR_PUBLISH=true
  • OpenAPI import is operator-only for the MVP
  • Harbor Pack import/export is operator-only for the MVP
  • Dock import is operator-only for the MVP
  • templated paths may use explicit placeholders like {owner} and {repo}
  • callers provide those values through input.path

Minimum draft fields:

  • id
  • portId
  • slug
  • label
  • description
  • method
  • path
  • approvalMode
  • requestBodyMode
  • resultMode
  • status
  • validationSummary
  • latestTestSummary
  • publishedActionId
  • timestamps

Path guidance:

  • use a fixed relative path like /user/repos when the route is fully static
  • use a bounded template like /repos/{owner}/{repo}/issues when the route needs a small explicit placeholder set
  • do not include query strings in path
  • do not use arbitrary full URLs

Example draft creation request:

json
{
  "slug": "draft-hello",
  "label": "Draft Hello",
  "description": "Draft GET hello action",
  "method": "GET",
  "path": "/v1/hello",
  "approvalMode": "automatic",
  "requestBodyMode": "none",
  "resultMode": "json_summary"
}

Call it with:

POST /v1/ports/:portId/action-drafts

Example draft response:

json
{
  "id": "draft_12345678-1234-1234-1234-123456789abc",
  "portId": "<http-api-port-id>",
  "slug": "draft-hello",
  "label": "Draft Hello",
  "description": "Draft GET hello action",
  "method": "GET",
  "path": "/v1/hello",
  "approvalMode": "automatic",
  "requestBodyMode": "none",
  "resultMode": "json_summary",
  "status": "draft",
  "createdAt": "2026-03-26T17:20:00.000Z",
  "updatedAt": "2026-03-26T17:20:00.000Z"
}

Validation:

POST /v1/action-drafts/:draftId/validate

Use an explicit JSON body in PowerShell:

json
{}

Testing:

POST /v1/action-drafts/:draftId/test

Example test request:

json
{
  "input": {},
  "requestedBy": {
    "kind": "script",
    "name": "powershell-example"
  }
}

Publishing:

POST /v1/action-drafts/:draftId/publish

Use an explicit JSON body in PowerShell:

json
{}

Rejecting:

POST /v1/action-drafts/:draftId/reject

Example reject request:

json
{
  "note": "Not publishing this draft"
}

Request publish without publishing directly:

POST /v1/action-drafts/:draftId/request-publish

Recommended caller headers for agent-safe draft work:

  • Authorization: Bearer <agent-token>
  • Content-Type: application/json
  • optional harbor-name: OpenClaw

Once published, the live action appears in:

  • GET /v1/ports/:portId/actions
  • GET /v1/actions/:actionId

Published actions keep a sourceDraftId link when they originated from a Harbor draft.

OpenAPI import MVP

Harbor can import a small OpenAPI document into drafts under an existing http_api port.

Call it with:

POST /v1/ports/:portId/openapi-import

Example request:

json
{
  "document": {
    "openapi": "3.0.3",
    "info": {
      "title": "Custom HTTP API",
      "version": "1.0.0"
    },
    "servers": [
      {
        "url": "http://127.0.0.1:18081"
      }
    ],
    "paths": {
      "/v1/hello": {
        "get": {
          "operationId": "imported-hello",
          "summary": "Imported Hello"
        }
      },
      "/v1/echo": {
        "post": {
          "operationId": "imported-echo",
          "summary": "Imported Echo",
          "requestBody": {
            "required": true,
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    }
  }
}

Supported now:

  • JSON OpenAPI input
  • import into an existing http_api port only
  • GET operations with no request body
  • POST operations with application/json request bodies
  • relative paths under the existing port base URL
  • operationId or a derived Harbor slug

Skipped or rejected now:

  • non-matching servers origins
  • callbacks and webhooks
  • path parameters
  • operation parameters
  • unsupported auth schemes
  • unsupported methods
  • unsupported request body types
  • conflicting slugs

Imported operations remain drafts and do not become live actions until they are reviewed, tested, and published through the existing draft lifecycle.

Harbor Pack foundation

Harbor Packs are a local JSON format for safe reusable http_api templates.

Current MVP boundaries:

  • export from an existing compatible http_api port only
  • import into a new local http_api port shell only
  • operator-only import/export
  • imported action templates remain drafts by default
  • no hidden auth values or raw secrets in the pack payload

Export:

POST /v1/ports/:portId/pack-export

Use an explicit JSON body in PowerShell:

json
{}

Import:

POST /v1/packs/import

What gets exported:

  • pack metadata
  • a safe http_api port template with fields like baseUrl, authHeaderName, and authTokenPrefix
  • action template metadata such as slug, method, path, approvalMode, requestBodyMode, and resultMode

What does not get exported:

  • authToken
  • hidden runtime config
  • audit history
  • approval records

Import behavior:

  • creates a new local http_api port shell
  • copies only safe config defaults from the pack
  • creates action drafts under the new port
  • does not create live actions
  • does not auto-publish imported drafts

The imported port starts degraded until the operator writes hidden auth locally with PATCH /v1/ports/:portId.

Dock integration import

Dock is the hosted catalog layer for safe Harbor integration metadata.

Harbor import routes:

  • POST /v1/integrations/import-url
  • POST /v1/integrations/import-manifest

Current MVP behavior:

  • operator-only
  • imports safe metadata only
  • creates or updates a local http_api port shell
  • syncs live local actions onto that port
  • defaults Dock-backed ports to autoUpdate: true
  • skips workflow templates with an explicit reason
  • never copies hidden credentials from Dock

Example import-by-URL request:

json
{
  "url": "http://127.0.0.1:11826/p/google/gmail"
}

Example response shape:

json
{
  "status": "imported",
  "sourceUrl": "http://127.0.0.1:11826/p/google/gmail",
  "integration": {
    "slug": "gmail",
    "title": "Gmail",
    "version": "0.1.0",
    "publisher": {
      "slug": "google",
      "name": "Google"
    }
  },
  "port": {
    "id": "<imported-port-id>",
    "kind": "http_api",
    "status": "degraded"
  },
  "createdDrafts": [],
  "syncedActions": [
    {
      "id": "<imported-port-id>.get-profile",
      "slug": "get-profile",
      "enabled": true
    }
  ],
  "updatedExistingPort": false,
  "importedWorkflows": [
    {
      "slug": "triage-inbox",
      "status": "skipped",
      "reason": "Workflow templates are not installable yet in this MVP."
    }
  ],
  "warnings": [
    "Credentials must be configured locally before use."
  ]
}

Configured webhook port update semantics

Call it with:

PATCH /v1/ports/:portId

Behavior:

  • omit authToken to preserve the current hidden value
  • set authToken to a new non-empty value to replace the current hidden value
  • set clearAuthToken: true to clear the current hidden value
  • do not send authToken and clearAuthToken: true together

Example metadata-only update:

json
{
  "label": "Local Mock Webhook Updated",
  "description": "Updated without re-entering the hidden token",
  "baseUrl": "http://127.0.0.1:11821/_mock/webhook/tenant-next/opaque-next"
}

Example hidden-value replacement:

json
{
  "authToken": "replacement-secret-token"
}

Example hidden-value clear:

json
{
  "clearAuthToken": true
}

Configured Discord webhook port update semantics

Call it with:

PATCH /v1/ports/:portId

Behavior:

  • omit webhookUrl to preserve the current hidden Discord webhook URL
  • set webhookUrl to a new non-empty value to replace the current hidden value
  • set clearWebhookUrl: true to clear the current hidden value
  • do not send webhookUrl and clearWebhookUrl: true together
  • clearing the webhook URL leaves the port record in place but marks it degraded until a new hidden URL is saved

Example metadata-only update:

json
{
  "label": "Discord Alerts Updated",
  "description": "Updated without re-entering the hidden webhook URL"
}

Example hidden-value replacement:

json
{
  "webhookUrl": "https://discord.com/api/webhooks/123456789012345678/replacement-token"
}

Example hidden-value clear:

json
{
  "clearWebhookUrl": true
}

Configured HTTP API port update semantics

Call it with:

PATCH /v1/ports/:portId

Behavior:

  • omit authToken to preserve the current hidden auth token
  • set authToken to a new non-empty value to replace the current hidden value
  • set clearAuthToken: true to clear the current hidden value
  • baseUrl, label, description, authHeaderName, and authTokenPrefix can be updated without re-entering the hidden value
  • set dockAutoUpdate on a Dock-imported port to choose whether Harbor should keep that port synced to newer Dock versions
  • imported Dock action sync keeps actions enabled by default
  • clearing the hidden auth token leaves the port record in place but marks it degraded until a new hidden value is saved

Example hidden-value replacement:

json
{
  "authToken": "replacement-secret-token"
}

Example hidden-value clear:

json
{
  "clearAuthToken": true
}

Example action request

json
{
  "input": {
    "message": "hello"
  },
  "requestedBy": {
    "kind": "agent",
    "name": "openclaw"
  }
}

Templated path example:

json
{
  "input": {
    "path": {
      "owner": "BreakwaterNinja",
      "repo": "harbor"
    },
    "body": {
      "title": "Add bounded path templates",
      "body": "Document repo-scoped GitHub actions."
    }
  },
  "requestedBy": {
    "kind": "agent",
    "name": "openclaw"
  }
}

Call it with:

POST /v1/actions/system.echo/execute

Example action result

json
{
  "actionId": "system.echo",
  "portId": "system-local",
  "status": "approved",
  "executionId": "exec_123",
  "decision": "allow",
  "output": {
    "echoedInput": {
      "message": "hello"
    }
  }
}

Example configured action result

json
{
  "actionId": "<port-id>.echo-remote",
  "portId": "<port-id>",
  "status": "approved",
  "executionId": "exec_321",
  "decision": "allow",
  "output": {
    "remoteOk": true,
    "remoteStatus": 200,
    "targetOrigin": "http://127.0.0.1:11821",
    "routeKey": "echo_remote",
    "remoteBody": {
      "ok": true,
      "route": "echo"
    }
  }
}

Example Discord action result

json
{
  "actionId": "<discord-port-id>.send-message",
  "portId": "<discord-port-id>",
  "status": "approved",
  "executionId": "exec_654",
  "decision": "allow",
  "output": {
    "delivered": true,
    "remoteStatus": 200,
    "targetHost": "discord.com",
    "messageId": "135791357913579135"
  }
}

Example HTTP API action result

json
{
  "actionId": "<http-api-port-id>.say-hello",
  "portId": "<http-api-port-id>",
  "status": "approved",
  "executionId": "exec_765",
  "decision": "allow",
  "output": {
    "delivered": true,
    "remoteStatus": 200,
    "targetOrigin": "http://127.0.0.1:18081",
    "routeKey": "say_hello",
    "remoteBody": {
      "ok": true,
      "message": "hello from custom-http-api"
    }
  }
}

Example approval-required result

json
{
  "actionId": "system.rotate-token",
  "portId": "system-local",
  "status": "pending_approval",
  "executionId": "exec_456",
  "decision": "require_approval",
  "approvalId": "approval_123",
  "approvalStatus": "pending",
  "error": {
    "code": "APPROVAL_REQUIRED",
    "message": "Token rotation is intentionally approval-gated in the MVP."
  }
}

Example approval record

json
{
  "id": "approval_123",
  "status": "approved",
  "actionId": "system.rotate-token",
  "portId": "system-local",
  "policyDecision": "require_approval",
  "policyReason": "Token rotation is intentionally approval-gated in the MVP.",
  "requestedAt": "2026-03-23T19:00:00.000Z",
  "resolvedAt": "2026-03-23T19:01:00.000Z",
  "requestedBy": {
    "kind": "script",
    "name": "powershell-example"
  },
  "resolvedBy": {
    "kind": "user",
    "name": "local-operator"
  },
  "requestInput": {
    "reason": "rotate local test token"
  },
  "decisionNote": "approved locally",
  "execution": {
    "status": "succeeded",
    "executionId": "exec_987",
    "executedAt": "2026-03-23T19:01:01.000Z",
    "output": {
      "requested": true
    }
  },
  "createdAt": "2026-03-23T19:00:00.000Z",
  "updatedAt": "2026-03-23T19:01:01.000Z"
}

Example denied result

json
{
  "actionId": "system.shutdown",
  "portId": "system-local",
  "status": "denied",
  "executionId": "exec_789",
  "decision": "deny",
  "error": {
    "code": "POLICY_DENIED",
    "message": "Shutdown remains denied in the MVP to prove hard-stop policy behavior."
  }
}

Example audit event

json
{
  "id": "audit_123",
  "type": "action_executed",
  "actionId": "system.echo",
  "portId": "system-local",
  "decision": "allow",
  "requestedBy": {
    "kind": "agent",
    "name": "openclaw"
  },
  "createdAt": "2026-03-23T17:00:00.000Z",
  "detail": {
    "executionId": "exec_123"
  }
}

Approval lifecycle events now include:

  • approval_requested
  • approval_approved
  • approval_rejected
  • approval_execution_started
  • approval_execution_succeeded
  • approval_execution_failed

Configured-port update lifecycle events now include:

  • port_created
  • port_updated
  • port_hidden_value_replaced
  • port_hidden_value_cleared

Classified outbound execution failures

When a real outbound configured action fails, Harbor returns a safe ACTION_EXECUTION_FAILED payload with bounded details.

Current failure kinds:

  • timeout
  • network_error
  • remote_4xx
  • remote_5xx
  • invalid_remote_response
  • misconfigured_port

Example bounded failure:

json
{
  "code": "ACTION_EXECUTION_FAILED",
  "message": "The Harbor action failed during execution.",
  "details": {
    "actionId": "<discord-port-id>.send-message",
    "message": "Discord webhook responded with status 404.",
    "failureKind": "remote_4xx",
    "remoteStatus": 404,
    "retryable": false
  }
}

Current seeded built-in actions

  • system.echo => allow
  • system.time => allow
  • system.rotate-token => require approval
  • system.shutdown => deny

Configured webhook policy defaults

  • <port-id>.echo-remote => allow
  • <port-id>.send-event => require approval
  • <discord-port-id>.send-message => allow
  • <http-api-port-id>.say-hello => allow
  • <http-api-port-id>.echo => allow

This keeps one configured action immediately executable for local verification while proving that configured outbound actions still go through Harbor Guard. /_mock/webhook/* is a test-only local verification surface and not part of Harbor's intended public connector model.

Cloud API starter surface

Health

  • GET /health

Enrollment

  • POST /v1/enroll

License

  • POST /v1/license/validate

Account

  • GET /v1/account/self

SDK goals

  • simple install
  • clean typed methods
  • no surprises
  • map directly to Harbor Node action model