> ## Documentation Index
> Fetch the complete documentation index at: https://docs.madiad.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Đăng bài tài liệu

> Đăng tệp PDF, PPT, PPTX, DOC hoặc DOCX. `caption` trở thành tên tài liệu trên bài.



## OpenAPI

````yaml /openapi.vi.yaml post /v1/posts/document
openapi: 3.1.0
info:
  title: MADIAD Hub API
  version: 1.0.0
  description: >-
    Một API để đăng lên 22 kênh, đọc lại kết quả, và quản lý hồ sơ cùng các kết
    nối đứng sau.


    ## Những quy ước bắt buộc


    - **Chỉ `success: false` mới là thất bại.** Không thấy trường `success`
    KHÔNG có nghĩa là hỏng.
      Nhiều kênh nhận bài rồi mới xử lý, nên phản hồi đầu tiên hiếm khi là kết quả cuối — tra lại bằng
      `GET /v1/posts/status`.
    - **Đọc kết quả THEO TỪNG KÊNH.** Một lần gọi có thể được kênh này nhận và
    kênh kia từ chối;
      `failed_platforms` và `results` nói rõ kênh nào, và không có một mã chung nào mang nghĩa đó.
    - **`Idempotency-Key` là của riêng từng bài.** Sinh khoá MỚI cho mỗi bài và
    ghi xuống TRƯỚC khi
      gọi. Dùng lại một khoá đã hoàn tất thì API trả về kết quả cũ chứ không đăng bài mới, nhìn từ
      ngoài giống hệt bị bỏ qua trong im lặng.
    - **Trang đã ghim thắng trường bạn gửi.** Khi hồ sơ đã ghim một Trang,
    `facebook_page_id` trong
      yêu cầu không đổi được đích, và các bài hẹn giờ bỏ hẳn trường đó.
    - **`caption` là thân bài, `title` là tiêu đề.** Đây là hai trường khác
    nhau. Nhét thân bài vào
      `title` thì bài vẫn đăng nên không ai để ý, nhưng nó đi vòng qua phép kiểm độ dài rồi hỏng ở
      phía kênh thay vì nhận một lỗi 400 rõ ràng.

    ## Hub từ chối, khác với kênh từ chối


    Một lỗi trong bảng dưới đây là Hub từ chối lệnh trước khi tiêu tốn gì — yêu
    cầu chưa hề đi đâu.

    Còn kênh từ chối một bài mà nó đã nhận thì đến theo đường khác: HTTP 200, và
    trong `results` kênh

    đó mang `success: false`. Hai thứ này xử lý khác nhau nên không bao giờ được
    gộp.


    ## Bảng mã lỗi


    Mọi lỗi đều trả về dạng `{ "error": { "code", "message" } }`.


    | Mã | HTTP | Nghĩa là gì |

    | --- | --- | --- |

    | `unauthorized` | 401 | Thiếu API key, key sai định dạng, hoặc key không
    còn hiệu lực. |

    | `forbidden` | 403 | Key hợp lệ nhưng tài khoản này không được phép làm
    việc đó. |

    | `not_found` | 404 | Tài khoản này không có tài nguyên đó. |

    | `invalid_request` | 400 | Lệnh bị từ chối trước khi tiêu tốn bất cứ thứ gì
    — thiếu một trường hoặc trường sai định dạng. Câu lỗi nói rõ trường nào. |

    | `quota_exceeded` | 429 | Hạn mức của gói cho thao tác này đã dùng hết. Xem
    `GET /v1/usage`. |

    | `no_active_plan` | 403 | Tài khoản chưa có gói đang hoạt động, hoặc gói đã
    hết hạn. |

    | `profile_limit_reached` | 403 | Đã chạm giới hạn số hồ sơ của gói. Không
    có gì được tạo. |

    | `rate_limited` | 429 | Gọi quá nhiều trong thời gian ngắn. Chờ đúng số
    giây trong `Retry-After`. |

    | `idempotency_conflict` | 409 | `Idempotency-Key` này đã dùng cho một yêu
    cầu KHÁC. Mỗi bài một khoá mới. |

    | `idempotency_in_progress` | 409 | Một lệnh mang `Idempotency-Key` này vẫn
    đang chạy. Tra lại trạng thái thay vì gọi lại. |

    | `page_pin_conflict` | 409 | Lệnh chỉ định một Trang nhưng hồ sơ đang ghim
    một Trang khác. Ghim thắng, nên nếu cứ đăng thì bài sẽ nằm ở chỗ lệnh không
    hề nhắm tới. Bỏ trường đó đi, hoặc gỡ ghim. |

    | `page_pin_unverified` | 503 | Không đọc được Trang đang ghim của hồ sơ,
    nên không biết bài có bị đẩy sang chỗ khác không. Chưa đăng gì cả — gọi lại
    được. |

    | `platform_not_startable` | 400 | Kênh đó không đăng nhập bằng chuyển
    hướng; nối nó bằng `POST /v1/connections/{channel}`. |

    | `platform_unavailable` | 503 | Đường nối kênh đó đang được nâng cấp nên
    chưa hoàn tất được. Thử lại sau. |

    | `link_not_found` | 404 | Liên kết nối kênh không tồn tại hoặc đã hết hạn.
    Tạo liên kết mới. |

    | `platform_not_connected` | 400, 409 | Hồ sơ chưa nối tài khoản nào cho một
    kênh mà lệnh nhắm tới, nên bài không thể đi được. Nối tài khoản đó hoặc bỏ
    kênh đó ra — gọi lại không giải quyết được gì. |

    | `location_required` | 400 | Tài khoản Google của hồ sơ này có nhiều hơn
    một địa điểm Business và lệnh không chỉ định địa điểm nào. Gửi kèm
    `location_id`, hoặc ghim sẵn một cái. |

    | `upstream_error` | 502 | Đăng bài thất bại. Không có gì được xác nhận là
    đã đăng. |

    | `upstream_timeout` | 502 | Lệnh đăng quá hạn chờ và bài CÓ THỂ đã đi. ĐỪNG
    gọi lại mù — tra `GET /v1/posts/status` trước. |

    | `internal_error` | 500, 502 | Lỗi ngoài dự tính ở phía chúng tôi. |


    ## Tài liệu này được tạo ra thế nào


    Sinh bởi `api/_build.py` từ chính mã nguồn gateway. Đường dẫn lấy từ
    `extract_endpoints()`, tên các

    trường bắt buộc lấy từ `extract_required()` (đọc chính câu lỗi mà gateway
    trả về), mã lỗi lấy từ

    union `ErrorCode`, danh sách kênh và trường thông tin đăng nhập lấy từ
    `gateway/src/platforms.ts`.

    Kiểu dữ liệu, câu chữ và hình dạng phản hồi là phần viết tay, và đó là phần
    còn có thể lệch;

    `dashboard/test/openapi-drift.test.ts` là thứ giữ cho phần còn lại không
    lệch được.


    Tài liệu đầy đủ: https://docs.madiad.com
  contact:
    name: MADIAD
    url: https://docs.madiad.com
    email: info@madiad.com
  termsOfService: https://madiad.com/en/terms
  license:
    name: Proprietary — MADIAD Terms of Service
    url: https://madiad.com/en/terms
servers:
  - url: https://api.madiad.com
security:
  - bearerAuth: []
tags:
  - name: posts
    description: Đăng bài và tra lại kết quả
  - name: schedule
    description: Bài hẹn giờ
  - name: queue
    description: Hàng chờ đăng bài
  - name: profiles
    description: Hồ sơ thương hiệu
  - name: connections
    description: Nối kênh vào một hồ sơ
  - name: tiktok_music
    description: Nhạc TikTok cho bài ảnh
  - name: analytics
    description: Số liệu
  - name: engagement
    description: Bình luận và tin nhắn
  - name: autodm
    description: Bộ theo dõi tự trả lời bình luận
  - name: gbp
    description: Địa điểm và đánh giá Google Business
  - name: ffmpeg
    description: Xử lý video
  - name: usage
    description: Hạn mức của gói
  - name: status
    description: Tình trạng dịch vụ
  - name: health
    description: Kiểm tra sống
paths:
  /v1/posts/document:
    post:
      tags:
        - posts
      summary: Đăng bài tài liệu
      description: >-
        Đăng tệp PDF, PPT, PPTX, DOC hoặc DOCX. `caption` trở thành tên tài liệu
        trên bài.
      operationId: publishDocument
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Khoá bạn tự sinh cho TỪNG bài, ghi xuống TRƯỚC khi gọi. Dùng lại một
            khoá đã hoàn tất thì API trả về kết quả cũ chứ không đăng bài mới —
            nhìn từ ngoài giống hệt bị bỏ qua trong im lặng. Dùng lại cho một
            thân yêu cầu KHÁC thì nhận `idempotency_conflict`.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                profile_id:
                  type: string
                  description: >-
                    Id của hồ sơ thương hiệu cần thao tác. Lấy danh sách bằng
                    `GET /v1/profiles`.
                platforms:
                  type: array
                  items:
                    type: string
                    enum:
                      - tiktok
                      - instagram
                      - linkedin
                      - youtube
                      - facebook
                      - x
                      - threads
                      - pinterest
                      - reddit
                      - bluesky
                      - google_business
                      - telegram
                      - discord
                      - slack
                      - mastodon
                      - nostr
                      - lemmy
                      - devto
                      - hashnode
                      - wordpress
                      - whop
                      - listmonk
                  description: >-
                    Các kênh cần đăng. Nhận cả chuỗi các mã cách nhau bằng dấu
                    phẩy.
                document_url:
                  type: string
                  format: uri
                  description: Địa chỉ https:// của tài liệu cần đăng.
                caption:
                  type: string
                  description: >-
                    PHẦN THÂN của bài. Đây KHÔNG phải tiêu đề: `title` (và
                    `<kênh>_title` cho riêng từng kênh) là trường khác. Nhét
                    thân bài vào `title` thì bài vẫn đăng được nên rất khó phát
                    hiện, nhưng nó đi vòng qua phép kiểm độ dài thân bài.
                scheduled_at:
                  type: string
                  format: date-time
                  description: >-
                    Đăng vào thời điểm này thay vì đăng ngay (ISO 8601). Đi kèm
                    `timezone`.
                timezone:
                  type: string
                  description: Múi giờ IANA cho `scheduled_at`, ví dụ `Asia/Ho_Chi_Minh`.
              required:
                - profile_id
                - platforms
                - document_url
                - caption
      responses:
        '200':
          description: Thành công.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublishResult'
        '400':
          description: Lệnh bị từ chối trước khi tiêu tốn bất cứ thứ gì.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Thiếu API key hoặc key không hợp lệ.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Gói dịch vụ không bao gồm việc này.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Tài khoản này không có tài nguyên đó.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Chạm giới hạn tần suất hoặc hạn mức của gói.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Lỗi ngoài dự tính.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: Đăng bài thất bại, hoặc quá hạn chờ và chưa rõ kết quả.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    PublishResult:
      type: object
      properties:
        status:
          type: string
          enum:
            - completed
            - partial
            - failed
            - processing
            - scheduled
          description: >-
            `processing` và `scheduled` chưa phải kết quả — tra lại bằng `GET
            /v1/posts/status`.
        profile_id:
          type: string
        platforms:
          type: array
          items:
            type: string
        failed_platforms:
          type: array
          items:
            type: string
          description: Chỉ xuất hiện khi có ít nhất một kênh từ chối.
        request_id:
          type:
            - string
            - 'null'
        job_id:
          type:
            - string
            - 'null'
        results:
          type:
            - object
            - 'null'
          additionalProperties:
            $ref: '#/components/schemas/PlatformResult'
          description: Khoá theo tên kênh.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
    PlatformResult:
      type: object
      description: >-
        Kết luận của riêng một kênh. `success: false` ở đây là KÊNH từ chối,
        không phải Hub từ chối.
      properties:
        success:
          type: boolean
        error:
          type: string
      additionalProperties: true
    ErrorCode:
      type: string
      description: >-
        Toàn bộ các mã lỗi API có thể trả về. Ý nghĩa từng mã xem bảng ở phần mở
        đầu.
      enum:
        - unauthorized
        - forbidden
        - not_found
        - invalid_request
        - quota_exceeded
        - no_active_plan
        - profile_limit_reached
        - rate_limited
        - idempotency_conflict
        - idempotency_in_progress
        - page_pin_conflict
        - page_pin_unverified
        - platform_not_startable
        - platform_unavailable
        - link_not_found
        - platform_not_connected
        - location_required
        - upstream_error
        - upstream_timeout
        - internal_error
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key của bạn, gửi ở header `Authorization: Bearer <key>`. Một key
        quản lý mọi hồ sơ trong tài khoản.

````