{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "/schemas/3.1.0-rc.4/core/account.json",
  "title": "Account",
  "description": "A billing account representing the relationship between a buyer and seller. The account determines rate cards, payment terms, and billing entity.",
  "type": "object",
  "properties": {
    "account_id": {
      "type": "string",
      "description": "Unique identifier for this account",
      "x-entity": "account"
    },
    "name": {
      "type": "string",
      "description": "Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')"
    },
    "advertiser": {
      "type": "string",
      "description": "The advertiser whose rates apply to this account"
    },
    "billing_proxy": {
      "type": "string",
      "description": "Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)"
    },
    "status": {
      "$ref": "/schemas/3.1.0-rc.4/enums/account-status.json",
      "description": "Account lifecycle status. See the Accounts Protocol overview for the operations matrix showing which tasks are permitted in each state."
    },
    "brand": {
      "$ref": "/schemas/3.1.0-rc.4/core/brand-ref.json",
      "description": "Brand reference identifying the advertiser"
    },
    "operator": {
      "type": "string",
      "description": "Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.",
      "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$",
      "x-entity": "operator"
    },
    "billing": {
      "$ref": "/schemas/3.1.0-rc.4/enums/billing-party.json",
      "description": "Who is invoiced on this account. See billing_entity for the invoiced party's business details."
    },
    "billing_entity": {
      "$ref": "/schemas/3.1.0-rc.4/core/business-entity.json",
      "description": "Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only)."
    },
    "rate_card": {
      "type": "string",
      "description": "Identifier for the rate card applied to this account"
    },
    "payment_terms": {
      "$ref": "/schemas/3.1.0-rc.4/enums/payment-terms.json",
      "description": "Payment terms agreed for this account. Binding for all invoices when the account is active."
    },
    "credit_limit": {
      "type": "object",
      "description": "Maximum outstanding balance allowed",
      "properties": {
        "amount": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "type": "string",
          "pattern": "^[A-Z]{3}$"
        }
      },
      "required": [
        "amount",
        "currency"
      ]
    },
    "setup": {
      "type": "object",
      "description": "Present when status is 'pending_approval'. Contains next steps for completing account activation.",
      "properties": {
        "url": {
          "type": "string",
          "format": "uri",
          "description": "URL where the human can complete the required action (credit application, legal agreement, add funds)."
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what's needed."
        },
        "expires_at": {
          "type": "string",
          "format": "date-time",
          "description": "When this setup link expires."
        }
      },
      "required": [
        "message"
      ],
      "additionalProperties": true
    },
    "account_scope": {
      "$ref": "/schemas/3.1.0-rc.4/enums/account-scope.json"
    },
    "governance_agents": {
      "type": "array",
      "description": "Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.",
      "items": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Governance agent endpoint URL. Must use HTTPS."
          }
        },
        "required": [
          "url"
        ],
        "additionalProperties": false
      },
      "minItems": 1,
      "maxItems": 1
    },
    "reporting_bucket": {
      "type": "object",
      "description": "Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities).",
      "properties": {
        "protocol": {
          "$ref": "/schemas/3.1.0-rc.4/enums/cloud-storage-protocol.json",
          "description": "Cloud storage protocol"
        },
        "bucket": {
          "type": "string",
          "description": "Bucket or container name",
          "minLength": 3,
          "maxLength": 63,
          "pattern": "^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$"
        },
        "prefix": {
          "type": "string",
          "description": "Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.",
          "maxLength": 512,
          "pattern": "^[a-zA-Z0-9/_.-]+$",
          "examples": [
            "accounts/pinnacle/adcp",
            "reporting/2024"
          ]
        },
        "region": {
          "type": "string",
          "description": "Cloud region for the bucket",
          "maxLength": 64,
          "pattern": "^[a-z0-9-]+$",
          "examples": [
            "us-east-1",
            "europe-west1"
          ]
        },
        "format": {
          "type": "string",
          "enum": [
            "jsonl",
            "csv",
            "parquet",
            "avro",
            "orc"
          ],
          "description": "File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).",
          "default": "jsonl"
        },
        "compression": {
          "type": "string",
          "enum": [
            "gzip",
            "none"
          ],
          "description": "Compression applied to delivered files",
          "default": "gzip"
        },
        "file_retention_days": {
          "type": "integer",
          "description": "How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.",
          "minimum": 1,
          "examples": [
            14,
            30,
            90
          ]
        },
        "setup_instructions": {
          "type": "string",
          "format": "uri",
          "pattern": "^https://",
          "description": "URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery."
        }
      },
      "required": [
        "protocol",
        "bucket",
        "file_retention_days"
      ],
      "additionalProperties": false
    },
    "sandbox": {
      "type": "boolean",
      "description": "When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account."
    },
    "notification_configs": {
      "type": "array",
      "description": "Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.",
      "items": {
        "$ref": "/schemas/3.1.0-rc.4/core/notification-config.json"
      },
      "maxItems": 16
    },
    "ext": {
      "$ref": "/schemas/3.1.0-rc.4/core/ext.json"
    }
  },
  "required": [
    "account_id",
    "name",
    "status"
  ],
  "additionalProperties": true,
  "examples": [
    {
      "description": "Direct advertiser account",
      "data": {
        "account_id": "acc_acme_direct",
        "name": "Acme",
        "advertiser": "Acme Corp",
        "brand": {
          "domain": "acme-corp.com"
        },
        "operator": "acme-corp.com",
        "status": "active",
        "billing": "operator",
        "account_scope": "brand",
        "rate_card": "acme_vip_2024",
        "payment_terms": "net_30"
      }
    },
    {
      "description": "Advertiser account with agency billing proxy",
      "data": {
        "account_id": "acc_acme_pinnacle",
        "name": "Acme c/o Pinnacle",
        "advertiser": "Acme Corp",
        "billing_proxy": "Pinnacle Media",
        "brand": {
          "domain": "acme-corp.com"
        },
        "operator": "pinnacle-media.com",
        "status": "active",
        "billing": "operator",
        "account_scope": "operator_brand",
        "rate_card": "acme_vip_2024",
        "payment_terms": "net_60"
      }
    },
    {
      "description": "Agency as direct buyer",
      "data": {
        "account_id": "acc_pinnacle",
        "name": "Pinnacle",
        "advertiser": "Pinnacle Media",
        "brand": {
          "domain": "pinnacle-media.com"
        },
        "operator": "pinnacle-media.com",
        "status": "active",
        "billing": "operator",
        "account_scope": "operator",
        "rate_card": "agency_standard",
        "payment_terms": "net_45"
      }
    },
    {
      "description": "Account with brand identity and operator (via sync_accounts)",
      "data": {
        "account_id": "acc_spark_001",
        "name": "Spark (via Pinnacle)",
        "advertiser": "Nova Brands",
        "status": "active",
        "brand": {
          "domain": "nova-brands.com",
          "brand_id": "spark"
        },
        "operator": "pinnacle-media.com",
        "billing": "agent",
        "account_scope": "operator_brand",
        "payment_terms": "net_30"
      }
    },
    {
      "description": "Pending account awaiting seller approval",
      "data": {
        "account_id": "acc_glow_pending",
        "name": "Glow",
        "advertiser": "Nova Brands",
        "status": "pending_approval",
        "brand": {
          "domain": "nova-brands.com",
          "brand_id": "glow"
        },
        "operator": "pinnacle-media.com",
        "billing": "operator",
        "account_scope": "brand"
      }
    },
    {
      "description": "Agency operates but advertiser is billed directly with structured billing entity",
      "data": {
        "account_id": "acc_acme_direct_bill",
        "name": "Acme (billed direct)",
        "advertiser": "Acme Corp",
        "brand": {
          "domain": "acme-corp.com"
        },
        "operator": "pinnacle-media.com",
        "status": "active",
        "billing": "advertiser",
        "billing_entity": {
          "legal_name": "Acme Corporation GmbH",
          "vat_id": "DE987654321",
          "address": {
            "street": "Hauptstrasse 42",
            "city": "Munich",
            "postal_code": "80331",
            "country": "DE"
          },
          "contacts": [
            {
              "role": "billing",
              "name": "AP Department",
              "email": "billing@acme-corp.com"
            }
          ]
        },
        "account_scope": "operator_brand",
        "payment_terms": "net_30"
      }
    }
  ]
}
