{
  "openapi": "3.1.0",
  "info": {
    "title": "Natalka anonymous chart preview",
    "version": "1.0.0",
    "description": "City search and a calculated chart preview. No saved charts, readings, payments or Telegram account access. Birth inputs are not persisted. Astrology is not scientific prediction."
  },
  "servers": [
    {
      "url": "https://natalka.ai"
    }
  ],
  "security": [],
  "externalDocs": {
    "url": "https://natalka.ai/docs/api"
  },
  "paths": {
    "/api/preview/cities": {
      "post": {
        "operationId": "searchBirthCities",
        "summary": "Find birthplace candidates in GeoNames",
        "description": "Choose a returned city id. At most 120 requests per minute across all visitors, and four concurrent city reads. Search text is trimmed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 80
                  },
                  "locale": {
                    "default": "ru",
                    "type": "string",
                    "enum": [
                      "ru",
                      "en"
                    ]
                  }
                },
                "required": [
                  "query"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Up to eight city candidates.",
            "content": {
              "application/json": {
                "schema": {
                  "$schema": "https://json-schema.org/draft/2020-12/schema",
                  "type": "object",
                  "properties": {
                    "cities": {
                      "maxItems": 8,
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991
                          },
                          "label": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "label"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "cities"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or city selection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Cross-site browser request or mismatched Origin.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "JSON body exceeds 2048 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Shared request budget exhausted; retry after 60 seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/preview/chart": {
      "post": {
        "operationId": "calculateChartPreview",
        "summary": "Calculate an anonymous natal chart preview",
        "description": "Use date YYYY-MM-DD (1800-01-01 through today), local birth time HH:MM or null when unknown, and a city id from search. time:null omits houses and angles and preserves Moon-sign ambiguity. A fold response requires the user to choose index 0 or 1; never assume it. Maximum 30 attempts per minute shared by all visitors. Responses contain rendered HTML, not a structured ephemeris API.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "maxLength": 30
                  },
                  "time": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 10
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "cityId": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 20000000
                  },
                  "locale": {
                    "default": "ru",
                    "type": "string",
                    "enum": [
                      "ru",
                      "en"
                    ]
                  },
                  "fold": {
                    "anyOf": [
                      {
                        "type": "number",
                        "const": 0
                      },
                      {
                        "type": "number",
                        "const": 1
                      }
                    ]
                  }
                },
                "required": [
                  "date",
                  "time",
                  "cityId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rendered chart HTML, or two UTC offsets in minutes requiring a user decision.",
            "content": {
              "application/json": {
                "schema": {
                  "$schema": "https://json-schema.org/draft/2020-12/schema",
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "const": "chart"
                        },
                        "html": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "kind",
                        "html"
                      ],
                      "additionalProperties": false
                    },
                    {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "const": "fold"
                        },
                        "offsets": {
                          "minItems": 2,
                          "maxItems": 2,
                          "type": "array",
                          "items": {
                            "type": "number"
                          }
                        }
                      },
                      "required": [
                        "kind",
                        "offsets"
                      ],
                      "additionalProperties": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid input or city selection.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Cross-site browser request or mismatched Origin.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "JSON body exceeds 2048 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "Content-Type must be application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Nonexistent local time (gap), skipped local date (skipped), or calculation unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Shared request budget exhausted; retry after 60 seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "503": {
            "description": "Service temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
