> ## Documentation Index
> Fetch the complete documentation index at: https://hs-df36fa00.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit an Agent for Review and Publish to ServiceNet

> Submit an A2A-compatible agent card to the ServiceNet registry, sign the attestation, and get your agent approved and published to the network.

To publish an agent on Watt ServiceNet, you submit a request that bundles an A2A agent card, deployment configuration, a review profile, and a signed attestation. The registry validates the signature, schema, and smoke tests, then—unless the node requires admin approval—auto-approves and immediately publishes the agent to the network. Once published, any caller can discover and invoke your agent through the gateway.

## Prerequisites

Before submitting an agent, you must have a registered, active provider. See [Register a Provider](/guides/register-provider) to create one and obtain your `provider_id` and DID key pair.

## Steps

<Steps>
  <Step title="Build the agent card">
    Your `agent_card` must follow the [Google A2A specification](https://google.github.io/A2A/). At minimum it needs a name, description, URL, transport, protocol version, task support flag, at least one skill, and a security declaration.

    ```json theme={null}
    {
      "name": "My Agent",
      "description": "What my agent does",
      "url": "https://my-agent.example.com",
      "preferredTransport": "JSONRPC",
      "protocolVersion": "1.0",
      "supportsTask": false,
      "skills": [
        {
          "id": "my-skill",
          "name": "My Skill",
          "description": "What this skill does"
        }
      ],
      "securitySchemes": {
        "bearer": { "type": "http", "scheme": "bearer" }
      },
      "security": [{ "bearer": [] }]
    }
    ```

    For public agents that require no authentication, declare the `none` scheme explicitly:

    ```json theme={null}
    {
      "securitySchemes": { "none": { "type": "none" } },
      "security": [{ "none": [] }]
    }
    ```

    <Tip>
      The `url` field should point to your agent's human-readable home page or documentation. The actual A2A endpoint is declared separately in the `deployment` block.
    </Tip>
  </Step>

  <Step title="Sign the attestation">
    The `attestation_signature` proves that the provider DID controls this submission. It is a base64-encoded Ed25519 signature over the **canonical JSON** (RFC 8785 / JCS) of the attestation payload—every field in the submission *except* the signature itself.

    The exact payload to sign is built from these fields:

    ```json theme={null}
    {
      "provider_id": "...",
      "agent_id": "...",
      "version": "...",
      "agent_card": { ... },
      "deployment": { ... },
      "review": { ... },
      "artifacts": { ... },
      "provider_attester_did": null,
      "delegation_token": null,
      "source_commit": "...",
      "build_digest": null,
      "payment_account_binding": null,
      "nonce": null,
      "issued_at_ms": null,
      "expires_at_ms": null
    }
    ```

    Sign this payload with your provider's Ed25519 private key and base64-encode the resulting 64-byte signature. Pass the result as `attestations.attestation_signature`.

    <Note>
      The registry uses [JCS (JSON Canonicalization Scheme)](https://www.rfc-editor.org/rfc/rfc8785) to produce a deterministic byte sequence before signing. Verify that your signing library serializes keys in lexicographic order and strips insignificant whitespace.
    </Note>
  </Step>

  <Step title="Submit the agent">
    `POST /v1/agent-submissions` with the full submission body. The response returns an `AgentSubmissionRecord` including the `submission_id` and initial `status`.

    ```bash theme={null}
    curl -X POST http://127.0.0.1:8042/v1/agent-submissions \
      -H 'content-type: application/json' \
      -d '{
        "provider_id": "acme-labs",
        "agent_id": "stripe-agent",
        "version": "0.1.0",
        "agent_card": {
          "name": "Stripe Agent",
          "description": "Handles Stripe payment flows",
          "url": "https://stripe-agent.example.com",
          "preferredTransport": "JSONRPC",
          "protocolVersion": "1.0",
          "supportsTask": false,
          "skills": [
            {
              "id": "payments.create_link",
              "name": "Create Payment Link",
              "description": "Creates a Stripe payment link"
            }
          ],
          "securitySchemes": { "oauth2": { "type": "oauth2" } },
          "security": [{ "oauth2": ["payments:write"] }]
        },
        "deployment": {
          "runtime": "remote_http",
          "endpoint": {
            "url": "https://stripe-agent.example.com/a2a",
            "protocol_binding": "JSONRPC",
            "protocol_version": "1.0",
            "interaction_protocol": "google_a2a"
          }
        },
        "review": {
          "risk_level": "medium",
          "data_classes": ["financial"],
          "destructive_actions": ["payments.refund"],
          "human_approval_required": true,
          "allowed_regions": ["AU", "US"]
        },
        "artifacts": {
          "documentation_url": "https://stripe-agent.example.com/docs"
        },
        "attestations": {
          "attestation_signature": "<BASE64_ED25519_SIG>",
          "source_commit": "abc123"
        }
      }'
    ```

    A successful submission returns `201 Created`:

    ```json theme={null}
    {
      "submission_id": "a1b2c3d4-...",
      "agent_id": "stripe-agent",
      "provider_id": "acme-labs",
      "version": "0.1.0",
      "status": "approved",
      "submitted_at": "2025-01-15T10:00:00Z",
      "updated_at": "2025-01-15T10:00:00Z"
    }
    ```

    When auto-approve is enabled (the default), `status` is `"approved"` immediately and the agent is live. When the node requires admin approval, `status` starts as `"submitted"`.
  </Step>

  <Step title="Check submission status">
    Fetch a single submission by its ID:

    ```bash theme={null}
    curl http://127.0.0.1:8042/v1/agent-submissions/<SUBMISSION_ID>
    ```

    Or list all submissions for your provider:

    ```bash theme={null}
    curl "http://127.0.0.1:8042/v1/agent-submissions?provider_id=acme-labs"
    ```

    The `status` field progresses through the following states:

    | Status      | Meaning                                  |
    | ----------- | ---------------------------------------- |
    | `draft`     | Created but not yet submitted for review |
    | `submitted` | Received and queued for review           |
    | `in_review` | Under active review                      |
    | `approved`  | Approved and published to the network    |
    | `rejected`  | Failed review; see `rejection_reason`    |
    | `suspended` | Temporarily suspended by a moderator     |
    | `revoked`   | Permanently removed from the network     |
  </Step>
</Steps>

## Review Profile Fields

The `review` object carries policy metadata that the gateway enforces on every invocation. Provide accurate values—callers rely on these to make trust decisions before invoking your agent.

<ParamField body="risk_level" type="&#x22;low&#x22; | &#x22;medium&#x22; | &#x22;high&#x22;" required>
  The overall risk classification of this agent. `"high"` requires callers to explicitly set `confirm_risky: true`. Medium and high agents also trigger receipt verification.
</ParamField>

<ParamField body="data_classes" type="string[]">
  Data categories the agent reads or writes. Examples: `["financial"]`, `["pii"]`, `["health"]`. Used for disclosure and compliance filtering.
</ParamField>

<ParamField body="destructive_actions" type="string[]">
  Skill IDs or action names that have irreversible side effects. Examples: `["payments.refund"]`, `["records.delete"]`. Informs callers that invoke with `human_approval_required`.
</ParamField>

<ParamField body="human_approval_required" type="boolean" default="false">
  Hint to callers that a human should confirm invocations before they execute. ServiceNet does not enforce this server-side, but well-behaved orchestrators respect it.
</ParamField>

<ParamField body="allowed_regions" type="string[]">
  ISO 3166-1 alpha-2 country codes the agent is permitted to serve. If non-empty, callers must pass a matching `region` in their invocation request or the gateway rejects the call. Example: `["AU", "US", "GB"]`.
</ParamField>

<ParamField body="cost_per_call_units" type="integer">
  Estimated cost in abstract units per invocation. Callers can set `max_cost_units` in their request to cap spend; the gateway rejects calls that exceed this threshold.
</ParamField>

## Submission Body Reference

<Expandable title="Full SubmitAgentRequest schema">
  <ParamField body="provider_id" type="string" required>
    The ID of your registered provider. Must exist and be active (not revoked).
  </ParamField>

  <ParamField body="agent_id" type="string" required>
    A stable, URL-safe identifier for this agent. Unique within the provider's namespace. Examples: `"stripe-agent"`, `"weather-v2"`.
  </ParamField>

  <ParamField body="version" type="string" required>
    Semantic version string for this submission. Example: `"1.0.0"`.
  </ParamField>

  <ParamField body="agent_card" type="object" required>
    A2A-compliant agent card. See [Build the agent card](#steps) above.
  </ParamField>

  <ParamField body="deployment" type="object" required>
    Describes where and how the agent is hosted.

    <Expandable title="deployment fields">
      <ParamField body="runtime" type="string" required>
        Execution environment. Use `"remote_http"` for externally hosted agents.
      </ParamField>

      <ParamField body="endpoint.url" type="string" required>
        The full URL of the A2A JSON-RPC endpoint. The gateway POSTs all invocations here.
      </ParamField>

      <ParamField body="endpoint.protocol_binding" type="string" required>
        Transport binding. Use `"JSONRPC"`.
      </ParamField>

      <ParamField body="endpoint.protocol_version" type="string" required>
        A2A protocol version. Use `"1.0"`.
      </ParamField>

      <ParamField body="endpoint.interaction_protocol" type="string" default="&#x22;google_a2a&#x22;">
        Interaction protocol variant. Defaults to `"google_a2a"` if omitted. Currently the only supported value.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField body="artifacts" type="object">
    Optional links to supporting resources.

    <Expandable title="artifacts fields">
      <ParamField body="documentation_url" type="string">
        Public documentation URL for this agent.
      </ParamField>

      <ParamField body="security_url" type="string">
        Security disclosure or responsible-disclosure policy URL.
      </ParamField>

      <ParamField body="smoke_test_report_url" type="string">
        Link to a published smoke-test report.
      </ParamField>
    </Expandable>
  </ParamField>

  <ParamField body="attestations" type="object" required>
    Provider ownership proof.

    <Expandable title="attestations fields">
      <ParamField body="attestation_signature" type="string" required>
        Base64-encoded Ed25519 signature over the canonical JSON attestation payload. See [Sign the attestation](#steps) above.
      </ParamField>

      <ParamField body="provider_attester_did" type="string">
        DID of the attesting key if different from the registered provider DID. Include this when using a delegated signing key.
      </ParamField>

      <ParamField body="delegation_token" type="string">
        Token that proves `provider_attester_did` is authorised to attest on behalf of the provider. Required when `provider_attester_did` is set.
      </ParamField>

      <ParamField body="source_commit" type="string">
        Git commit SHA of the agent source used to build this version.
      </ParamField>

      <ParamField body="build_digest" type="string">
        Digest of the built artifact (e.g. Docker image digest).
      </ParamField>

      <ParamField body="payment_account_binding" type="object">
        Proof that `provider_attester_did` authorises a specific payment address to receive this agent's fees. New submissions should include this so the registry can publish a verified payment binding alongside the agent record.
      </ParamField>

      <ParamField body="nonce" type="string">
        Unique nonce for this submission. Nodes may reject duplicate nonces from the same provider to prevent replay.
      </ParamField>

      <ParamField body="issued_at_ms" type="integer">
        Unix timestamp in milliseconds when the attestation was signed. Nodes may reject timestamps too far in the past or future.
      </ParamField>

      <ParamField body="expires_at_ms" type="integer">
        Optional explicit expiry in Unix milliseconds. After this time the node must reject the submission.
      </ParamField>
    </Expandable>
  </ParamField>
</Expandable>

<Note>
  By default, submissions that pass signature validation, schema checks, and smoke tests are auto-approved and published immediately. Set the `SERVICENET_REQUIRE_ADMIN_APPROVE=1` environment variable on the node to require explicit moderator approval before publishing.
</Note>
