{
  "openapi": "3.1.0",
  "info": {
    "title": "GoFastTickets Public API",
    "version": "1.0.0",
    "description": "API privée par clé pour automatiser un compte revendeur GoFastTickets. Toutes les opérations protégées agissent uniquement sur le compte lié à la clé. Les réponses incluent RateLimit, RateLimit-Policy et les champs de compatibilité RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset ; une réponse 429 inclut aussi Retry-After."
  },
  "servers": [
    {
      "url": "https://www.gofasttickets.com/api/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {"name": "Compte"},
    {"name": "Événements"},
    {"name": "Annonces"},
    {"name": "Ventes"}
  ],
  "security": [{"BearerAuth": []}],
  "paths": {
    "/": {
      "get": {
        "summary": "Informations sur l'API",
        "security": [],
        "responses": {"200": {"description": "Informations et liens de documentation"}}
      }
    },
    "/health": {
      "get": {
        "summary": "État de l'API",
        "security": [],
        "responses": {"200": {"description": "API disponible"}}
      }
    },
    "/me": {
      "get": {
        "tags": ["Compte"],
        "summary": "Lire le compte lié à la clé",
        "description": "Requiert le scope account:read.",
        "responses": {
          "200": {"description": "Compte, clé courante et compteurs d'annonces"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "403": {"$ref": "#/components/responses/Forbidden"}
        }
      }
    },
    "/events": {
      "get": {
        "tags": ["Événements"],
        "summary": "Rechercher les événements publiables",
        "description": "Requiert le scope events:read.",
        "parameters": [
          {"$ref": "#/components/parameters/Page"},
          {"$ref": "#/components/parameters/PerPage"},
          {
            "name": "query",
            "in": "query",
            "schema": {"type": "string"},
            "description": "Texte recherché dans le nom, la catégorie ou le lieu."
          },
          {
            "name": "category_id",
            "in": "query",
            "schema": {"type": "string", "format": "uuid"}
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {"type": "string", "format": "date"}
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {"type": "string", "format": "date"}
          }
        ],
        "responses": {
          "200": {
            "description": "Page d'événements",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {"$ref": "#/components/schemas/PaginatedResponse"},
                    {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Event"}}}}
                  ]
                }
              }
            }
          },
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    },
    "/events/{event_uuid}": {
      "get": {
        "tags": ["Événements"],
        "summary": "Lire un événement",
        "parameters": [{"$ref": "#/components/parameters/EventId"}],
        "responses": {
          "200": {
            "description": "Événement",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/EventResponse"}}}
          },
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/listings": {
      "get": {
        "tags": ["Annonces"],
        "summary": "Lister les annonces du compte",
        "description": "Requiert le scope listings:read.",
        "parameters": [
          {"$ref": "#/components/parameters/Page"},
          {"$ref": "#/components/parameters/PerPage"},
          {
            "name": "status",
            "in": "query",
            "schema": {"$ref": "#/components/schemas/ListingStatus"}
          },
          {
            "name": "event_id",
            "in": "query",
            "schema": {"type": "string", "format": "uuid"}
          },
          {
            "name": "query",
            "in": "query",
            "schema": {"type": "string"}
          }
        ],
        "responses": {
          "200": {
            "description": "Page d'annonces",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {"$ref": "#/components/schemas/PaginatedResponse"},
                    {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Listing"}}}}
                  ]
                }
              }
            }
          },
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      },
      "post": {
        "tags": ["Annonces"],
        "summary": "Créer une annonce",
        "description": "Requiert listings:write. Un statut published déclenche normalement le contrôle du moyen de paiement et le dépôt de garantie. Les comptes explicitement exemptés par l'exploitation reçoivent le statut de dépôt api_exempt pour leurs publications via l'API.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/ListingWrite"},
              "examples": {
                "draft": {
                  "summary": "Brouillon complet",
                  "value": {
                    "event_uuid": "00000000-0000-0000-0000-000000000000",
                    "quantity": 2,
                    "location": {"category": "Catégorie 1", "block": "B101", "row": "8", "seat": "21-22"},
                    "pricing": {"cost_price": "150.00", "sale_price": "190.00"},
                    "side_by_side": true,
                    "restrictions": ["Vente: Par paire"],
                    "restrictions_reviewed": true,
                    "status": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Annonce créée",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}
          },
          "402": {"$ref": "#/components/responses/PaymentRequired"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    },
    "/listings/bulk": {
      "post": {
        "tags": ["Annonces"],
        "summary": "Exécuter une action sur plusieurs annonces",
        "description": "Maximum 50 annonces. Chaque élément est traité indépendamment.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["action", "listing_ids"],
                "properties": {
                  "action": {"type": "string", "enum": ["publish", "unpublish", "delete", "duplicate"]},
                  "listing_ids": {"type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": {"type": "string", "format": "uuid"}}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Toutes les actions ont réussi"},
          "207": {"description": "Succès et échecs mélangés"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    },
    "/listings/{listing_uuid}": {
      "parameters": [{"$ref": "#/components/parameters/ListingId"}],
      "get": {
        "tags": ["Annonces"],
        "summary": "Lire une annonce",
        "responses": {
          "200": {"description": "Annonce", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "patch": {
        "tags": ["Annonces"],
        "summary": "Modifier partiellement une annonce",
        "requestBody": {
          "required": true,
          "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingWrite"}}}
        },
        "responses": {
          "200": {"description": "Annonce modifiée", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}},
          "402": {"$ref": "#/components/responses/PaymentRequired"},
          "404": {"$ref": "#/components/responses/NotFound"},
          "409": {"$ref": "#/components/responses/Conflict"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      },
      "delete": {
        "tags": ["Annonces"],
        "summary": "Supprimer une annonce et ses billets",
        "description": "Une annonce vendue ne peut pas être supprimée. Le dépôt d'une annonce invendue est remboursé avant suppression.",
        "responses": {
          "204": {"description": "Annonce supprimée"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/listings/{listing_uuid}/publish": {
      "post": {
        "tags": ["Annonces"],
        "summary": "Publier une annonce",
        "description": "Peut déclencher un dépôt de garantie sur le moyen de paiement du vendeur.",
        "parameters": [{"$ref": "#/components/parameters/ListingId"}],
        "responses": {
          "200": {"description": "Annonce publiée", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}},
          "402": {"$ref": "#/components/responses/PaymentRequired"},
          "409": {"$ref": "#/components/responses/Conflict"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    },
    "/listings/{listing_uuid}/unpublish": {
      "post": {
        "tags": ["Annonces"],
        "summary": "Dépublier une annonce",
        "description": "Repasse une annonce invendue en brouillon et rembourse son dépôt de garantie.",
        "parameters": [{"$ref": "#/components/parameters/ListingId"}],
        "responses": {
          "200": {"description": "Annonce repassée en brouillon", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}},
          "409": {"$ref": "#/components/responses/Conflict"}
        }
      }
    },
    "/listings/{listing_uuid}/duplicate": {
      "post": {
        "tags": ["Annonces"],
        "summary": "Dupliquer une annonce",
        "parameters": [{"$ref": "#/components/parameters/ListingId"}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {"type": "object", "properties": {"publish": {"type": "boolean", "default": false}}}
            }
          }
        },
        "responses": {
          "201": {"description": "Copie créée", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}},
          "402": {"$ref": "#/components/responses/PaymentRequired"}
        }
      }
    },
    "/listings/{listing_uuid}/payment-link": {
      "get": {
        "tags": ["Annonces"],
        "summary": "Obtenir le lien de paiement public",
        "parameters": [{"$ref": "#/components/parameters/ListingId"}],
        "responses": {
          "200": {
            "description": "Lien de paiement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "listing_id": {"type": "string", "format": "uuid"},
                        "path": {"type": "string"},
                        "url": {"type": "string", "format": "uri"}
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/listings/{listing_uuid}/tickets/{slot_index}": {
      "post": {
        "tags": ["Annonces"],
        "summary": "Ajouter ou remplacer un billet",
        "description": "Le premier slot est 0. Les fichiers utilisent les validations, le chiffrement et l'antivirus GoFastTickets.",
        "parameters": [
          {"$ref": "#/components/parameters/ListingId"},
          {"name": "slot_index", "in": "path", "required": true, "schema": {"type": "integer", "minimum": 0}}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {"type": "object", "required": ["ticket"], "properties": {"ticket": {"type": "string", "format": "binary"}}}
            }
          }
        },
        "responses": {
          "200": {"description": "Billet enregistré", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ListingResponse"}}}},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    },
    "/sales": {
      "get": {
        "tags": ["Ventes"],
        "summary": "Lister les ventes du compte",
        "description": "Requiert le scope sales:read.",
        "parameters": [
          {"$ref": "#/components/parameters/Page"},
          {"$ref": "#/components/parameters/PerPage"},
          {"name": "status", "in": "query", "schema": {"type": "string", "enum": ["pending_delivery", "delivered", "cancelled"]}}
        ],
        "responses": {
          "200": {
            "description": "Page de ventes",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {"$ref": "#/components/schemas/PaginatedResponse"},
                    {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Sale"}}}}
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/stats": {
      "get": {
        "tags": ["Ventes"],
        "summary": "Lire les statistiques vendeur",
        "parameters": [
          {"name": "period", "in": "query", "schema": {"type": "string", "enum": ["month", "year", "all"], "default": "all"}}
        ],
        "responses": {
          "200": {"description": "Résumé et série temporelle"},
          "422": {"$ref": "#/components/responses/ValidationError"}
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "gft_live_...",
        "description": "Clé API serveur liée à un compte revendeur."
      }
    },
    "parameters": {
      "Page": {
        "name": "page",
        "in": "query",
        "schema": {"type": "integer", "minimum": 1, "default": 1}
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 25}
      },
      "EventId": {
        "name": "event_uuid",
        "in": "path",
        "required": true,
        "schema": {"type": "string", "format": "uuid"}
      },
      "ListingId": {
        "name": "listing_uuid",
        "in": "path",
        "required": true,
        "schema": {"type": "string", "format": "uuid"}
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Clé absente, invalide, expirée ou révoquée",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}
      },
      "Forbidden": {
        "description": "Scope ou type de compte insuffisant",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}
      },
      "PaymentRequired": {
        "description": "Action de paiement requise ou dépôt de garantie refusé",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}
      },
      "NotFound": {
        "description": "Ressource introuvable pour ce compte",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}
      },
      "Conflict": {
        "description": "Transition d'état impossible",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}
      },
      "ValidationError": {
        "description": "Données métier invalides",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ErrorResponse"}}}
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "resolution_hint", "request_id"],
            "properties": {
              "code": {"type": "string"},
              "message": {"type": "string"},
              "resolution_hint": {"type": "string", "description": "Action concrète suggérée au client pour résoudre l'erreur."},
              "request_id": {"type": "string"},
              "details": {}
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["page", "per_page", "total", "total_pages", "has_more"],
        "properties": {
          "page": {"type": "integer"},
          "per_page": {"type": "integer"},
          "total": {"type": "integer"},
          "total_pages": {"type": "integer"},
          "has_more": {"type": "boolean"}
        }
      },
      "PaginatedResponse": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {"data": {"type": "array"}, "meta": {"$ref": "#/components/schemas/Pagination"}}
      },
      "Event": {
        "type": "object",
        "required": ["id", "name", "date", "place", "category"],
        "properties": {
          "id": {"type": "string", "format": "uuid"},
          "name": {"type": "string"},
          "date": {"type": "string", "format": "date"},
          "time": {"type": ["string", "null"]},
          "place": {"type": "string"},
          "category": {
            "type": "object",
            "properties": {
              "id": {"type": ["string", "null"], "format": "uuid"},
              "name": {"type": "string"}
            }
          }
        }
      },
      "EventResponse": {
        "type": "object",
        "required": ["data"],
        "properties": {"data": {"$ref": "#/components/schemas/Event"}}
      },
      "ListingStatus": {
        "type": "string",
        "enum": ["draft", "published", "waiting_delivery", "delivered"]
      },
      "Location": {
        "type": "object",
        "properties": {
          "category": {"type": "string", "default": "N/A"},
          "zone": {"type": "string", "default": "N/A"},
          "stair": {"type": "string", "default": "N/A"},
          "gate": {"type": "string", "default": "N/A"},
          "block": {"type": "string", "default": "N/A"},
          "row": {"type": "string", "default": "N/A"},
          "seat": {"type": "string", "default": "N/A"}
        }
      },
      "Pricing": {
        "type": "object",
        "required": ["cost_price", "sale_price"],
        "properties": {
          "cost_price": {"type": ["string", "number"], "examples": ["150.00"]},
          "sale_price": {"type": ["string", "number"], "examples": ["190.00"]}
        }
      },
      "ListingWrite": {
        "type": "object",
        "properties": {
          "event_uuid": {"type": "string", "format": "uuid"},
          "quantity": {"type": "integer", "minimum": 1, "maximum": 100},
          "location": {"oneOf": [{"$ref": "#/components/schemas/Location"}, {"type": "string"}]},
          "section_row_seat": {"type": "string", "deprecated": true},
          "pricing": {"$ref": "#/components/schemas/Pricing"},
          "cost_price": {"type": ["string", "number"], "deprecated": true},
          "sale_price": {"type": ["string", "number"], "deprecated": true},
          "side_by_side": {"type": "boolean"},
          "note": {"type": "string", "maxLength": 2000},
          "restrictions": {"type": "array", "maxItems": 50, "items": {"type": "string", "maxLength": 200}},
          "restrictions_reviewed": {"type": "boolean"},
          "status": {"type": "string", "enum": ["draft", "published"], "default": "draft"}
        }
      },
      "Listing": {
        "type": "object",
        "required": ["id", "event", "location", "pricing", "status", "tickets", "security_deposit"],
        "properties": {
          "id": {"type": "string", "format": "uuid"},
          "event": {
            "type": "object",
            "required": ["id", "name", "category", "date", "place"],
            "properties": {
              "id": {"type": ["string", "null"], "format": "uuid"},
              "name": {"type": "string"},
              "category": {"type": "string"},
              "date": {"type": ["string", "null"], "format": "date"},
              "time": {"type": ["string", "null"]},
              "place": {"type": "string"}
            }
          },
          "quantity": {"type": ["integer", "null"]},
          "location": {"$ref": "#/components/schemas/Location"},
          "pricing": {
            "type": "object",
            "properties": {
              "currency": {"type": "string", "const": "EUR"},
              "cost_price": {"type": ["string", "null"]},
              "sale_price": {"type": ["string", "null"]}
            }
          },
          "side_by_side": {"type": ["boolean", "null"]},
          "note": {"type": "string"},
          "restrictions": {"type": "array", "items": {"type": "string"}},
          "restrictions_reviewed": {"type": "boolean"},
          "status": {"$ref": "#/components/schemas/ListingStatus"},
          "tickets": {
            "type": "object",
            "properties": {
              "uploaded_count": {"type": "integer"},
              "files": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "slot": {"type": "integer"},
                    "name": {"type": "string"},
                    "uploaded": {"type": "boolean"}
                  }
                }
              }
            }
          },
          "payment_link": {"type": ["string", "null"], "format": "uri"},
          "security_deposit": {
            "type": "object",
            "properties": {
              "amount": {"type": "string"},
              "status": {"type": "string"},
              "error": {"type": ["string", "null"]}
            }
          },
          "created_at": {"type": ["string", "null"], "format": "date-time"},
          "updated_at": {"type": ["string", "null"], "format": "date-time"}
        }
      },
      "ListingResponse": {
        "type": "object",
        "required": ["data"],
        "properties": {"data": {"$ref": "#/components/schemas/Listing"}}
      },
      "Sale": {
        "type": "object",
        "required": ["id", "listing_id", "event", "quantity", "status", "currency"],
        "properties": {
          "id": {"type": "string", "format": "uuid"},
          "listing_id": {"type": "string", "format": "uuid"},
          "buyer_email": {"type": "string", "format": "email"},
          "event": {
            "type": "object",
            "required": ["id", "name", "date", "place"],
            "properties": {
              "id": {"type": ["string", "null"], "format": "uuid"},
              "name": {"type": "string"},
              "date": {"type": ["string", "null"], "format": "date"},
              "place": {"type": "string"}
            }
          },
          "quantity": {"type": "integer"},
          "cost_price": {"type": "string"},
          "sale_price": {"type": "string"},
          "total": {"type": "string"},
          "currency": {"type": "string", "const": "EUR"},
          "status": {"type": "string", "enum": ["pending_delivery", "delivered", "cancelled"]},
          "sold_at": {"type": ["string", "null"], "format": "date-time"},
          "delivered_at": {"type": ["string", "null"], "format": "date-time"},
          "cancelled_at": {"type": ["string", "null"], "format": "date-time"}
        }
      }
    }
  }
}
