{
  "openapi": "3.1.0",
  "info": {
    "title": "RxApp CRM Contacts API",
    "version": "1.0.0",
    "description": "Tenant-scoped, read-only contact synchronization API."
  },
  "externalDocs": {
    "description": "RxApp Contacts API developer guide",
    "url": "https://docs.rxapp.io/api"
  },
  "servers": [{ "url": "https://api.rxapp.io" }],
  "security": [{ "apiKey": [] }],
  "paths": {
    "/v1/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "List contacts",
        "description": "Returns a stable page. A cursor must be sent by itself; its signed limit and filters are reused.",
        "parameters": [
          { "name": "updatedAfter", "in": "query", "schema": { "type": "string", "format": "date-time", "pattern": "Z$" }, "description": "Exclusive RFC 3339 UTC lower bound." },
          { "name": "contactType", "in": "query", "schema": { "type": "string", "enum": ["lead", "member"] } },
          { "name": "cursor", "in": "query", "schema": { "type": "string", "minLength": 1 }, "description": "Opaque signed continuation cursor. Cannot be combined with any other query parameter." },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 } }
        ],
        "responses": {
          "200": {
            "description": "Contact page",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" },
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateReset" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ContactList" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "503": { "$ref": "#/components/responses/ContactDataInvalid" }
        }
      }
    },
    "/v1/contacts/{contactId}": {
      "get": {
        "operationId": "getContact",
        "summary": "Get one contact",
        "parameters": [{ "name": "contactId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": {
            "description": "Contact",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "Cache-Control": { "$ref": "#/components/headers/CacheControl" },
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateReset" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Contact" } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" },
          "503": { "$ref": "#/components/responses/ContactDataInvalid" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "http", "scheme": "bearer", "bearerFormat": "RxApp API key" }
    },
    "headers": {
      "RequestId": { "description": "Random request correlation UUID.", "schema": { "type": "string", "format": "uuid" } },
      "CacheControl": { "description": "CRM responses are never stored by shared or private caches.", "schema": { "type": "string", "const": "private, no-store" } },
      "RateLimit": { "description": "Per-key requests allowed per minute.", "schema": { "type": "integer" } },
      "RateRemaining": { "description": "Requests remaining in the current window.", "schema": { "type": "integer" } },
      "RateReset": { "description": "RFC 3339 UTC reset timestamp.", "schema": { "type": "string", "format": "date-time" } },
      "RetryAfter": { "description": "Seconds until retry.", "schema": { "type": "integer", "minimum": 1 } }
    },
    "schemas": {
      "Contact": {
        "type": "object",
        "additionalProperties": false,
        "required": ["id", "contactType", "firstName", "lastName", "email", "membershipStatus", "subscriptionPlanCode", "subscriptionPlanName", "subscriptionStartedAt", "subscriptionEndsAt", "firstMembershipStartedAt", "version", "updatedAt", "deletedAt"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "contactType": { "type": "string", "enum": ["lead", "member"] },
          "firstName": { "type": ["string", "null"] },
          "lastName": { "type": ["string", "null"] },
          "email": { "type": ["string", "null"], "format": "email" },
          "membershipStatus": { "type": ["string", "null"], "enum": ["active", "pending", "inactive", null] },
          "subscriptionPlanCode": { "type": ["string", "null"] },
          "subscriptionPlanName": { "type": ["string", "null"] },
          "subscriptionStartedAt": { "type": ["string", "null"], "format": "date-time" },
          "subscriptionEndsAt": { "type": ["string", "null"], "format": "date-time" },
          "firstMembershipStartedAt": { "type": ["string", "null"], "format": "date-time" },
          "version": { "type": "integer", "minimum": 1 },
          "updatedAt": { "type": "string", "format": "date-time" },
          "deletedAt": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "ContactList": {
        "type": "object",
        "additionalProperties": false,
        "required": ["items", "page", "sync"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Contact" } },
          "page": {
            "type": "object",
            "additionalProperties": false,
            "required": ["nextCursor", "hasMore"],
            "properties": {
              "nextCursor": { "type": ["string", "null"] },
              "hasMore": { "type": "boolean" }
            }
          },
          "sync": {
            "type": "object",
            "additionalProperties": false,
            "required": ["watermark"],
            "properties": { "watermark": { "type": "string", "format": "date-time" } }
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message", "requestId"],
            "properties": {
              "code": { "type": "string", "enum": ["invalid_request", "invalid_cursor", "unauthorized", "forbidden", "not_found", "rate_limit_exceeded", "contact_data_invalid", "internal_error", "service_unavailable"] },
              "message": { "type": "string" },
              "requestId": { "type": "string", "format": "uuid" }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request or cursor", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid API key", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" }, "WWW-Authenticate": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "API key lacks contacts:read", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Contact missing or outside the authenticated tenant", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "Per-key rate limit exceeded", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" }, "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimit" }, "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateRemaining" }, "X-RateLimit-Reset": { "$ref": "#/components/headers/RateReset" }, "Retry-After": { "$ref": "#/components/headers/RetryAfter" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "InternalError": { "description": "Internal error", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "ContactDataInvalid": { "description": "Contact data is invalid or a dependency is temporarily unavailable", "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" }, "Cache-Control": { "$ref": "#/components/headers/CacheControl" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
