{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://platinumcard.ai/schema/portfolio-v0.1.1.json",
  "title": "Portfolio",
  "description": "Declaration checked by POST https://validate.platinumcard.ai. A valid result means the document is well-formed. The endpoint does not evaluate a payment, and it does not choose a card. Recorded fields are accepted and are not compared to an amount, a merchant, or a category.",
  "x-pc-ai-validate": {
    "endpoint": "https://validate.platinumcard.ai",
    "evaluates_payments": false,
    "secret_key_match": "case-insensitive exact property name at any depth",
    "max_depth": 64,
    "mcc_range_order": "start <= end",
    "mcc_inverted_code": "INVALID_MCC_RANGE",
    "checked_limits": ["daily_limit", "monthly_limit", "per_transaction_limit", "lifetime_limit", "approval_threshold"],
    "limit_code": "INVALID_LIMIT",
    "unknown_constraint_key_code": "INVALID_FIELD",
    "secret_keys": [
      "pan",
      "primary_account_number",
      "card_number",
      "cvv",
      "cvc",
      "cid",
      "cvv2",
      "cvc2",
      "security_code",
      "pin",
      "token",
      "access_token",
      "refresh_token",
      "api_key",
      "apikey",
      "bearer",
      "authorization",
      "private_key",
      "secret",
      "password",
      "account_number",
      "iban",
      "routing_number",
      "ssn",
      "tax_id",
      "social_security_number"
    ],
    "recorded_not_evaluated": [
      "owner",
      "declared_at",
      "product_tier",
      "issuer",
      "product_name",
      "label",
      "allowed_merchants",
      "blocked_merchants",
      "require_human_approval",
      "allowed_countries",
      "blocked_countries",
      "preferences",
      "optimization_policy"
    ]
  },
  "allOf": [{ "$ref": "#/$defs/SecretFree" }],
  "type": "object",
  "required": ["version", "cards"],
  "properties": {
    "version": {
      "type": "string",
      "pattern": "^\\s*0\\.[0-9]+\\.[0-9]+\\s*$",
      "description": "Portfolio schema version. The endpoint trims the string and accepts major 0 only."
    },
    "owner": {
      "description": "Recorded. Human or organization that granted authority. The endpoint does not check the type."
    },
    "declared_at": {
      "description": "Recorded. Intended as an ISO 8601 timestamp. The endpoint does not check the type or the format."
    },
    "default_card": {
      "description": "Recorded local card id. When the value is not null and not empty, the endpoint requires it to equal some card.id (INVALID_REFERENCE)."
    },
    "cards": {
      "type": "array",
      "items": { "$ref": "#/$defs/Card" }
    },
    "optimization_policy": {
      "description": "Recorded. The endpoint does not check mode, routing flags, or max_cards_to_consider, and it does not use this object to choose a card."
    }
  },
  "$defs": {
    "NotASecretName": {
      "not": {
        "pattern": "^(?:[Pp][Aa][Nn]|[Pp][Rr][Ii][Mm][Aa][Rr][Yy]_[Aa][Cc][Cc][Oo][Uu][Nn][Tt]_[Nn][Uu][Mm][Bb][Ee][Rr]|[Cc][Aa][Rr][Dd]_[Nn][Uu][Mm][Bb][Ee][Rr]|[Cc][Vv][Vv]|[Cc][Vv][Cc]|[Cc][Ii][Dd]|[Cc][Vv][Vv]2|[Cc][Vv][Cc]2|[Ss][Ee][Cc][Uu][Rr][Ii][Tt][Yy]_[Cc][Oo][Dd][Ee]|[Pp][Ii][Nn]|[Tt][Oo][Kk][Ee][Nn]|[Aa][Cc][Cc][Ee][Ss][Ss]_[Tt][Oo][Kk][Ee][Nn]|[Rr][Ee][Ff][Rr][Ee][Ss][Hh]_[Tt][Oo][Kk][Ee][Nn]|[Aa][Pp][Ii]_[Kk][Ee][Yy]|[Aa][Pp][Ii][Kk][Ee][Yy]|[Bb][Ee][Aa][Rr][Ee][Rr]|[Aa][Uu][Tt][Hh][Oo][Rr][Ii][Zz][Aa][Tt][Ii][Oo][Nn]|[Pp][Rr][Ii][Vv][Aa][Tt][Ee]_[Kk][Ee][Yy]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]|[Aa][Cc][Cc][Oo][Uu][Nn][Tt]_[Nn][Uu][Mm][Bb][Ee][Rr]|[Ii][Bb][Aa][Nn]|[Rr][Oo][Uu][Tt][Ii][Nn][Gg]_[Nn][Uu][Mm][Bb][Ee][Rr]|[Ss][Ss][Nn]|[Tt][Aa][Xx]_[Ii][Dd]|[Ss][Oo][Cc][Ii][Aa][Ll]_[Ss][Ee][Cc][Uu][Rr][Ii][Tt][Yy]_[Nn][Uu][Mm][Bb][Ee][Rr])$"
      }
    },
    "SecretFree": {
      "description": "Any JSON value whose object keys are not on the secret-key denylist. The endpoint compares names case-insensitively, at every depth, before other checks.",
      "anyOf": [
        {
          "type": "object",
          "propertyNames": { "$ref": "#/$defs/NotASecretName" },
          "additionalProperties": { "$ref": "#/$defs/SecretFree" }
        },
        {
          "type": "array",
          "items": { "$ref": "#/$defs/SecretFree" }
        },
        { "type": ["string", "number", "boolean", "null"] }
      ]
    },
    "Card": {
      "type": "object",
      "required": ["id", "network", "status"],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Stable local identifier, unique within the portfolio. A repeated id fails on the later card at cards.N.id (DUPLICATE_CARD_ID)."
        },
        "network": {
          "type": "string",
          "enum": ["visa", "mastercard", "amex", "discover", "other"]
        },
        "product_tier": {
          "description": "Recorded. Callers usually send platinum, gold, infinite, world_elite, signature, other, or another string. The endpoint does not check the value."
        },
        "issuer": {
          "description": "Recorded. The endpoint does not check the type."
        },
        "product_name": {
          "description": "Recorded. The endpoint does not check the type."
        },
        "label": {
          "description": "Recorded friendly name. The endpoint does not check the type."
        },
        "currency": {
          "description": "When this value is a non-empty string, the endpoint requires three uppercase letters (ISO 4217). Empty and null are accepted. Any other non-empty value is INVALID_FORMAT.",
          "anyOf": [
            { "type": "null" },
            { "type": "string", "maxLength": 0 },
            { "type": "string", "pattern": "^[A-Z]{3}$" }
          ]
        },
        "status": {
          "type": "string",
          "enum": ["active", "suspended", "expired"]
        },
        "constraints": { "$ref": "#/$defs/Constraints" },
        "preferences": {
          "description": "Recorded soft signals (preferred_for, avoid_for, priority, notes). The endpoint does not check them and does not use them to choose a card."
        }
      }
    },
    "MccEntry": {
      "type": "string",
      "pattern": "^[0-9]{4}(-[0-9]{4})?$",
      "description": "Exact code (5812) or a range (3000-3299). The pattern checks the shape. The endpoint also rejects a range whose start is greater than its end with INVALID_MCC_RANGE. A JSON Schema pattern cannot express that ordering."
    },
    "Constraints": {
      "type": "object",
      "description": "Declared limits and category rules. The endpoint checks MCC shape and range order, valid_from against valid_until, that each spend limit and the approval threshold is a number >= 0 or null (INVALID_LIMIT), and that no other key is present (INVALID_FIELD). It does not evaluate a payment against the recorded limits.",
      "additionalProperties": false,
      "properties": {
        "daily_limit": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Checked: a number >= 0 or null (INVALID_LIMIT). The endpoint does not compare it to a payment."
        },
        "monthly_limit": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Checked: a number >= 0 or null (INVALID_LIMIT). The endpoint does not compare it to a payment."
        },
        "per_transaction_limit": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Checked: a number >= 0 or null (INVALID_LIMIT). The endpoint does not compare it to a payment."
        },
        "lifetime_limit": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Checked: a number >= 0 or null (INVALID_LIMIT). The endpoint does not compare it to a payment."
        },
        "allowed_mccs": {
          "description": "When this value is an array, each entry is an MCC code or range. Any other type is ignored.",
          "anyOf": [
            { "type": "array", "items": { "$ref": "#/$defs/MccEntry" } },
            { "not": { "type": "array" } }
          ]
        },
        "blocked_mccs": {
          "description": "When this value is an array, each entry is an MCC code or range. Any other type is ignored.",
          "anyOf": [
            { "type": "array", "items": { "$ref": "#/$defs/MccEntry" } },
            { "not": { "type": "array" } }
          ]
        },
        "allowed_merchants": {
          "description": "Recorded. The endpoint does not check the type and does not match a merchant."
        },
        "blocked_merchants": {
          "description": "Recorded. The endpoint does not check the type and does not match a merchant."
        },
        "require_human_approval": {
          "description": "Recorded. The endpoint does not check the type and does not gate a payment on it."
        },
        "approval_threshold": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Checked: a number >= 0 or null (INVALID_LIMIT). The endpoint does not compare it to an amount."
        },
        "allowed_countries": {
          "description": "Recorded. Intended as ISO 3166-1 alpha-2 codes. The endpoint does not check them."
        },
        "blocked_countries": {
          "description": "Recorded. Intended as ISO 3166-1 alpha-2 codes. The endpoint does not check them."
        },
        "valid_from": {
          "description": "When both valid_from and valid_until are truthy and valid_from > valid_until, the endpoint rejects the card (INVALID_DATE_RANGE). The comparison is JavaScript greater-than. The endpoint does not check that the values are dates."
        },
        "valid_until": {
          "description": "See valid_from. The endpoint does not check that the value is a date."
        }
      }
    }
  }
}
