{
  "openapi": "3.1.0",
  "info": {
    "title": "Sakhaa Capture API",
    "version": "1.0.0",
    "description": "The supported external API for capturing customer records in Sakhaa. Signed-in CRM application routes are not part of this contract."
  },
  "servers": [
    {
      "url": "http://localhost:7200/api/v1/capture",
      "description": "Local mock or disposable test client"
    },
    {
      "url": "https://crm.sakhaa.ai/api/v1/capture",
      "description": "Production. Use only after the synthetic flow is approved."
    }
  ],
  "security": [
    {
      "ApiKey": [],
      "ApiSecret": []
    }
  ],
  "paths": {
    "/info": {
      "get": {
        "operationId": "getApiClientInfo",
        "summary": "Get the active API client settings",
        "description": "Use this endpoint as the first bounded connection check. It does not return the API secret.",
        "x-sakhaa-public": true,
        "x-sakhaa-status": "supported",
        "responses": {
          "200": {
            "description": "The API client is active and authenticated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiClientInfoResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/person": {
      "post": {
        "operationId": "capturePerson",
        "summary": "Capture one person",
        "description": "Creates or updates one canonical person through the API client's source, ownership, duplicate, and field-mapping rules.",
        "x-sakhaa-public": true,
        "x-sakhaa-status": "supported",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonCapture"
              },
              "examples": {
                "syntheticPerson": {
                  "summary": "Synthetic test record",
                  "value": {
                    "name": "API Test Person",
                    "email": "api-test@example.invalid",
                    "company": "Example Test Company",
                    "notes": "Synthetic documentation verification record"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The person was captured or an idempotent result was reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PersonCaptureResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/CaptureConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/persons/bulk": {
      "post": {
        "operationId": "capturePersonsBulk",
        "summary": "Capture up to 100 people",
        "description": "Processes each person independently and returns successful, duplicate, and failed results.",
        "x-sakhaa-public": true,
        "x-sakhaa-status": "supported",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["persons"],
                "properties": {
                  "persons": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/BulkPersonCapture"
                    }
                  }
                }
              },
              "examples": {
                "syntheticBatch": {
                  "summary": "Synthetic test batch",
                  "value": {
                    "persons": [
                      {
                        "name": "API Batch Test Person",
                        "email": "api-batch-test@example.invalid",
                        "idempotency_key": "docs-batch-person-0001"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The batch completed. Inspect all three result arrays.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkCaptureResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/CaptureConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The API client key shown in Sakhaa."
      },
      "ApiSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Secret",
        "description": "The one-time API client secret. Keep it in a server-side secret store."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "A stable key for one logical request. Reuse it only when retrying the same request.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 512
        }
      }
    },
    "schemas": {
      "PersonCapture": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "first_name": {
            "type": "string",
            "minLength": 1
          },
          "last_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "job_title": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "custom_fields": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "allOf": [
          {
            "anyOf": [
              {
                "required": ["name"]
              },
              {
                "required": ["first_name"]
              }
            ]
          },
          {
            "anyOf": [
              {
                "required": ["email"]
              },
              {
                "required": ["phone"]
              }
            ]
          }
        ]
      },
      "BulkPersonCapture": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "first_name": {
            "type": "string",
            "minLength": 1
          },
          "last_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "job_title": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "custom_fields": {
            "type": "object",
            "additionalProperties": true
          },
          "idempotency_key": {
            "type": "string",
            "maxLength": 512,
            "description": "Optional stable key for this person within the batch."
          }
        },
        "allOf": [
          {
            "anyOf": [
              {
                "required": ["name"]
              },
              {
                "required": ["first_name"]
              }
            ]
          },
          {
            "anyOf": [
              {
                "required": ["email"]
              },
              {
                "required": ["phone"]
              }
            ]
          }
        ]
      },
      "SuccessEnvelope": {
        "type": "object",
        "required": ["success", "statusCode", "timestamp", "data"],
        "properties": {
          "success": {
            "const": true
          },
          "statusCode": {
            "type": "integer"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "message": {
            "type": "string"
          },
          "data": {}
        }
      },
      "ApiClientInfoResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "required": ["client_name", "rate_limit", "has_custom_field_mapping"],
                "properties": {
                  "client_name": { "type": "string" },
                  "rate_limit": { "type": "integer" },
                  "default_lead_source": { "type": ["string", "null"] },
                  "has_custom_field_mapping": { "type": "boolean" },
                  "allowed_origins": { "type": ["array", "null"], "items": { "type": "string" } }
                }
              }
            }
          }
        ]
      },
      "PersonCaptureResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "required": ["person_id"],
                "properties": {
                  "person_id": { "type": "string", "format": "uuid" },
                  "status": { "type": "string" },
                  "action": { "type": ["string", "null"] },
                  "duplicate_detected": { "type": "boolean" },
                  "idempotent_replay": { "type": "boolean" }
                }
              }
            }
          }
        ]
      },
      "BulkCaptureResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "required": ["successful", "duplicates", "failed"],
                "properties": {
                  "successful": { "type": "array", "items": { "type": "object" } },
                  "duplicates": { "type": "array", "items": { "type": "object" } },
                  "failed": { "type": "array", "items": { "type": "object" } }
                }
              }
            }
          }
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": ["success", "statusCode", "timestamp", "error"],
        "properties": {
          "success": { "const": false },
          "statusCode": { "type": "integer" },
          "timestamp": { "type": "string", "format": "date-time" },
          "error": {
            "type": "object",
            "required": ["name", "message", "statusCode"],
            "properties": {
              "name": { "type": "string" },
              "message": { "type": "string" },
              "statusCode": { "type": "integer" },
              "code": { "type": "string" },
              "retryable": { "type": "boolean" },
              "isOperational": { "type": "boolean" }
            },
            "additionalProperties": true
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request, idempotency key, or person data is invalid.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key or secret is missing, invalid, or inactive.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
          }
        }
      },
      "RateLimited": {
        "description": "The API client exceeded its configured request limit.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
          }
        }
      },
      "CaptureConflict": {
        "description": "An earlier request with this idempotency key needs manual recovery. Do not retry automatically.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Capture or request-limit state is temporarily unavailable. Retry only when the returned error is marked retryable, and keep the same idempotency key for the same logical request.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorEnvelope" }
          }
        }
      }
    }
  }
}
