{
  "openapi": "3.1.0",
  "info": {
    "title": "Anshar Images public read API",
    "version": "1.0.0",
    "summary": "Search the published photos of Anshar Images and read one photo with its licences and prices.",
    "description": "A read-only JSON API. It needs no sign-in, sets no cookie and gives only published photos. Every error answer is JSON. One IP address may send 60 requests a minute to this API and to the MCP server at /mcp together; a client over the limit gets status 429. A found answer may be kept for 600 seconds (`Cache-Control: private`). An error answer has `Cache-Control: no-store`."
  },
  "servers": [
    {
      "url": "https://ansharimages.com",
      "description": "Production"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "The full price lists and the licence terms: the public price document",
    "url": "https://ansharimages.com/feeds/prices.json"
  },
  "tags": [
    {
      "name": "Photos",
      "description": "Search the published photos and read one photo."
    }
  ],
  "paths": {
    "/api/v1/photos": {
      "get": {
        "operationId": "searchPhotos",
        "tags": ["Photos"],
        "summary": "Search the published photos",
        "description": "Gives one page of the published photos that match, newest first. Every word of `q` must be in the title, the keywords or the description of a photo. A search shows at most 10 pages: on page 10, `has_more` is `false` also when more photos match. To see more photos, narrow the search with words or filters. Each parameter value is trimmed. An empty value counts as missing, so `page` and `per_page` then take their defaults. The limits of `q` apply to the trimmed text. The photo feed https://ansharimages.com/feeds/photos.json lists every published photo.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Words to find. The text is split into words at a comma, a dot, a bar, a colon, a space, a semicolon and a plus. At most 10 words and 200 characters.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "The slug of a category. It matches the first category of a photo, the one that an answer names.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "The slug of a country.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "gallery",
            "in": "query",
            "required": false,
            "description": "The slug of a gallery. It needs `country`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "The page to give, from 1 to 10.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "The number of photos on a page, from 1 to 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of photo summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchAnswer"
                },
                "examples": {
                  "search": {
                    "summary": "A search for q=canal",
                    "value": {
                      "photos": [
                        {
                          "id": 1234,
                          "slug": "grand-canal-dawn",
                          "url": "https://ansharimages.com/photo/grand-canal-dawn",
                          "title": "Grand Canal at Dawn",
                          "category": {
                            "title": "Cities",
                            "slug": "cities"
                          },
                          "gallery_title": "Venice",
                          "country_title": "Italy",
                          "preview": {
                            "url": "https://images.ansharimages.com/photo/italy/venice/grand-canal-dawn.jpg",
                            "width": 800,
                            "height": 533
                          }
                        }
                      ],
                      "page": 1,
                      "per_page": 40,
                      "has_more": false
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/RefusedInput"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/photos/{slug}": {
      "get": {
        "operationId": "getPhoto",
        "tags": ["Photos"],
        "summary": "Read one published photo",
        "description": "Gives one published photo with its licences and prices.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The slug of the photo, as in its page address.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One photo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Photo"
                },
                "examples": {
                  "photo": {
                    "summary": "A published photo",
                    "value": {
                      "id": 1234,
                      "slug": "grand-canal-dawn",
                      "url": "https://ansharimages.com/photo/grand-canal-dawn",
                      "title": "Grand Canal at Dawn",
                      "category": {
                        "title": "Cities",
                        "slug": "cities"
                      },
                      "gallery_title": "Venice",
                      "country_title": "Italy",
                      "preview": {
                        "url": "https://images.ansharimages.com/photo/italy/venice/grand-canal-dawn.jpg",
                        "width": 800,
                        "height": 533
                      },
                      "description": null,
                      "alt_text": "Boats on the Grand Canal at dawn",
                      "keywords": ["venice", "canal", "dawn"],
                      "gallery_url": "https://ansharimages.com/photos/italy/venice",
                      "country": "italy",
                      "image_1200_url": "https://images.ansharimages.com/photo/italy/venice/grand-canal-dawn-1200.jpg",
                      "original_width": 6000,
                      "original_height": 4000,
                      "file_type": "JPG",
                      "licenses": [
                        {
                          "license": "standard",
                          "name": "Standard",
                          "summary": "Digital use, print up to 500,000 copies, AI training",
                          "prices": [
                            {"code": "USD", "amount": "29.00", "iso4217": true},
                            {"code": "BTC", "amount": "0.00012000", "iso4217": false}
                          ]
                        },
                        {
                          "license": "extended",
                          "name": "Extended",
                          "summary": "Plus wall art, unlimited print, and merchandise and templates for sale",
                          "prices": [
                            {"code": "USD", "amount": "99.00", "iso4217": true},
                            {"code": "BTC", "amount": "0.00041000", "iso4217": false}
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No published photo has this slug. The answer is the same for a photo that is not published and for a slug that never existed.",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/NoStore"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "message": "No listed photo has this slug."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "NoStore": {
        "description": "Always `no-store`: no client or cache keeps an error answer.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "RefusedInput": {
        "description": "A parameter was refused. The messages are fixed and never repeat the input.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "example": {
              "message": "The page must be from 1 to 10.",
              "errors": {
                "page": ["The page must be from 1 to 10."]
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "The IP address sent more than 60 requests in one minute to this API and to /mcp together.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/NoStore"
          },
          "Retry-After": {
            "description": "The seconds to wait before the next request.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "Too Many Attempts."
            }
          }
        }
      }
    },
    "schemas": {
      "SearchAnswer": {
        "type": "object",
        "required": ["photos", "page", "per_page", "has_more"],
        "additionalProperties": false,
        "properties": {
          "photos": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/PhotoSummary"
            }
          },
          "page": {
            "description": "The page that was asked for.",
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          },
          "per_page": {
            "description": "The page size that was used.",
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "has_more": {
            "description": "`true` when more photos match and `page` is below 10.",
            "type": "boolean"
          }
        }
      },
      "PhotoSummary": {
        "type": "object",
        "required": ["id", "slug", "url", "title", "category", "gallery_title", "country_title", "preview"],
        "additionalProperties": false,
        "properties": {
          "id": {"$ref": "#/components/schemas/Id"},
          "slug": {"$ref": "#/components/schemas/Slug"},
          "url": {"$ref": "#/components/schemas/PageUrl"},
          "title": {"$ref": "#/components/schemas/OptionalText"},
          "category": {"$ref": "#/components/schemas/OptionalCategory"},
          "gallery_title": {"$ref": "#/components/schemas/OptionalText"},
          "country_title": {"$ref": "#/components/schemas/OptionalText"},
          "preview": {"$ref": "#/components/schemas/Preview"}
        }
      },
      "Photo": {
        "type": "object",
        "required": [
          "id", "slug", "url", "title", "category", "gallery_title", "country_title", "preview",
          "description", "alt_text", "keywords", "gallery_url", "country", "image_1200_url",
          "original_width", "original_height", "file_type", "licenses"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {"$ref": "#/components/schemas/Id"},
          "slug": {"$ref": "#/components/schemas/Slug"},
          "url": {"$ref": "#/components/schemas/PageUrl"},
          "title": {"$ref": "#/components/schemas/OptionalText"},
          "category": {"$ref": "#/components/schemas/OptionalCategory"},
          "gallery_title": {"$ref": "#/components/schemas/OptionalText"},
          "country_title": {"$ref": "#/components/schemas/OptionalText"},
          "preview": {"$ref": "#/components/schemas/Preview"},
          "description": {"$ref": "#/components/schemas/OptionalText"},
          "alt_text": {"$ref": "#/components/schemas/OptionalText"},
          "keywords": {
            "type": "array",
            "items": {"type": "string"}
          },
          "gallery_url": {"$ref": "#/components/schemas/PageUrl"},
          "country": {
            "description": "The slug of the photo's country.",
            "type": "string"
          },
          "image_1200_url": {
            "description": "The photo scaled to a long side of 1200 pixels, as a JPEG file.",
            "type": "string"
          },
          "original_width": {
            "description": "The width of the original file in pixels, as stored.",
            "type": "integer",
            "minimum": 0
          },
          "original_height": {
            "description": "The height of the original file in pixels, as stored.",
            "type": "integer",
            "minimum": 0
          },
          "file_type": {
            "description": "The file type of the original, for example JPG.",
            "type": "string"
          },
          "licenses": {
            "description": "The two licences, Standard first.",
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "items": {"$ref": "#/components/schemas/License"}
          }
        }
      },
      "Id": {
        "type": "integer",
        "minimum": 1
      },
      "Slug": {
        "description": "The slug of the photo, as in its page address.",
        "type": "string"
      },
      "PageUrl": {
        "description": "The address of a page of the site.",
        "type": "string",
        "format": "uri"
      },
      "OptionalText": {
        "description": "Text that the owner wrote, or `null` when it is missing or empty.",
        "type": ["string", "null"]
      },
      "OptionalCategory": {
        "description": "The first category of the photo, or `null` when it has none or the category is hidden.",
        "anyOf": [
          {"$ref": "#/components/schemas/Category"},
          {"type": "null"}
        ]
      },
      "Category": {
        "type": "object",
        "required": ["title", "slug"],
        "additionalProperties": false,
        "properties": {
          "title": {"type": "string"},
          "slug": {"type": "string"}
        }
      },
      "Preview": {
        "description": "The preview image.",
        "type": "object",
        "required": ["url", "width", "height"],
        "additionalProperties": false,
        "properties": {
          "url": {"type": "string"},
          "width": {"type": "integer", "minimum": 0},
          "height": {"type": "integer", "minimum": 0}
        }
      },
      "License": {
        "type": "object",
        "required": ["license", "name", "summary", "prices"],
        "additionalProperties": false,
        "properties": {
          "license": {
            "type": "string",
            "enum": ["standard", "extended"]
          },
          "name": {"type": "string"},
          "summary": {
            "description": "A short summary of the licence, the same for every photo.",
            "type": "string"
          },
          "prices": {
            "description": "One price per published currency, in the order of the price document. Empty when no price list is published.",
            "type": "array",
            "items": {"$ref": "#/components/schemas/Price"}
          }
        }
      },
      "Price": {
        "type": "object",
        "required": ["code", "amount", "iso4217"],
        "additionalProperties": false,
        "properties": {
          "code": {
            "description": "The code of the currency, for example USD or BTC.",
            "type": "string"
          },
          "amount": {
            "description": "The exact list amount in the major unit, as a decimal text. The photo page may round it for display.",
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]+)?$"
          },
          "iso4217": {
            "description": "`false` for a currency that is not an ISO 4217 currency, such as BTC, USDT and USDC.",
            "type": "boolean"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["message"],
        "additionalProperties": false,
        "properties": {
          "message": {"type": "string"}
        }
      },
      "Messages": {
        "type": "array",
        "minItems": 1,
        "items": {"type": "string"}
      },
      "ValidationError": {
        "type": "object",
        "required": ["message", "errors"],
        "additionalProperties": false,
        "properties": {
          "message": {"type": "string"},
          "errors": {
            "description": "The fixed messages of each refused parameter.",
            "type": "object",
            "minProperties": 1,
            "additionalProperties": false,
            "properties": {
              "q": {"$ref": "#/components/schemas/Messages"},
              "category": {"$ref": "#/components/schemas/Messages"},
              "country": {"$ref": "#/components/schemas/Messages"},
              "gallery": {"$ref": "#/components/schemas/Messages"},
              "page": {"$ref": "#/components/schemas/Messages"},
              "per_page": {"$ref": "#/components/schemas/Messages"}
            }
          }
        }
      }
    }
  }
}
