openapi: 3.0.3
info:
  title: LazySEO API
  description: |
    LazySEO content & SEO automation API.

    ## Authentication
    LazySEO uses a two-step token flow:
    1. Exchange your account API key for a short-lived JWT (valid ~30 minutes):
       `POST /api/get-auth-token` with body `{"key": "<YOUR_API_KEY>"}`.
    2. Send the returned token on every request: `Authorization: Bearer <token>`.

    When the token expires the API returns `401` — simply exchange the key again.
  version: 1.0.0
  contact:
    name: LazySEO
    url: https://lazyseo.co
servers:
  - url: https://lazyseo.co
tags:
  - name: Auth
  - name: Account
  - name: Sites
  - name: Folders
  - name: Tags
  - name: Articles
  - name: Publishing
  - name: Templates
  - name: Webhooks

paths:
  /api/get-auth-token:
    post:
      operationId: getAuthToken
      tags: [Auth]
      summary: Exchange an API key for a short-lived JWT
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key]
              properties:
                key:
                  type: string
                  description: Your LazySEO account API key
      responses:
        "200":
          description: JWT issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: boolean }
                  message: { type: string }
                  token: { type: string, description: "JWT, valid ~30 minutes" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/check-credits:
    get:
      operationId: checkCredits
      tags: [Account]
      summary: Check the account's remaining credits
      responses:
        "200":
          description: Credit balance
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_credits: { type: string }
                  used_credits: { type: string }
                  remaining_credits: { type: integer }
                  plan_name: { type: string }
                  renew_date: { type: string, format: date }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/customer-sites:
    get:
      operationId: getCustomerSites
      tags: [Sites]
      summary: List the customer's connected sites
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/sites:
    get:
      operationId: getSites
      tags: [Sites]
      summary: List sites, optionally filtered by site_id
      parameters:
        - name: site_id
          in: query
          required: false
          schema: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-categories:
    post:
      operationId: getCategories
      tags: [Sites]
      summary: Get categories for a site
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [site_id]
              properties:
                site_id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-link-site-categories:
    post:
      operationId: getLinkSiteCategories
      tags: [Sites]
      summary: Get system site categories for a site
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [site_id]
              properties:
                site_id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/add-domain-tool:
    post:
      operationId: addDomainTool
      tags: [Sites]
      summary: Add inbound link tools for a domain
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [site_domain, inbound_links]
              properties:
                site_domain: { type: string }
                inbound_links: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-folders:
    get:
      operationId: getFolders
      tags: [Folders]
      summary: List the account's folders
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/add-folder:
    post:
      operationId: addFolder
      tags: [Folders]
      summary: Create a new folder
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [title]
              properties:
                title: { type: string }
                color: { type: string }
                folder_for: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/edit-folder:
    post:
      operationId: editFolder
      tags: [Folders]
      summary: Rename a folder
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [folder_id, title]
              properties:
                folder_id: { type: number }
                title: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/delete-folder:
    post:
      operationId: deleteFolder
      tags: [Folders]
      summary: Delete a folder
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [folder_id]
              properties:
                folder_id: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-tags:
    get:
      operationId: getTags
      tags: [Tags]
      summary: List the account's tags
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/save-tag:
    post:
      operationId: saveTag
      tags: [Tags]
      summary: Create or update a tag (omit tag_id to create)
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [tag_name]
              properties:
                tag_name: { type: string }
                tag_id: { type: number, description: Existing tag id when updating }
                tag_color: { type: string }
                tag_status: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/add-article-tag:
    post:
      operationId: addArticleTag
      tags: [Tags]
      summary: Attach a tag to an article
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id, tag_id]
              properties:
                article_id: { type: string }
                tag_id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/create-blog:
    post:
      operationId: createBlog
      tags: [Articles]
      summary: Generate a blog article
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [api_model, title, words, language]
              properties:
                api_model: { type: string, description: AI model identifier }
                title: { type: string }
                words: { type: string, description: Target word count }
                language: { type: string }
                addArticleImages: { type: number, description: 1 to include images }
                addArticleTables: { type: number, description: 1 to include tables }
                article_anchors: { type: string }
                client_template: { type: number }
                folder_id: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/create-link:
    post:
      operationId: createLink
      tags: [Articles]
      summary: Generate a link article
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [title, words, api_model]
              properties:
                title: { type: string }
                words: { type: string }
                api_model: { type: string }
                addArticleImages: { type: number }
                addArticleTables: { type: number }
                article_anchors: { type: string }
                client_template: { type: number }
                folder_id: { type: number }
                include_tem_links: { type: string }
                instructions: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/create-reference:
    post:
      operationId: createReference
      tags: [Articles]
      summary: Generate a reference article
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [title, words, client_template, api_model]
              properties:
                title: { type: string }
                words: { type: string }
                client_template: { type: number }
                api_model: { type: string }
                addArticleImages: { type: number }
                addArticleTables: { type: number }
                folder_id: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-article-ideas:
    post:
      operationId: getArticleIdeas
      tags: [Articles]
      summary: Generate article ideas for a title/topic
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [title]
              properties:
                title: { type: string }
                is_new: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-article:
    post:
      operationId: getArticle
      tags: [Articles]
      summary: Fetch a single article by id
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id]
              properties:
                article_id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/update-article:
    post:
      operationId: updateArticle
      tags: [Articles]
      summary: Update an article's content and SEO fields
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id, title, content]
              properties:
                article_id: { type: string }
                title: { type: string }
                content: { type: string }
                seo_title: { type: string }
                seo_description: { type: string }
                que_ans: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/move-article:
    post:
      operationId: moveArticle
      tags: [Articles]
      summary: Move an article between folders
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id, source_folder, destination_folder]
              properties:
                article_id: { type: number }
                source_folder: { type: number }
                destination_folder: { type: number }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/search-article-link:
    post:
      operationId: searchArticleLink
      tags: [Articles]
      summary: Search article links by URL text
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [search_text]
              properties:
                search_text: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/cancel-published-article:
    post:
      operationId: cancelPublishedArticle
      tags: [Publishing]
      summary: Cancel a published article
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id]
              properties:
                article_id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/publish-blog:
    post:
      operationId: publishBlog
      tags: [Publishing]
      summary: Publish/schedule a blog article to a site
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id, site_id, schedule_date, status, category]
              properties:
                article_id: { type: string }
                site_id: { type: string }
                schedule_date: { type: string }
                status: { type: string }
                category: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/publish-link:
    post:
      operationId: publishLink
      tags: [Publishing]
      summary: Publish/schedule a link article to a site
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id, site_id]
              properties:
                article_id: { type: string }
                site_id: { type: string }
                anchor_name: { type: string }
                article_topic: { type: string }
                category: { type: string }
                schedule_date: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/publish-reference:
    post:
      operationId: publishReference
      tags: [Publishing]
      summary: Publish/schedule a reference article to a site
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [article_id, site_id]
              properties:
                article_id: { type: string }
                site_id: { type: string }
                anchor_name: { type: string }
                article_topic: { type: string }
                category: { type: string }
                schedule_date: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-template:
    get:
      operationId: getTemplates
      tags: [Templates]
      summary: Get the account's templates
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/store-template:
    post:
      operationId: storeTemplate
      tags: [Templates]
      summary: Create or update a writing template
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [client_name, website_url, template_name, template_status, writing_style]
              properties:
                client_name: { type: string }
                website_url: { type: string }
                template_name: { type: string }
                template_status: { type: string }
                writing_style: { type: string }
                template_id: { type: string, description: Existing id when updating }
                template_content: { type: string }
                writing_rules: { type: string }
                seo_rules: { type: string }
                design_rules: { type: string }
                home_link: { type: string }
                blog_link: { type: string }
                cateogory_link: { type: string, description: Category link (API spelling) }
                contact_link: { type: string }
                support_link: { type: string }
                section_1: { type: string }
                section_2: { type: string }
                section_3: { type: string }
                section_4: { type: string }
                section_5: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/remove-template:
    post:
      operationId: removeTemplate
      tags: [Templates]
      summary: Remove a template by id
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-webhooks:
    get:
      operationId: getWebhooks
      tags: [Webhooks]
      summary: List webhooks for a given site
      parameters:
        - name: site_id
          in: query
          required: true
          schema: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/get-webhook:
    post:
      operationId: getWebhook
      tags: [Webhooks]
      summary: Get a single webhook by id
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/store-webhook:
    post:
      operationId: storeWebhook
      tags: [Webhooks]
      summary: Create or update a webhook
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [webhook_name, webhook_url, webhook_options]
              properties:
                webhook_name: { type: string }
                webhook_url: { type: string }
                webhook_options: { type: string }
                secret_key: { type: string }
                webhook_id: { type: string, description: Existing id when updating }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/webhook-status:
    post:
      operationId: webhookStatus
      tags: [Webhooks]
      summary: Enable/disable a webhook
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id, status]
              properties:
                id: { type: string }
                status: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/webhook-sample:
    post:
      operationId: webhookSample
      tags: [Webhooks]
      summary: Get a sample payload for a webhook
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/upload-blog:
    post:
      operationId: uploadBlog
      tags: [Articles]
      summary: Upload a blog file
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [files]
              properties:
                files:
                  type: string
                  format: binary
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /api/user/upload-link:
    post:
      operationId: uploadLink
      tags: [Articles]
      summary: Upload a link file
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [files]
              properties:
                files:
                  type: string
                  format: binary
      responses:
        "200": { $ref: "#/components/responses/GenericOk" }
        "401": { $ref: "#/components/responses/Unauthorized" }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Short-lived JWT obtained from POST /api/get-auth-token
  responses:
    GenericOk:
      description: Successful response
      content:
        application/json:
          schema:
            type: object
            additionalProperties: true
    Unauthorized:
      description: Missing or expired token — exchange your API key again via /api/get-auth-token
      content:
        application/json:
          schema:
            type: object
            properties:
              status: { type: boolean, example: false }
              message: { type: string, example: "Unauthorized : Token not valid" }

security:
  - bearerAuth: []
