{
  "openapi": "3.1.0",
  "info": {
    "title": "Musikeando public API (en)",
    "version": "1.0.0",
    "summary": "Create and order personalized AI songs as gifts.",
    "description": "Public API of Musikeando. Read endpoints are open; song creation works with an anonymous guest session and check-out requires a Musikeando account. Documentation: https://musikeando.com/docs",
    "contact": {
      "name": "Musikeando",
      "url": "https://musikeando.com/contact"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://musikeando.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "catalog",
      "description": "Public, unauthenticated catalog."
    },
    {
      "name": "songs",
      "description": "Song creation and retrieval."
    },
    {
      "name": "payments",
      "description": "Checkout and credit management."
    },
    {
      "name": "system",
      "description": "Operational endpoints."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "system"
        ],
        "summary": "Service health",
        "description": "Liveness probe. Returns ok when the application is serving traffic.",
        "responses": {
          "200": {
            "description": "Service is healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    }
                  },
                  "required": [
                    "status"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/pricing": {
      "get": {
        "operationId": "getPublicPricing",
        "tags": [
          "catalog"
        ],
        "summary": "Public pricing catalog",
        "description": "First-purchase pricing offers for every market. Informational only; the final amount is confirmed at checkout.",
        "responses": {
          "200": {
            "description": "Pricing offers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "plan_key": {
                        "type": "string"
                      },
                      "locale": {
                        "type": "string",
                        "enum": [
                          "pt-BR",
                          "es",
                          "en"
                        ]
                      },
                      "currency": {
                        "type": "string"
                      },
                      "songs": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "price_cents": {
                        "type": "integer"
                      },
                      "original_cents": {
                        "type": "integer"
                      },
                      "discount_pct": {
                        "type": "integer"
                      },
                      "label": {
                        "type": "string"
                      },
                      "price_condition": {
                        "type": "string",
                        "const": "first_purchase"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "validateDiscountCode",
        "tags": [
          "catalog"
        ],
        "summary": "Validate a coupon or discount code",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "plan_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "code"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "discount_pct": {
                      "type": "integer",
                      "nullable": true
                    },
                    "discount_cents": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/occasions": {
      "get": {
        "operationId": "listOccasions",
        "tags": [
          "catalog"
        ],
        "summary": "List active gift occasions",
        "responses": {
          "200": {
            "description": "Active occasions within their availability window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "key": {
                        "type": "string"
                      },
                      "label": {
                        "type": "string"
                      },
                      "icon": {
                        "type": "string"
                      },
                      "badge": {
                        "type": "string",
                        "nullable": true
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/testimonials": {
      "get": {
        "operationId": "listTestimonials",
        "tags": [
          "catalog"
        ],
        "summary": "List approved customer testimonials",
        "responses": {
          "200": {
            "description": "Approved testimonials.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "testimonials": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "text": {
                            "type": "string"
                          },
                          "stars": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 5
                          },
                          "occasion": {
                            "type": "string",
                            "nullable": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/blog": {
      "get": {
        "operationId": "listBlogPosts",
        "tags": [
          "catalog"
        ],
        "summary": "List published blog posts for the current market",
        "description": "Page-number pagination: `page` (1-based) and `limit` (1-50, default 12). The response carries the total and page count.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 12
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of published posts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "posts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "excerpt": {
                            "type": "string",
                            "nullable": true
                          },
                          "featured_image": {
                            "type": "string",
                            "nullable": true
                          },
                          "author_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "published_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "locale": {
                            "type": "string",
                            "enum": [
                              "pt-BR",
                              "es",
                              "en"
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "title",
                          "slug",
                          "locale"
                        ]
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": [
                    "posts",
                    "pagination"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "submitContactMessage",
        "tags": [
          "catalog"
        ],
        "summary": "Send a contact message",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many messages from this client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/register": {
      "post": {
        "operationId": "registerAccount",
        "tags": [
          "payments"
        ],
        "summary": "Create a Musikeando account",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 8
                  }
                },
                "required": [
                  "email",
                  "password"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The email is already registered; sign in instead. Deliberately not a 409.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "already_exists": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "already_exists"
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Account created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string",
                      "format": "email"
                    }
                  },
                  "required": [
                    "id",
                    "email"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/songs/create": {
      "post": {
        "operationId": "createSong",
        "tags": [
          "songs"
        ],
        "summary": "Start a personalized song order",
        "description": "Works with an anonymous guest session (cookie) or an authenticated account. The guest cookie is issued automatically on the first call.",
        "security": [
          {
            "guestSession": []
          },
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "occasion": {
                    "type": "string",
                    "enum": [
                      "birthday",
                      "anniversary",
                      "wedding_anniversary",
                      "wedding",
                      "graduation",
                      "valentines",
                      "mothers_day",
                      "fathers_day",
                      "friendship",
                      "apology",
                      "memorial",
                      "christmas",
                      "children",
                      "gender_reveal",
                      "other"
                    ]
                  },
                  "occasion_custom": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 200
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "recipient_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "recipient_relation": {
                    "anyOf": [
                      {
                        "type": "string",
                        "enum": [
                          "partner",
                          "mother",
                          "father",
                          "child",
                          "friend",
                          "grandparent",
                          "sibling",
                          "coworker",
                          "teacher",
                          "other"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sender_name": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 100
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "story": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000
                  },
                  "story_additional": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 1000
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "mood": {
                    "type": "string",
                    "enum": [
                      "happy",
                      "romantic",
                      "emotional",
                      "nostalgic",
                      "fun",
                      "inspiring",
                      "melancholic",
                      "aggressive"
                    ]
                  },
                  "genre": {
                    "type": "string",
                    "enum": [
                      "pop",
                      "rock",
                      "ballad",
                      "reggaeton",
                      "samba",
                      "bossa_nova",
                      "electronic",
                      "folk",
                      "rnb",
                      "latin_pop",
                      "sertanejo",
                      "mpb",
                      "pagode",
                      "forro",
                      "brega",
                      "gospel",
                      "axe",
                      "funk",
                      "flamenco",
                      "rumba",
                      "bachata",
                      "trap",
                      "indie",
                      "copla",
                      "country",
                      "jazz",
                      "hiphop",
                      "classical",
                      "soul",
                      "lullaby",
                      "children_pop"
                    ]
                  },
                  "voice_gender": {
                    "default": "female",
                    "type": "string",
                    "enum": [
                      "female",
                      "male"
                    ]
                  },
                  "song_language": {
                    "default": "pt-BR",
                    "type": "string",
                    "enum": [
                      "pt-BR",
                      "pt-PT",
                      "es-ES",
                      "en-US",
                      "fr-FR",
                      "it-IT",
                      "de-DE",
                      "ja-JP",
                      "ko-KR",
                      "zh-CN",
                      "hi-IN",
                      "ar-SA",
                      "ca-ES",
                      "gl-ES",
                      "eu-ES"
                    ]
                  },
                  "recipient_age": {
                    "anyOf": [
                      {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 150
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "additional_info": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 500
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "include_name_in_song": {
                    "default": true,
                    "type": "boolean"
                  },
                  "name_in_song": {
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 100
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "occasion",
                  "recipient_name",
                  "story",
                  "mood",
                  "genre"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order created. Continue with lyrics generation on the site or track it with getSongStatus.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "song_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "description": "Song lifecycle status, starts at \"draft\"."
                    }
                  },
                  "required": [
                    "song_id",
                    "status"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload or story rejected by content validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Session could not be established.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit: 5 orders per hour per guest, 20 per account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/music/status/{taskId}": {
      "get": {
        "operationId": "getSongStatus",
        "tags": [
          "songs"
        ],
        "summary": "Check generation status of a song",
        "security": [
          {
            "guestSession": []
          },
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "taskId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Task identifier returned when the order was created."
          }
        ],
        "responses": {
          "200": {
            "description": "Current generation status. Poll until completed or failed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "processing",
                        "completed",
                        "failed"
                      ]
                    },
                    "progress_percent": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100
                    },
                    "new_task_id": {
                      "type": "string",
                      "description": "Present when generation was retried under a new task; poll that one."
                    }
                  },
                  "required": [
                    "status",
                    "progress_percent"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Session could not be established.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown task, or not owned by this session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/payments/create-final-intent": {
      "post": {
        "operationId": "createFinalPaymentIntent",
        "tags": [
          "payments"
        ],
        "summary": "Create a Stripe payment intent for a finished song",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "song_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "version": {
                    "type": "integer",
                    "enum": [
                      1,
                      2
                    ],
                    "description": "Which of the two generated versions to buy."
                  },
                  "coupon_code": {
                    "type": "string"
                  },
                  "affiliate_code": {
                    "type": "string"
                  },
                  "wallet_cents": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Wallet balance to apply, in cents."
                  }
                },
                "required": [
                  "song_id",
                  "version"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stripe PaymentIntent created (or the song was fully covered by credits/wallet). Amounts in cents of the market currency.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client_secret": {
                      "type": "string",
                      "nullable": true,
                      "description": "Stripe client secret to confirm the payment; null when nothing is due."
                    },
                    "amount": {
                      "type": "integer"
                    },
                    "discount": {
                      "type": "integer"
                    },
                    "coupon_discount": {
                      "type": "integer"
                    },
                    "wallet_discount": {
                      "type": "integer"
                    },
                    "first_purchase_discount": {
                      "type": "boolean"
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "amount",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload or song not ready.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Secure-next-auth.session-token",
        "description": "Musikeando account session cookie, set by the sign-in flow."
      },
      "guestSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "mk_guest",
        "description": "Anonymous guest session cookie. Issued in the Set-Cookie of the first song-creation call; send it back on later calls."
      }
    },
    "schemas": {
      "Pagination": {
        "type": "object",
        "description": "Page-number pagination block returned by list endpoints.",
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "limit": {
            "type": "integer",
            "minimum": 1
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "pages": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "page",
          "limit",
          "total",
          "pages"
        ]
      },
      "Error": {
        "type": "object",
        "description": "Standard error envelope.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "details": {
            "description": "Optional structured details, for example validation issues."
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}