{
  "openapi": "3.1.0",
  "info": {
    "title": "foto foto connector",
    "version": "2.0.0",
    "description": "Create editable photo books, review prices and prepare purchases. Full access requirements: https://fotofoto.app/connectors.md"
  },
  "servers": [
    {
      "url": "https://fotofoto.app"
    }
  ],
  "security": [],
  "paths": {
    "/api/connector/catalog": {
      "get": {
        "operationId": "getCatalog",
        "description": "List active products, shipping countries and upload requirements.",
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily quota exceeded"
          }
        }
      }
    },
    "/api/connector/photos": {
      "post": {
        "operationId": "uploadPhoto",
        "description": "Upload a JPEG, PNG or WebP original, at most 15 MiB. Returns a private uploadId valid for 24 hours. No login required.",
        "responses": {
          "429": {
            "description": "Rate or daily quota exceeded"
          },
          "201": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        }
      }
    },
    "/api/connector/books": {
      "post": {
        "operationId": "createBook",
        "description": "Create a free editable book. Photos are used in supplied order. Reuse idempotencyKey for retries.",
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily quota exceeded"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["title", "photos", "idempotencyKey"],
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "productId": {
                    "type": "string"
                  },
                  "photos": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "uploadId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "filename": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "oneOf": [
                        {
                          "required": ["url"]
                        },
                        {
                          "required": ["uploadId"]
                        }
                      ],
                      "additionalProperties": false
                    }
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Fresh random UUID. Keep private. Reuse for retries."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/connector/book": {
      "get": {
        "operationId": "getBook",
        "description": "Get an owned book. Include country for a shipping-inclusive EUR price. Unorderable books have price=null and an explanation.",
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily quota exceeded"
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "bookId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "country",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 2
            }
          },
          {
            "in": "header",
            "name": "X-Fotofoto-Book-Token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/connector/checkout": {
      "post": {
        "operationId": "createCheckout",
        "description": "Return a Stripe hosted checkout link. This does not take payment. The buyer confirms the final amount and delivery address at Stripe.",
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily quota exceeded"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["bookId", "country", "idempotencyKey", "bookToken"],
                "properties": {
                  "bookId": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 2
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10,
                    "default": 1
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 100,
                    "pattern": "^[a-zA-Z0-9_-]+$"
                  },
                  "bookToken": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/connector/quotes": {
      "post": {
        "operationId": "createPaymentQuote",
        "description": "Optional MPP: freeze a book snapshot, address and price for 15 minutes. Requires merchant enablement. Ask the buyer to confirm the exact total and address before paying.",
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily quota exceeded"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bookId",
                  "country",
                  "idempotencyKey",
                  "shippingAddress",
                  "bookToken"
                ],
                "properties": {
                  "bookId": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 2
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10,
                    "default": 1
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 100,
                    "pattern": "^[a-zA-Z0-9_-]+$"
                  },
                  "shippingAddress": {
                    "type": "object",
                    "required": [
                      "firstName",
                      "lastName",
                      "addressLine1",
                      "city",
                      "postalCode",
                      "phone"
                    ],
                    "properties": {
                      "firstName": {
                        "type": "string"
                      },
                      "lastName": {
                        "type": "string"
                      },
                      "addressLine1": {
                        "type": "string"
                      },
                      "addressLine2": {
                        "type": "string"
                      },
                      "city": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string"
                      },
                      "postalCode": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string"
                      }
                    }
                  },
                  "bookToken": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/connector/pay/{quoteId}": {
      "post": {
        "operationId": "payQuote",
        "description": "Native MPP charge. Send the private book token. Retry the 402 challenge with Authorization: Payment CREDENTIAL after buyer approval. Reuse the same credential.",
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate or daily quota exceeded"
          },
          "402": {
            "description": "MPP challenge in WWW-Authenticate"
          },
          "409": {
            "description": "Payment already attempted or awaiting resolution; do not submit a new payment"
          },
          "503": {
            "description": "MPP is not configured; use Stripe Checkout"
          },
          "412": {
            "description": "Quote expired, book not claimed or payment configuration unavailable"
          }
        },
        "parameters": [
          {
            "in": "path",
            "name": "quoteId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "Authorization",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "X-Fotofoto-Book-Token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/connector/invitations": {
      "post": {
        "operationId": "createPhotoInvitation",
        "description": "Create a private partner claim link. Stores photo URLs but fetches no images until the recipient opens the link. No account required. Email verification establishes ownership after import.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["title", "idempotencyKey", "photos"],
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "idempotencyKey": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "photos": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "required": ["url"],
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "filename": {
                          "type": "string",
                          "maxLength": 200
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Private claimUrl and expiry. No images fetched."
          },
          "400": {
            "description": "Unsupported source or invalid input"
          },
          "429": {
            "description": "Daily invitation limit exceeded"
          }
        }
      }
    }
  }
}
