{
  "openapi": "3.1.0",
  "info": {
    "title": "VertoDigital Agent API (v1, pinned)",
    "version": "1.0.0",
    "description": "Version-pinned contract for the VertoDigital Agent API. Identical operations to /agent/openapi.json (the unversioned, always-current alias - currently also v1), served under /agent/v1/* instead of /agent/*. Integrate against this document if you want your integration to keep working unchanged after a future v2 ships. Deprecation policy: when v1 is eventually superseded, its responses will start carrying a Deprecation: true header and a Sunset header with the retirement date, announced here, in /agent/openapi.json, and in /llms.txt at least 90 days ahead of removal. Every response also carries an API-Version header (currently 1) so a caller can detect which version actually served it.",
    "termsOfService": "https://vertodigital.com/terms",
    "contact": {
      "name": "VertoDigital",
      "url": "https://vertodigital.com/contact",
      "email": "paul.green@vertodigital.com"
    }
  },
  "servers": [
    {
      "url": "https://vertodigital.com"
    }
  ],
  "security": [],
  "x-versioning-policy": {
    "scheme": "url-path-major-version",
    "current_version": "1",
    "recommended_integration_path": "/agent",
    "pinned_version_paths": {
      "1": "/agent/v1"
    },
    "version_response_header": "API-Version",
    "deprecation_process": {
      "description": "A breaking change ships as a new major version path (e.g. /agent/v2) alongside the existing one - nothing on the current version breaks the day a new version ships. Only once a version is actually retired do its responses carry the two headers below, with the notice period given here as a minimum.",
      "minimum_notice_days": 90,
      "response_headers": [
        "Deprecation",
        "Sunset"
      ],
      "announced_at": [
        "https://vertodigital.com/agent/openapi.json#info.description",
        "https://vertodigital.com/llms.txt",
        "https://vertodigital.com/developers"
      ]
    }
  },
  "externalDocs": {
    "description": "LLM-readable site index",
    "url": "https://vertodigital.com/llms.txt"
  },
  "tags": [
    {
      "name": "meta",
      "description": "Service status"
    },
    {
      "name": "contact",
      "description": "Contact and enquiry submission"
    }
  ],
  "paths": {
    "/agent/v1/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "meta"
        ],
        "summary": "Service health check",
        "description": "Anonymous, unauthenticated, no rate limit. Returns service status and a map of related discovery endpoints. Does not probe upstream dependencies (HubSpot, Resend) or report which secrets are configured.",
        "responses": {
          "200": {
            "description": "Service is reachable",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "description": "Methods this endpoint accepts",
                "schema": {
                  "type": "string",
                  "const": "GET, HEAD, OPTIONS"
                }
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    },
    "/agent/v1/contact": {
      "post": {
        "operationId": "submitEnquiry",
        "tags": [
          "contact"
        ],
        "summary": "Submit a contact enquiry on behalf of a user",
        "description": "Anonymous, rate-limited (5 requests/minute and 50 requests/day per caller IP). No authentication required. Returns 202 once the enquiry is accepted for delivery; delivery itself (CRM + internal notification) is best-effort and not guaranteed by this response. Supports Idempotency-Key: retrying the same request with the same key and body replays the original response (marked with an Idempotent-Replayed header) instead of submitting a second enquiry - safe to do after a timeout or dropped connection. Reusing a key with a different body returns 409.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Caller-generated key (e.g. a UUID), up to 200 characters. Retrying this call with the same key and an identical request body within 24h replays the first call's response rather than submitting a duplicate enquiry. Reusing the key with a different body returns 409.",
            "schema": { "type": "string", "maxLength": 200 }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["fromName", "fromEmail", "message"],
                "properties": {
                  "fromName": { "type": "string", "minLength": 1, "maxLength": 100 },
                  "fromEmail": { "type": "string", "format": "email", "maxLength": 254 },
                  "organization": { "type": "string", "maxLength": 100 },
                  "topic": {
                    "type": "string",
                    "enum": ["assessment", "services", "partnership", "press", "careers", "general"],
                    "default": "general"
                  },
                  "message": { "type": "string", "minLength": 10, "maxLength": 2000 },
                  "agentId": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Self-asserted identifier for the calling agent. Not verified."
                  }
                }
              },
              "example": {
                "fromName": "Ada Lovelace",
                "fromEmail": "ada@example.com",
                "organization": "Analytical Engines Ltd",
                "topic": "sales",
                "message": "We'd like to discuss a marketing analytics engagement.",
                "agentId": "agent-123"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Enquiry accepted",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotentReplayed"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reused with a different request body",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body too large",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the caller may retry",
                "schema": {
                  "type": "integer"
                }
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimited"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Upstream delivery error",
            "headers": {
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "deprecated": false
      }
    }
  },
  "components": {
    "headers": {
      "ApiVersion": {
        "description": "The major API version that served this response (see x-versioning-policy at the document root, and the policy note in info.description).",
        "schema": {
          "type": "string",
          "const": "1"
        }
      },
      "Deprecation": {
        "description": "Present only once this API version has been deprecated (per x-versioning-policy); absent otherwise.",
        "schema": {
          "type": "string",
          "const": "true"
        }
      },
      "Sunset": {
        "description": "Present only once this API version has been deprecated - the HTTP-date this version stops being served (RFC 8594). Absent otherwise.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "RateLimitLimit": {
        "description": "The request quota for the current rate-limit window (RFC draft-ietf-httpapi-ratelimit-headers).",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current rate-limit window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the current rate-limit window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplayed": {
        "description": "Present and \"true\" only when this response is a replay of an earlier call with the same Idempotency-Key; absent on a first-time response.",
        "schema": { "type": "string", "const": "true" }
      }
    },
    "schemas": {
      "ContactAccepted": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "accepted"
          },
          "reference": {
            "type": "string",
            "pattern": "^enq_[0-9a-f]{16}$"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "invalid_request"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "rate_limited"
          },
          "message": {
            "type": "string"
          },
          "retryAfter": {
            "type": "integer",
            "description": "Seconds"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ]
          },
          "service": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "time": {
            "type": "string",
            "format": "date-time"
          },
          "endpoints": {
            "type": "object",
            "properties": {
              "contact": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "catalog": {
                "type": "string",
                "format": "uri"
              },
              "mcp": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}
