# Cơ cấu người theo dõi Source: https://docs.madiad.com/vi/api-reference/analytics/cơ-cấu-người-theo-dõi /openapi.vi.yaml get /v1/analytics/audience Ai đang theo dõi tài khoản trên một kênh, kể cả số người theo dõi đang trực tuyến theo từng giờ trong ngày. Hiện chỉ TikTok trả lời; kênh khác trả `platform_not_supported`. # Gợi ý nội dung từ một từ khoá gốc Source: https://docs.madiad.com/vi/api-reference/analytics/gợi-ý-nội-dung-từ-một-từ-khoá-gốc /openapi.vi.yaml get /v1/analytics/suggestions Hashtag nên đi cùng từ khoá gốc `q`, hoặc những gì người dùng hay tìm quanh nó. Hiện chỉ TikTok trả lời. # Lượt hiển thị theo thời gian Source: https://docs.madiad.com/vi/api-reference/analytics/lượt-hiển-thị-theo-thời-gian /openapi.vi.yaml get /v1/analytics/impressions Tổng lượt hiển thị của một hồ sơ trong một `period` đặt sẵn hoặc từ `start_date` đến `end_date`. `platform`, `breakdown` và `metrics` để thu hẹp kết quả. # Số liệu các bài gần đây Source: https://docs.madiad.com/vi/api-reference/analytics/số-liệu-các-bài-gần-đây /openapi.vi.yaml get /v1/analytics/posts Các bài một hồ sơ đã đăng trên một kênh trong khoảng 30 ngày gần đây, mới nhất trước, kèm số liệu đã biết của từng bài. Bài đăng qua API này có trong danh sách khi kênh đã trả về mã bài (100 bài mới nhất của mỗi kênh trong khoảng). Bài chưa được đọc số liệu thì `metrics` là `null`; mỗi lần gọi đọc tối đa 6 bài như vậy trong chính danh sách trả về, bài mới nhất trước. Bài mà kênh không cho số liệu thì vẫn là `null` và được để yên khoảng 30 phút. `seed=false` thì bỏ qua bước đọc đó. Bài đăng ngoài API có trong danh sách sau khi số liệu của nó được đọc bằng `GET /v1/analytics/post-organic`. `limit` (mặc định 50, tối đa 200), `since` và `until` để thu hẹp kết quả. `limit` và `has_more` nói về chính kết quả này (không có con trỏ trang: muốn xem thêm thì tăng `limit`); `cached_posts_in_range` và `published_posts_in_range` là bộ đếm của kho số liệu, không phải của danh sách. Muốn biết các bài gần đây chạy ra sao thì dùng lệnh này. # Số liệu cấp hồ sơ Source: https://docs.madiad.com/vi/api-reference/analytics/số-liệu-cấp-hồ-sơ /openapi.vi.yaml get /v1/analytics/profile Số người theo dõi, lượt tiếp cận và tương tác của một hồ sơ trên từng kênh trong `platforms`. Số liệu Google Business tính theo địa điểm: không gửi `location_id` thì dùng địa điểm duy nhất hoặc địa điểm mặc định của hồ sơ; có nhiều địa điểm mà không có mặc định thì kênh đó trả `location_required`. # Số liệu của một bài mình đã đăng Source: https://docs.madiad.com/vi/api-reference/analytics/số-liệu-của-một-bài-mình-đã-đăng /openapi.vi.yaml get /v1/analytics/post Số liệu của một bài đăng qua API này, tra bằng `request_id` mà lệnh đăng đã trả về. `request_id` không thuộc tài khoản này thì trả 404. # Số liệu tự nhiên của một bài, tra theo id của kênh Source: https://docs.madiad.com/vi/api-reference/analytics/số-liệu-tự-nhiên-của-một-bài-tra-theo-id-của-kênh /openapi.vi.yaml get /v1/analytics/post-organic Dùng cho bài đang tồn tại trên kênh, bất kể nó có được đăng qua API này hay không. # Chạy lại một bộ theo dõi Source: https://docs.madiad.com/vi/api-reference/autodm/chạy-lại-một-bộ-theo-dõi /openapi.vi.yaml post /v1/autodm/monitors/{id}/resume Cho một bộ theo dõi đang tạm dừng trả lời bình luận trở lại. # Danh sách bộ theo dõi bình luận Source: https://docs.madiad.com/vi/api-reference/autodm/danh-sách-bộ-theo-dõi-bình-luận /openapi.vi.yaml get /v1/autodm/monitors Các bộ theo dõi bình luận tài khoản này đã tạo và chưa xoá, mới nhất trước, kèm bài mà mỗi bộ đang canh, tối đa 100, không phân trang. Không kèm trạng thái đang chạy: xem trạng thái từng bộ bằng `GET /v1/autodm/monitors/{id}`. # Dừng hẳn một bộ theo dõi Source: https://docs.madiad.com/vi/api-reference/autodm/dừng-hẳn-một-bộ-theo-dõi /openapi.vi.yaml post /v1/autodm/monitors/{id}/stop Dừng một bộ theo dõi bình luận. Chỉ muốn ngừng một lúc rồi chạy tiếp thì dùng tạm dừng. # Tạm dừng một bộ theo dõi Source: https://docs.madiad.com/vi/api-reference/autodm/tạm-dừng-một-bộ-theo-dõi /openapi.vi.yaml post /v1/autodm/monitors/{id}/pause Tạm dừng một bộ theo dõi bình luận cho tới khi chạy lại bằng `POST /v1/autodm/monitors/{id}/resume`. # Tạo một bộ theo dõi bình luận Source: https://docs.madiad.com/vi/api-reference/autodm/tạo-một-bộ-theo-dõi-bình-luận /openapi.vi.yaml post /v1/autodm/monitors Canh một bài và tự trả lời những bình luận khớp điều kiện. # Xem hoạt động của một bộ theo dõi Source: https://docs.madiad.com/vi/api-reference/autodm/xem-hoạt-động-của-một-bộ-theo-dõi /openapi.vi.yaml get /v1/autodm/monitors/{id}/logs Nhật ký hoạt động của một bộ theo dõi bình luận. Bộ không do tài khoản này tạo thì trả 404. # Xem một bộ theo dõi Source: https://docs.madiad.com/vi/api-reference/autodm/xem-một-bộ-theo-dõi /openapi.vi.yaml get /v1/autodm/monitors/{id} Trạng thái đang chạy của một bộ theo dõi bình luận. Bộ không do tài khoản này tạo thì trả 404. Nhật ký hoạt động của bộ nằm ở `GET /v1/autodm/monitors/{id}/logs`. # Xoá một bộ theo dõi Source: https://docs.madiad.com/vi/api-reference/autodm/xoá-một-bộ-theo-dõi /openapi.vi.yaml delete /v1/autodm/monitors/{id} Xoá một bộ theo dõi bình luận. Xoá xong thì bộ đó không còn trong `GET /v1/autodm/monitors`. # Bắt đầu đăng nhập cho một kênh Source: https://docs.madiad.com/vi/api-reference/connections/bắt-đầu-đăng-nhập-cho-một-kênh /openapi.vi.yaml post /v1/connections/start-platform Trả về địa chỉ cấp quyền của chính kênh đó, để bảng chọn kênh nằm trong giao diện của bạn. Mã trạng thái trong địa chỉ đó dùng một lần và hết hạn sau khoảng 15 phút — chuyển hướng ngay, đừng lưu lại. # Danh sách bảng Pinterest Source: https://docs.madiad.com/vi/api-reference/connections/danh-sách-bảng-pinterest /openapi.vi.yaml get /v1/connections/pinterest/boards Các bảng Pinterest mà hồ sơ ghim bài lên được. Bài Pinterest nào cũng cần một bảng, gửi trong `pinterest_board_id`. # Danh sách Trang Facebook Source: https://docs.madiad.com/vi/api-reference/connections/danh-sách-trang-facebook /openapi.vi.yaml get /v1/connections/facebook/pages Các Trang Facebook mà tài khoản đã nối của hồ sơ đăng được. Hồ sơ có nhiều Trang thì gửi id Trang đã chọn trong `facebook_page_id` khi đăng. # Danh sách tổ chức LinkedIn Source: https://docs.madiad.com/vi/api-reference/connections/danh-sách-tổ-chức-linkedin /openapi.vi.yaml get /v1/connections/linkedin/pages Các tổ chức LinkedIn mà tài khoản LinkedIn đã nối của hồ sơ đăng bài thay mặt được. # Ghim Trang Facebook Source: https://docs.madiad.com/vi/api-reference/connections/ghim-trang-facebook /openapi.vi.yaml post /v1/connections/facebook/page Ghim một Trang Facebook cho hồ sơ này. Ghim THẮNG id gửi kèm từng bài, nên nó chỉ hợp với hồ sơ phục vụ MỘT thương hiệu. Trên một hồ sơ cố ý dùng để với tới nhiều Trang, ghim sẽ lặng lẽ đẩy một bài đã ghi đúng địa chỉ sang chỗ khác — ở đó thì gỡ ghim chứ đừng ghim. # Ghim tổ chức LinkedIn Source: https://docs.madiad.com/vi/api-reference/connections/ghim-tổ-chức-linkedin /openapi.vi.yaml post /v1/connections/linkedin/page Ghim một tổ chức LinkedIn cho hồ sơ này. Ghim THẮNG id gửi kèm từng bài, nên nó chỉ hợp với hồ sơ phục vụ MỘT thương hiệu. Trên một hồ sơ cố ý dùng để với tới nhiều Trang, ghim sẽ lặng lẽ đẩy một bài đã ghi đúng địa chỉ sang chỗ khác — ở đó thì gỡ ghim chứ đừng ghim. # Ghim địa điểm Google Business Source: https://docs.madiad.com/vi/api-reference/connections/ghim-địa-điểm-google-business /openapi.vi.yaml post /v1/connections/google_business/page Ghim một địa điểm Google Business cho hồ sơ này. Ghim THẮNG id gửi kèm từng bài, nên nó chỉ hợp với hồ sơ phục vụ MỘT thương hiệu. Trên một hồ sơ cố ý dùng để với tới nhiều Trang, ghim sẽ lặng lẽ đẩy một bài đã ghi đúng địa chỉ sang chỗ khác — ở đó thì gỡ ghim chứ đừng ghim. # Gỡ ghim Trang Facebook Source: https://docs.madiad.com/vi/api-reference/connections/gỡ-ghim-trang-facebook /openapi.vi.yaml delete /v1/connections/facebook/page Gỡ Trang Facebook đang ghim khỏi hồ sơ này, để `facebook_page_id` gửi kèm từng bài lại quyết định bài lên đâu. # Gỡ ghim tổ chức LinkedIn Source: https://docs.madiad.com/vi/api-reference/connections/gỡ-ghim-tổ-chức-linkedin /openapi.vi.yaml delete /v1/connections/linkedin/page Gỡ tổ chức LinkedIn đang ghim khỏi hồ sơ này, để `target_linkedin_page_id` gửi kèm từng bài lại quyết định bài lên đâu. # Gỡ ghim địa điểm Google Business Source: https://docs.madiad.com/vi/api-reference/connections/gỡ-ghim-địa-điểm-google-business /openapi.vi.yaml delete /v1/connections/google_business/page Gỡ địa điểm Google Business đang ghim khỏi hồ sơ này, để `gbp_location_id` gửi kèm từng bài lại quyết định bài lên đâu. # Mở trang nối kênh Source: https://docs.madiad.com/vi/api-reference/connections/mở-trang-nối-kênh /openapi.vi.yaml post /v1/connections/start Trả về một địa chỉ nối kênh có hạn dùng, để người dùng cuối mở ra và nối tài khoản của họ. # Nối devto bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-devto-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/devto `devto` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối discord bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-discord-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/discord `discord` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối hashnode bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-hashnode-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/hashnode `hashnode` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối lemmy bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-lemmy-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/lemmy `lemmy` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối listmonk bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-listmonk-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/listmonk `listmonk` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối mastodon bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-mastodon-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/mastodon `mastodon` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối nostr bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-nostr-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/nostr `nostr` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối slack bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-slack-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/slack `slack` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối telegram bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-telegram-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/telegram `telegram` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối whop bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-whop-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/whop `whop` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Nối wordpress bằng thông tin đăng nhập riêng Source: https://docs.madiad.com/vi/api-reference/connections/nối-wordpress-bằng-thông-tin-đăng-nhập-riêng /openapi.vi.yaml post /v1/connections/wordpress `wordpress` xác thực bằng thông tin đăng nhập chứ không qua chuyển hướng đăng nhập, nên nối ở đây thay vì qua trang nối kênh. Phần bí mật được lưu một chiều và không bao giờ trả ngược ra. # Xem mỗi hồ sơ đã nối những kênh nào Source: https://docs.madiad.com/vi/api-reference/connections/xem-mỗi-hồ-sơ-đã-nối-những-kênh-nào /openapi.vi.yaml get /v1/connections/status Một chuỗi rỗng ở một kênh KHÔNG mang thông tin gì về quá khứ: hồ sơ mới sinh ra đã có sẵn vài kênh mang giá trị `""`, nên đó không có nghĩa là kết nối bị mất. # Xem Trang Facebook đang ghim Source: https://docs.madiad.com/vi/api-reference/connections/xem-trang-facebook-đang-ghim /openapi.vi.yaml get /v1/connections/facebook/page Trang Facebook mà hồ sơ này luôn đăng lên, hoặc `null` nếu chưa ghim gì. # Xem tổ chức LinkedIn đang ghim Source: https://docs.madiad.com/vi/api-reference/connections/xem-tổ-chức-linkedin-đang-ghim /openapi.vi.yaml get /v1/connections/linkedin/page tổ chức LinkedIn mà hồ sơ này luôn đăng lên, hoặc `null` nếu chưa ghim gì. # Xem địa điểm Google Business đang ghim Source: https://docs.madiad.com/vi/api-reference/connections/xem-địa-điểm-google-business-đang-ghim /openapi.vi.yaml get /v1/connections/google_business/page địa điểm Google Business mà hồ sơ này luôn đăng lên, hoặc `null` nếu chưa ghim gì. # Danh sách hội thoại tin nhắn riêng Source: https://docs.madiad.com/vi/api-reference/engagement/danh-sách-hội-thoại-tin-nhắn-riêng /openapi.vi.yaml get /v1/engagement/dms/conversations Các cuộc hội thoại tin nhắn riêng của tài khoản đã nối của hồ sơ trên một kênh. Cần gói có tính năng tin nhắn riêng, như khi gửi tin. # Danh sách media có thể tương tác Source: https://docs.madiad.com/vi/api-reference/engagement/danh-sách-media-có-thể-tương-tác /openapi.vi.yaml get /v1/engagement/media Các bài của tài khoản đã nối trên một kênh, để đọc và trả lời bình luận. Danh sách tra theo tài khoản đã nối chứ không theo hồ sơ: tài khoản quản lý nhiều Trang Facebook hoặc LinkedIn thì có mục thuộc Trang khác. Muốn số liệu bài theo đúng hồ sơ thì dùng `GET /v1/analytics/posts`. # Gửi tin nhắn riêng Source: https://docs.madiad.com/vi/api-reference/engagement/gửi-tin-nhắn-riêng /openapi.vi.yaml post /v1/engagement/dms/send Gửi tin nhắn riêng cho một người trên một kênh. Cần gói có tính năng tin nhắn riêng và tính vào hạn mức tin nhắn mỗi ngày; tin nào kênh từ chối thì không bị tính. Khi mất câu trả lời của kênh (`502 upstream_timeout`), tin có thể đã tới người nhận nên vẫn được tính: xem lại hội thoại trước khi gửi lại. # Kiểm duyệt một bình luận Source: https://docs.madiad.com/vi/api-reference/engagement/kiểm-duyệt-một-bình-luận /openapi.vi.yaml post /v1/engagement/comments/action Ẩn, bỏ ẩn, thích, bỏ thích, ghim hoặc bỏ ghim một bình luận. `hide`, `unhide`, `pin` và `unpin` cần thêm `post_id`. Cần gói có tính năng viết bình luận. Hiện chỉ TikTok trả lời; kênh khác trả `platform_not_supported`. # Trả lời ngay dưới bình luận Source: https://docs.madiad.com/vi/api-reference/engagement/trả-lời-ngay-dưới-bình-luận /openapi.vi.yaml post /v1/engagement/comments/public-reply Một trả lời công khai dưới bình luận. Tính là một lượt viết bình luận, không phải tin nhắn riêng. # Trả lời riêng người bình luận Source: https://docs.madiad.com/vi/api-reference/engagement/trả-lời-riêng-người-bình-luận /openapi.vi.yaml post /v1/engagement/comments/reply Gửi tin nhắn riêng cho người bình luận. Đây là tin nhắn riêng và tính vào hạn mức tin nhắn mỗi ngày — muốn trả lời ngay dưới bình luận thì dùng `POST /v1/engagement/comments/public-reply`. Khi mất câu trả lời (`502 upstream_timeout`), tin vẫn được tính vì có thể đã tới người nhận. # Viết một bình luận Source: https://docs.madiad.com/vi/api-reference/engagement/viết-một-bình-luận /openapi.vi.yaml post /v1/engagement/comments/create Một bình luận gốc dưới bài, hoặc một trả lời công khai dưới bình luận khác. Cần `post_id` / `post_url` / `comment_id` nào là tuỳ kênh. # Xoá một bình luận Source: https://docs.madiad.com/vi/api-reference/engagement/xoá-một-bình-luận /openapi.vi.yaml post /v1/engagement/comments/delete Không khôi phục được ở phía kênh. # Đọc bình luận của một bài Source: https://docs.madiad.com/vi/api-reference/engagement/đọc-bình-luận-của-một-bài /openapi.vi.yaml get /v1/engagement/comments Gửi `post_id` (hoặc `media_id`) hoặc `post_url`. # Gửi một việc xử lý video Source: https://docs.madiad.com/vi/api-reference/ffmpeg/gửi-một-việc-xử-lý-video /openapi.vi.yaml post /v1/ffmpeg/jobs Gửi `file_url` hoặc `file_urls`, chỉ nhận https://. Lệnh được kiểm tra với danh sách cho phép trước khi tiêu tốn hạn mức. # Tra một việc Source: https://docs.madiad.com/vi/api-reference/ffmpeg/tra-một-việc /openapi.vi.yaml get /v1/ffmpeg/jobs/{id} Trạng thái của một việc xử lý video và, khi đã xong, thời lượng tệp kết quả. Tải tệp bằng `GET /v1/ffmpeg/jobs/{id}/result`. # Tải tệp kết quả Source: https://docs.madiad.com/vi/api-reference/ffmpeg/tải-tệp-kết-quả /openapi.vi.yaml get /v1/ffmpeg/jobs/{id}/result Trả thẳng tệp đã xử lý. Trước khi việc chạy xong thì trả 502. # Danh sách địa điểm Google Business Source: https://docs.madiad.com/vi/api-reference/gbp/danh-sách-địa-điểm-google-business /openapi.vi.yaml get /v1/gbp/locations Các địa điểm Google Business mà tài khoản Google đã nối của hồ sơ đang quản lý. Trường `name` của mỗi địa điểm (ví dụ `locations/123`) là giá trị gửi làm `location_id` khi đọc đánh giá, xem số liệu và ghim. # Trả lời một đánh giá Google Source: https://docs.madiad.com/vi/api-reference/gbp/trả-lời-một-đánh-giá-google /openapi.vi.yaml post /v1/gbp/reviews/reply Chỉ định đánh giá bằng `review_id` hoặc `review_name`. # Đọc đánh giá Google Source: https://docs.madiad.com/vi/api-reference/gbp/đọc-đánh-giá-google /openapi.vi.yaml get /v1/gbp/reviews Đánh giá của một địa điểm Google Business. Không gửi `location_id` thì dùng địa điểm duy nhất hoặc địa điểm mặc định của hồ sơ; có nhiều địa điểm mà không có mặc định thì lệnh trả 400 `location_required`. Kết quả kèm luôn danh sách địa điểm của hồ sơ. # Kiểm tra sống Source: https://docs.madiad.com/vi/api-reference/health/kiểm-tra-sống /openapi.vi.yaml get /v1/health Trả `ok: true` kèm giờ máy chủ khi API đang chạy. Không cần API key. # Danh sách bài đã đăng Source: https://docs.madiad.com/vi/api-reference/posts/danh-sách-bài-đã-đăng /openapi.vi.yaml get /v1/posts/history Các bài tài khoản này đã đăng qua API, mới nhất trước, kèm loại bài, `platforms`, `request_id` và kết quả (`completed`, `draft`, `partial`, `failed`, `canceled` hoặc `unknown`). `limit` mặc định 20, tối đa 100. # Gỡ một bài đã đăng Source: https://docs.madiad.com/vi/api-reference/posts/gỡ-một-bài-đã-đăng /openapi.vi.yaml post /v1/posts/unpublish Xoá bài khỏi kênh. Không phải kênh nào cũng cho phép. Phản hồi có trường `removal`: `confirmed` khi kênh báo bài đã được gỡ, `unverified` khi kênh chỉ nhận lệnh (X và LinkedIn, hai kênh nhận lệnh gỡ cả khi id bài không tồn tại). Với Google Business, `post_id` là tên đầy đủ của bài, `accounts/…/locations/…/localPosts/…`. # Tra kết quả của một bài Source: https://docs.madiad.com/vi/api-reference/posts/tra-kết-quả-của-một-bài /openapi.vi.yaml get /v1/posts/status Tra một bài bằng `request_id` (hoặc `job_id`) mà lệnh đăng đã trả về. Chỉ gửi một trong hai. Phản hồi đầu tiên của lệnh đăng hiếm khi là kết quả cuối — nhiều kênh nhận bài rồi mới xử lý. Bài hẹn giờ đã huỷ trước khi chạy thì trả `canceled`, `results` là `null`, và giữ nguyên như vậy. Bài mà lệnh đăng trả `502 upstream_timeout` thì tra bằng `request_id` trong chính câu trả lời đó: trả `unknown` (`results` là `null`) cho tới khi phía các kênh nhận được bài, sau đó là `processing` rồi kết quả. # Đăng bài chữ Source: https://docs.madiad.com/vi/api-reference/posts/đăng-bài-chữ /openapi.vi.yaml post /v1/posts/text Đăng phần chữ lên một hoặc nhiều kênh. Có thể ghi đè cho riêng từng kênh bằng các trường gửi thêm, chúng được chuyển đi nguyên vẹn: `_caption` ghi đè phần thân và `_title` ghi đè tiêu đề, cho bất kỳ kênh nào có trong `platforms`. Những kênh nhận bài chữ: `linkedin`, `facebook`, `x`, `threads`, `reddit`, `bluesky`, `google_business`, `telegram`, `discord`, `slack`, `mastodon`, `nostr`, `lemmy`, `devto`, `hashnode`, `wordpress`, `whop`, `listmonk`. # Đăng bài tài liệu Source: https://docs.madiad.com/vi/api-reference/posts/đăng-bài-tài-liệu /openapi.vi.yaml post /v1/posts/document Đăng tệp PDF, PPT, PPTX, DOC hoặc DOCX. `caption` trở thành tên tài liệu trên bài. # Đăng bài video Source: https://docs.madiad.com/vi/api-reference/posts/đăng-bài-video /openapi.vi.yaml post /v1/posts/video Đăng video theo địa chỉ. Có thể ghi đè cho riêng từng kênh bằng các trường gửi thêm, chúng được chuyển đi nguyên vẹn: `_caption` ghi đè phần thân và `_title` ghi đè tiêu đề, cho bất kỳ kênh nào có trong `platforms`. Những kênh nhận video: `tiktok`, `instagram`, `linkedin`, `youtube`, `facebook`, `x`, `threads`, `pinterest`, `reddit`, `bluesky`, `google_business`, `telegram`, `discord`, `mastodon`, `wordpress`. # Đăng bài ảnh Source: https://docs.madiad.com/vi/api-reference/posts/đăng-bài-ảnh /openapi.vi.yaml post /v1/posts/photos Đăng một hoặc nhiều ảnh. Gửi dạng `multipart/form-data` để tải thẳng tệp trong `photos[]`, hoặc dạng JSON khi mọi ảnh đều là địa chỉ. Có thể ghi đè cho riêng từng kênh bằng các trường gửi thêm, chúng được chuyển đi nguyên vẹn: `_caption` ghi đè phần thân và `_title` ghi đè tiêu đề, cho bất kỳ kênh nào có trong `platforms`. # Đăng lại một bài thất bại Source: https://docs.madiad.com/vi/api-reference/posts/đăng-lại-một-bài-thất-bại /openapi.vi.yaml post /v1/posts/retry Gửi lại một bài đã hỏng. Chỉ gửi một trong hai: `request_id` hoặc `job_id`. # Danh sách hồ sơ thương hiệu Source: https://docs.madiad.com/vi/api-reference/profiles/danh-sách-hồ-sơ-thương-hiệu /openapi.vi.yaml get /v1/profiles Các hồ sơ thương hiệu của tài khoản, cũ nhất trước, mỗi hồ sơ kèm `profile_id` mà các lệnh đăng bài, nối kênh và số liệu cần. Trả lời mà không phải hỏi kênh nào. # Sửa thiết lập hàng chờ Source: https://docs.madiad.com/vi/api-reference/queue/sửa-thiết-lập-hàng-chờ /openapi.vi.yaml post /v1/queue/settings Mọi trường khác trong thân yêu cầu được chuyển thẳng sang hàng chờ như đã gửi. # Xem khung giờ trống gần nhất Source: https://docs.madiad.com/vi/api-reference/queue/xem-khung-giờ-trống-gần-nhất /openapi.vi.yaml get /v1/queue/next-slot `next_slot` là `null` khi trong 30 ngày tới không còn khung nào trống. Đó là một câu trả lời, không phải lỗi. # Xem thiết lập hàng chờ Source: https://docs.madiad.com/vi/api-reference/queue/xem-thiết-lập-hàng-chờ /openapi.vi.yaml get /v1/queue/settings Thiết lập hàng chờ đăng bài của một hồ sơ, như múi giờ và các khung giờ. Cần gói có tính năng hàng chờ. # Xem trước các khung giờ tiếp theo Source: https://docs.madiad.com/vi/api-reference/queue/xem-trước-các-khung-giờ-tiếp-theo /openapi.vi.yaml get /v1/queue/preview Trả lời câu mà thiết lập hàng chờ không trả lời được: không phải "khung giờ rơi vào lúc nào" mà "bài tiếp theo thực sự đi lúc nào". # Danh sách bài đã hẹn giờ Source: https://docs.madiad.com/vi/api-reference/schedule/danh-sách-bài-đã-hẹn-giờ /openapi.vi.yaml get /v1/schedule Các bài đã hẹn giờ của một hồ sơ mà chưa đăng. Cần gói có tính năng hẹn giờ. # Huỷ một bài đã hẹn giờ Source: https://docs.madiad.com/vi/api-reference/schedule/huỷ-một-bài-đã-hẹn-giờ /openapi.vi.yaml delete /v1/schedule/{job_id} Huỷ một bài đã hẹn trước khi nó đăng. Bài được tách thành nhiều việc (ví dụ khi ghi đè caption cho một kênh) thì huỷ cả bài; việc nào không huỷ được thì lệnh trả 502 và nêu tên việc đó, vì phần ấy vẫn sẽ đăng. Huỷ không hoàn lại lượt đăng bài đã giữ chỗ. Bài không còn chờ trong lịch (đang đăng, đã chạy xong hoặc đã huỷ trước đó) thì không huỷ: lệnh trả 400. Sau khi huỷ, `GET /v1/posts/status` trả `canceled` cho bài đó. # Đổi giờ hoặc sửa một bài đã hẹn Source: https://docs.madiad.com/vi/api-reference/schedule/đổi-giờ-hoặc-sửa-một-bài-đã-hẹn /openapi.vi.yaml patch /v1/schedule/{job_id} Đổi giờ, múi giờ hoặc caption của một bài đã hẹn trước khi nó đăng. Gửi ít nhất một trong `scheduled_at`, `timezone`, `caption`. Mọi việc của bài đều được cập nhật; việc nào lỗi thì lệnh trả 502 và việc đó giữ lịch cũ. Bài không còn chờ trong lịch (đang đăng, đã chạy xong hoặc đã huỷ trước đó) thì không đổi: lệnh trả 400. # Tình trạng dịch vụ Source: https://docs.madiad.com/vi/api-reference/status/tình-trạng-dịch-vụ /openapi.vi.yaml get /v1/status Công khai, không cần xác thực, có bộ đệm ở biên. Chỉ gồm số liệu tổng hợp. # Bảng xếp hạng nhạc TikTok Source: https://docs.madiad.com/vi/api-reference/tiktok_music/bảng-xếp-hạng-nhạc-tiktok /openapi.vi.yaml get /v1/tiktok/music/trending Những bài nhạc gắn được vào bài ảnh TikTok, lấy từ bảng xếp hạng Commercial Music Library của TikTok cho một quốc gia: nhạc TikTok cho phép tài khoản doanh nghiệp dùng, không phải những bài đang viral. Mỗi bảng tối đa 100 bài, bài nào cũng có `preview_url` để nghe thử trước khi chọn. Chọn xong thì gửi `id` của bài làm `tiktok_music_id` khi gọi `POST /v1/posts/photos` có `tiktok` trong `platforms`. Hồ sơ mà kết nối TikTok chưa có quyền dùng nhạc sẽ nhận `platform_not_connected`: hãy kết nối lại TikTok cho hồ sơ đó. Nên gọi lệnh này để kiểm tra trước khi đăng. Hồ sơ thiếu quyền mà vẫn gửi bài ảnh kèm nhạc thì bài không bị từ chối, nhưng lên mà không có nhạc, và kết quả bài có một mục `warnings` mang mã `music_not_applied`. # Tìm nhạc TikTok Source: https://docs.madiad.com/vi/api-reference/tiktok_music/tìm-nhạc-tiktok /openapi.vi.yaml get /v1/tiktok/music/search Tìm một bài theo chữ trong tên bài hoặc tên nghệ sĩ. Phạm vi tìm là các bảng xếp hạng hiện tại, không phải toàn bộ kho nhạc của TikTok, nên bài không nằm trên bảng nào thì gõ đúng tên cũng không ra. Nếu để một người chọn nhạc, hãy liệt kê bằng `GET /v1/tiktok/music/trending`. Chọn xong thì gửi `id` làm `tiktok_music_id` khi đăng bài. # Xem hạn mức và mức đã dùng Source: https://docs.madiad.com/vi/api-reference/usage/xem-hạn-mức-và-mức-đã-dùng /openapi.vi.yaml get /v1/usage Gói cho phép những gì và còn lại bao nhiêu, không cần phải gọi cho tới khi dính 429 mới biết. # Xác thực Source: https://docs.madiad.com/vi/authentication Tạo API key và uỷ quyền cho mọi request gửi đến MADIAD Hub. MADIAD Hub sử dụng **API key** để xác thực các request. Mỗi lần gọi đều phải đính kèm key trong header `Authorization` dưới dạng Bearer token. ```bash theme={null} Authorization: Bearer mdc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` Request không có key hợp lệ sẽ nhận phản hồi `401 Unauthorized`. ## Tạo key 1. Đăng nhập vào [hub.madiad.com](https://hub.madiad.com). 2. Mở **API Keys** và nhấn **Create key**. 3. Đặt tên (ví dụ: `production`, `n8n`, `staging`) rồi sao chép giá trị. > [!WARNING] > > Key chỉ hiển thị **một lần duy nhất**, ngay khi tạo. Hãy sao chép ngay và lưu vào trình quản lý bí mật. Nếu mất key, thu hồi key đó và tạo cái mới. ## Định dạng key | Phần | Ví dụ | Ý nghĩa | | - | - | - | | Prefix | `mdc_live_` | Môi trường: key `live` sẽ đăng bài thật | | Body | `a1b2c3…` | Phần bí mật của key. Sau khi tạo, dashboard chỉ hiển thị prefix | Dashboard liệt kê từng key theo prefix và tên, giúp bạn nhận diện mà không lộ phần bí mật. ## Sử dụng key Gửi key dưới dạng Bearer token trong mọi request: ```bash theme={null} curl https://api.madiad.com/v1/connections/status \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Lưu key trong biến môi trường, không hard-code trong source hay đưa vào code phía client: ```bash theme={null} export MADIAD_API_KEY="mdc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` ## Xoay vòng và thu hồi Hãy đối xử với key như mật khẩu: * **Xoay vòng** định kỳ: tạo key mới, cập nhật ứng dụng, rồi thu hồi key cũ. * **Thu hồi ngay** nếu key bị lộ. Việc thu hồi có hiệu lực trong vài giây và không thể hoàn tác. * Dùng **key riêng cho từng tích hợp** (mỗi app, server hoặc workflow một key) để thu hồi một key không ảnh hưởng đến các bên còn lại. > [!NOTE] > > Cần uỷ quyền cho *tài khoản mạng xã hội* thay vì API request? Việc đó được thực hiện theo từng **Profile**, xem [Profiles](/vi/concepts/profiles). # Analytics & lịch sử Source: https://docs.madiad.com/vi/concepts/analytics Lấy số liệu Profile, hiệu suất từng bài, và lịch sử đăng bài của bạn. MADIAD Hub cung cấp báo cáo chỉ đọc cho các Profile và bài đăng của bạn. Mọi endpoint đều giới hạn trong phạm vi tài khoản, số liệu Profile được khóa theo `profile_id` của chính bạn, còn số liệu bài đăng yêu cầu `request_id` từ một lần đăng trước đó. Đây là các request `GET` và **không** tiêu tốn quota upload hay FFmpeg. > [!NOTE] > > **Analytics có số liệu cho mười kênh:** TikTok, Instagram, YouTube, Facebook, LinkedIn, X, Threads, Pinterest, Reddit và Bluesky. Google Business Profile và các kênh nhập thủ công (Telegram, Discord, Slack, Mastodon, Nostr, Lemmy, dev.to, Hashnode, WordPress, Whop, listmonk) vẫn đăng bài được, nhưng không có số liệu người theo dõi, phạm vi tiếp cận, tương tác hay lượt hiển thị. Hãy bỏ chúng khỏi danh sách `platforms` khi gọi analytics. Xem [Kênh](/vi/concepts/channels). ## Analytics theo Profile Số liệu follower, reach và engagement đa nền tảng cho một Profile. ```bash theme={null} curl "https://api.madiad.com/v1/analytics/profile?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83&platforms=instagram,tiktok,youtube" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` | Query param | Bắt buộc | Mô tả | | - | - | - | | `profile_id` | có | Profile cần báo cáo. | | `platforms` | có | Danh sách nền tảng phân tách bằng dấu phẩy, ví dụ `instagram,tiktok,linkedin`. | | `facebook_page_id` | không | Báo cáo cho một Facebook Page cụ thể. | | `linkedin_page_urn` | không | Báo cáo cho một trang tổ chức LinkedIn (mặc định là tài khoản đã kết nối). | | `location_id` | không | Địa điểm Google Business cần báo cáo, lấy từ `google_business_locations` trong response. Profile chỉ có một địa điểm thì tự dùng địa điểm đó. Có nhiều địa điểm thì dùng địa điểm mặc định đã chọn lúc kết nối Google Business; nếu chưa chọn, `google_business` trả `code: "location_required"` cho tới khi bạn truyền một địa điểm. `google_business_location_id` là địa điểm của số liệu, còn `google_business_location_source` cho biết đã chọn theo cách nào: `requested`, `only_location` hoặc `connection_default`. | Response gói payload số liệu theo nền tảng dưới khóa `analytics`: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "analytics": { "instagram": { "followers": 12840, "reach": 90213, "engagement": 0.047 }, "tiktok": { "followers": 5320, "views": 412900 } } } ``` ## Tổng impressions Tổng hợp impressions cho một Profile theo khoảng ngày hoặc theo một mốc thời gian định sẵn. ```bash theme={null} curl "https://api.madiad.com/v1/analytics/impressions?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83&period=last_month&breakdown=true" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` | Query param | Bắt buộc | Mô tả | | - | - | - | | `profile_id` | có | Profile cần báo cáo. | | `period` | không | Một trong `last_day`, `last_week`, `last_month`, `last_3months`, `last_year`. | | `start_date`, `end_date` | không | Khoảng `YYYY-MM-DD` (dùng thay cho `period`). | | `date` | không | Một ngày `YYYY-MM-DD` duy nhất. | | `platform` | không | Giới hạn về một nền tảng. | | `breakdown` | không | `true` để kèm chi tiết theo nền tảng hoặc theo ngày. | | `metrics` | không | Danh sách metrics phân tách bằng dấu phẩy, ví dụ `reach,views,likes,comments`. | ## Analytics theo từng bài Hiệu suất của một bài đăng, xác định bằng `request_id` trả về khi bạn đăng bài đó. ```bash theme={null} curl "https://api.madiad.com/v1/analytics/post?request_id=req_01HZX9H6S8T0V2W4X6Y8Z0A2B4" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` > [!NOTE] > > `request_id` phải thuộc về một bài đăng của chính bạn. Mọi id khác sẽ trả về `404 not_found`. ## Lịch sử đăng bài Danh sách phân trang, mới nhất trước, của mọi thứ bạn đã đăng qua MADIAD Hub. ```bash theme={null} curl "https://api.madiad.com/v1/posts/history?page=1&limit=20" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` | Query param | Bắt buộc | Mô tả | | - | - | - | | `page` | không | Số trang tính từ 1 (mặc định `1`). | | `limit` | không | Số mục mỗi trang, từ `1` đến `100` (mặc định `20`). | | `profile_id` | không | Lọc theo một Profile. | ```json theme={null} { "page": 1, "limit": 20, "total": 134, "items": [ { "id": "evt_01HZX9J8T0V2W4X6Y8Z0A2B4C6", "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "type": "video", "platforms": ["tiktok", "instagram"], "request_id": "req_01HZX9H6S8T0V2W4X6Y8Z0A2B4", "created_at": "2026-06-15T08:30:00Z", "status": "partial", "error": "Instagram: Action suspected as spam. Activity is restricted. · Tạm dịch: Hoạt động bị nghi là spam. Tài khoản đang bị hạn chế thao tác." } ] } ``` `status` dùng đúng bộ từ vựng của response lệnh đăng: `completed`, `draft`, `partial`, `failed` hoặc `unknown`, thêm `canceled` cho bài hẹn giờ đã hủy trước khi chạy, và `error` nêu lý do khi bài bị từ chối. Khi có nền tảng từ chối bài, trường này nêu tên từng nền tảng đó, kèm câu của chính nền tảng và, khi Hub có bản dịch tiếng Việt, phần dịch nằm sau `· Tạm dịch:`. Bài đăng bất đồng bộ được ghi nhận là đã tiếp nhận, rồi được chốt lại theo kết quả thật khi nền tảng trả lời: thường là lúc nền tảng báo đã xử lý xong bài, còn không thì ở lần kiểm tra hằng ngày. Vì vậy một mục vừa đăng có thể chuyển từ `completed` sang `partial` hoặc `failed` trong ngày. Dùng `request_id` từ một mục lịch sử để lấy [analytics theo từng bài](#analytics-theo-từng-bài) hoặc [kiểm tra trạng thái](/vi/concepts/posts) của nó. ## Số liệu từng bài trong 30 ngày > [!NOTE] > > **Kênh áp dụng:** Instagram · Facebook · YouTube · TikTok · LinkedIn · X · Threads · Pinterest · > Reddit · Bluesky. Truyền một `platform` mỗi lần gọi. Câu hỏi "mấy bài gần đây chạy thế nào" trả lời ở đây: danh sách các bài Profile đã đăng trên một kênh, mới nhất trước, kèm số liệu đã biết của từng bài. ```bash theme={null} curl "https://api.madiad.com/v1/analytics/posts?profile_id=$PROFILE_ID&platform=facebook" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Tuỳ chọn: `since`, `until`, `limit`, `seed`. Bài đăng qua Hub có trong danh sách khi kênh đã trả về mã bài (100 bài mới nhất của mỗi kênh trong khoảng thời gian). Số liệu của một bài chỉ có sau khi được đọc từ kênh: bài chưa được đọc thì `metrics` và `captured_at` là `null` (không phải 0). Mỗi lần gọi, Hub đọc tối đa 6 bài như vậy trong chính danh sách trả về, bài mới nhất trước, nên một kênh có nhiều bài sẽ có thêm số sau mỗi lần gọi. Bài mà kênh không cho số liệu (bài đã bị gỡ, hoặc loại bài kênh không thống kê) thì vẫn là `null`, và Hub để yên bài đó khoảng 30 phút trước khi thử lại. `captured_at` cho biết số liệu của bài được đọc lúc nào. Chỉ cần danh sách bài thì truyền `seed=false`, Hub không đọc số liệu bài nào trong lần gọi đó. `limit` mặc định 50, tối đa 200. `limit` và `has_more` trong kết quả nói về chính danh sách trả về (không có con trỏ trang: muốn xem thêm thì tăng `limit`); `cached_posts_in_range` và `published_posts_in_range` là bộ đếm của kho số liệu. Trên trang **Phân tích**, mục "Từng bài · 30 ngày" dùng chính lệnh này: chọn kênh là danh sách tải ngay. Một bài đăng ngoài Hub thì không có `request_id` để tra, và chỉ có trong danh sách trên sau khi số liệu của nó được đọc một lần. Dùng id của nền tảng: ```bash theme={null} curl "https://api.madiad.com/v1/analytics/post-organic?profile_id=$PROFILE_ID&platform=facebook&platform_post_id=$POST_ID" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ## Khán giả: họ online giờ nào > [!NOTE] > > **Kênh áp dụng:** chỉ TikTok. Kênh khác trả `400` `platform_not_supported` kèm danh sách đang > hỗ trợ. Tài khoản TikTok nối trước khi quyền này ra đời phải nối lại. Không phải bảng "giờ vàng đăng bài" chung chung, mà là số người theo dõi **của chính tài khoản bạn** có mặt trong từng giờ trong ngày. ```bash theme={null} curl "https://api.madiad.com/v1/analytics/audience?profile_id=$PROFILE_ID&platform=tiktok" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Trả về phân bố quốc gia, giới tính, thiết bị và `activity_by_hour`. Tuỳ chọn `start_date`, `end_date`, `benchmark_category`. > [!WARNING] > > **Hãy hiển thị `range` trong phản hồi, không phải khoảng bạn đã hỏi.** Nền tảng từ chối khoảng > kết thúc từ hôm nay trở đi và chỉ giữ 60 ngày, nên yêu cầu của bạn có thể bị cắt bớt trước khi > chạy. Hai khoảng không phải lúc nào cũng trùng nhau. ## Gợi ý hashtag và từ khoá > [!NOTE] > > **Kênh áp dụng:** chỉ TikTok. Riêng `type=keywords` cần tài khoản TikTok đã **nối lại**; > `type=hashtags` chạy trên tài khoản nối gần đây là đủ. ```bash theme={null} curl "https://api.madiad.com/v1/analytics/suggestions?profile_id=$PROFILE_ID&platform=tiktok&type=hashtags&q=g%E1%BB%ADi%20h%C3%A0ng&country_code=VN&language=vi" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` `type=hashtags` là các thẻ nên đi kèm từ khoá; `type=keywords` là những gì người ta tìm quanh nó. > [!NOTE] > > Hai nhóm trên hiện chỉ TikTok trả lời; nền tảng khác trả `400` với `error_code` > `platform_not_supported`. > > Nếu tài khoản TikTok được kết nối từ trước khi quyền này ra đời, bạn sẽ nhận `400` với > `tiktok_reconnect_required`. **Vào Kết nối và nối lại TikTok** là xong. Đây không phải lỗi ở hai > phía, mà là quyền phải được cấp đúng lúc kết nối. # Hỗ trợ tương tác Source: https://docs.madiad.com/vi/concepts/boost Credits tặng kèm gói trả phí, dùng để tăng tương tác cho bài viết và trang của chính bạn. Tính năng Beta (thử nghiệm), chưa mở cho khách hàng. > [!WARNING] > > **Tính năng Beta (thử nghiệm).** Hỗ trợ tương tác **chưa mở cho khách hàng**. Trang này mô tả cách tính năng hoạt động khi mở. Trước lúc đó, dashboard của bạn không có trang Hỗ trợ tương tác, và mọi đường dẫn `/v1/boost/*` trả `404 not_found` như một đường dẫn không tồn tại. ## Tính năng là gì *Beta (thử nghiệm), chưa mở cho khách hàng.* Hỗ trợ tương tác là **tính năng bổ sung tặng kèm theo gói trả phí** của MADIAD Hub. Mỗi kỳ gói, tài khoản có gói trả phí đang hiệu lực nhận một mức credits. Bạn dùng credits đó để đặt đơn tăng tương tác (ví dụ lượt thích) cho bài viết hoặc trang của chính bạn trên mạng xã hội. * **Không thu thêm khoản tiền nào ngoài giá gói.** Credits đi kèm gói, không quy đổi thành tiền. * Tính năng đang ở giai đoạn thử nghiệm: kết quả của một đơn có thể chậm, lệch hoặc gián đoạn. Đơn nào chạy thiếu hay không chạy thì được hoàn credits (xem [Trạng thái đơn](#trạng-thái-đơn)). * Điều khoản của tính năng nằm ở [mục 3 Điều khoản sử dụng](https://madiad.com/dieu-khoan-su-dung#interaction-support): giá trị, hạn dùng và cách hoàn credits. Trang Hỗ trợ tương tác của dashboard cũng có liên kết tới mục này. Trang này không ghi mức credits của từng gói. Con số đó hiện trong dashboard, đúng cho kỳ gói của chính bạn, nên luôn đọc ở đó. Số credits một đơn sẽ dùng hiện ngay trong form, trước khi bạn bấm đặt. ## Ai có credits *Beta (thử nghiệm), chưa mở cho khách hàng.* | Tài khoản | Credits | | - | - | | Gói trả phí đang hiệu lực | Có, theo mức của gói, làm mới mỗi kỳ gói | | Gói dùng thử, kỳ gói 0 đồng | Không | | Gói đã hết hạn, chưa gia hạn | Không (credits còn lại hết hiệu lực cùng lúc với gói) | | Tài khoản chưa có kỳ gói đã thanh toán | Không | Khi kỳ này không có credits, dashboard nói rõ lý do bằng một câu (ví dụ "Gói dùng thử và kỳ gói 0 đồng không kèm credits Hỗ trợ tương tác."), và API trả mã lý do trong trường `reason` (xem [Dùng qua API](#dùng-qua-api)). ## Kỳ gói và hạn dùng credits *Beta (thử nghiệm), chưa mở cho khách hàng.* * **Kỳ gói dài một tháng**, tính từ ngày thanh toán của bạn theo giờ Việt Nam. Gói 6 tháng và 12 tháng cũng làm mới credits **hằng tháng**, không nhận một lần cho cả gói. * **Credits không dùng hết sẽ hết hạn cùng kỳ**, không cộng dồn sang kỳ sau. * Kỳ cuối của gói kết thúc đúng lúc gói hết hạn. Dashboard ghi rõ khi bạn đang ở kỳ cuối. * Gia hạn trước hạn: kỳ gói mới bắt đầu ngay từ lần thanh toán đó, với trọn mức credits của kỳ. Phần còn lại của kỳ cũ không dùng tiếp được. * Ngày bắt đầu và ngày kết thúc của kỳ hiện tại hiện trên trang Hỗ trợ tương tác của dashboard, giờ Việt Nam. ## Đặt đơn *Beta (thử nghiệm), chưa mở cho khách hàng.* Trên dashboard, mục **Hỗ trợ tương tác** (cuối nhóm Nội dung) có danh sách những gì đặt được trong kỳ, số credits còn lại, form đặt đơn và lịch sử đơn. 1. **Dán liên kết** bài viết hoặc trang: một địa chỉ `https://`, dài tối đa 2.048 ký tự. Form tự nhận ra liên kết thuộc nền tảng nào (Facebook, Instagram…). 2. **Chọn loại tương tác** của nền tảng đó, ví dụ lượt thích. Danh sách loại đang có hiện trong dashboard. Bạn không cần chọn gì thêm: phần chạy đơn do MADIAD lo. 3. **Nhập số lượng** trong khoảng tối thiểu đến tối đa. Form hiện ngay số credits đơn sẽ dùng và số credits còn lại sau đơn. 4. **Đặt đơn.** Credits được giữ ngay khi đơn được nhận. Đơn thường được gửi đi trong vài phút, và trạng thái cập nhật trong lịch sử đơn. Với loại tương tác cần bình luận do bạn viết (khi kỳ của bạn có loại đó): mỗi dòng là một bình luận, **số dòng bằng số lượng**, mỗi dòng có chữ và dài tối đa 500 ký tự. Bình luận đi qua bộ lọc nội dung trước khi đơn được nhận. Dòng nào không qua thì đơn bị từ chối, không trừ credits, và dashboard chỉ ra đúng dòng đó để bạn sửa. **Số credits theo kỳ gói của bạn.** Số credits một loại tương tác dùng được chốt theo kỳ gói đang chạy. MADIAD giảm mức này giữa kỳ thì đơn của bạn dùng ít credits hơn ngay. MADIAD tăng thì mức mới chỉ áp từ kỳ gói sau của bạn. **Không đặt trùng.** Bấm đặt đơn mà mạng chập chờn, chưa rõ đơn đã được nhận hay chưa: xem lịch sử đơn trước. Đơn chưa có ở đó thì bấm đặt lại. Form giữ nguyên khoá của lần bấm trước, nên lần bấm lại không tạo đơn thứ hai. ## Trạng thái đơn *Beta (thử nghiệm), chưa mở cho khách hàng.* | Trạng thái (API) | Dashboard ghi | Ý nghĩa | Credits | | - | - | - | - | | `queued` | Đang chờ gửi | Đơn đã được nhận, đang chờ gửi đi | Đang giữ | | `processing` | Đang chạy | Đơn đã gửi đi và đang chạy | Đang giữ | | `completed` | Hoàn thành | Đơn chạy đủ | Đã dùng | | `partial` | Hoàn thành một phần | Đơn chạy được một phần | Phần chưa chạy được hoàn | | `cancelled` | Đã huỷ | Đơn bị huỷ, hoặc không được gửi đi | Phần chưa chạy được hoàn (đơn chưa gửi đi: hoàn đủ) | | `rejected` | Bị từ chối | Đơn bị từ chối khi gửi đi | Hoàn đủ | | `expired` | Quá hạn chờ | Đơn chờ quá lâu mà chưa gửi đi được (ví dụ trong lúc tính năng gián đoạn) | Hoàn đủ | | `unknown` | Chưa rõ kết quả | Chưa xác nhận được đơn có chạy hay không | Đang giữ, hoàn nếu xác nhận đơn không chạy | | `untracked` | Không theo dõi được | Đơn đã được tạo nhưng không theo dõi được tiến độ | Đang giữ | Với `unknown`, dashboard ghi đúng câu: "Chưa rõ kết quả, credits đang được giữ và sẽ được hoàn nếu xác nhận đơn không chạy." MADIAD chỉ hoàn khi đã có bằng chứng đơn không chạy, nên trạng thái này có thể giữ lâu hơn các trạng thái khác. **Credits hoàn về kỳ nào.** Đơn đã gửi đi (chạy thiếu, bị huỷ, bị từ chối, hay chưa rõ rồi xác nhận không chạy) hoàn vào **kỳ gói hiện tại** của bạn, kể cả khi đơn được đặt ở kỳ trước. Đơn chưa kịp gửi đi hoàn về **kỳ đã giữ chỗ** cho đơn đó. Không có nút huỷ hay hoàn đơn bằng tay: credits chỉ được hoàn theo kết quả thật của đơn. ## Khi tính năng gián đoạn, tạm ngừng hoặc ngừng *Beta (thử nghiệm), chưa mở cho khách hàng.* Đầu trang Hỗ trợ tương tác có một băng thông báo khi tính năng không ở trạng thái bình thường. Có thông báo công khai thì băng có liên kết "Xem thông báo". | Trạng thái | Dashboard ghi | Đặt đơn mới | | - | - | - | | Gián đoạn tạm thời (sự cố kỹ thuật, bảo trì) | Tính năng đang gián đoạn tạm thời, credits của bạn vẫn được giữ | Chưa được, đặt lại khi tính năng chạy lại | | Tạm ngừng | Tính năng đang tạm ngừng, chưa đặt được đơn mới | Không | | Sẽ ngừng từ kỳ sau | Tính năng sẽ ngừng từ kỳ gói sau. Credits của kỳ này vẫn dùng được tới hết kỳ | Được, tới hết kỳ | | Đã ngừng | Tính năng đã ngừng cung cấp | Không | Credits trong từng trường hợp này theo điều khoản sử dụng của tính năng. ## Dùng qua API *Beta (thử nghiệm), chưa mở cho khách hàng.* > [!NOTE] > > Các đường dẫn dưới đây chỉ trả lời khi tính năng đã mở cho tài khoản của bạn. Phần Tham chiếu API của chúng được thêm vào khi tính năng mở. Mọi câu trả lời theo cách của [Lỗi](/vi/concepts/errors). | Đường dẫn | Việc | | - | - | | `GET /v1/boost/services` | Các loại tương tác bạn đặt được trong kỳ: `id`, `name`, `platform`, `requires_comments`, `min`, `max`. Không kèm giá: số credits của một đơn nằm trong câu trả lời khi đặt đơn (`price_credits`) | | `POST /v1/boost/orders` | Đặt một đơn: `service_id` (`id` của loại tương tác), `link`, `quantity`, và `comments` (chỉ với loại cần bình luận). **Bắt buộc header `Idempotency-Key`**, một khoá cho một đơn. `202` khi đơn đang chờ gửi | | `GET /v1/boost/orders` | Đơn của bạn, mới nhất trước. `?limit=1..100`. Trang sau: `?before=&before_id=` | | `GET /v1/boost/orders/{id}` | Một đơn của bạn. Id của tài khoản khác trả `404` | Gửi lại cùng `Idempotency-Key` với cùng nội dung thì nhận lại đúng đơn cũ, không trừ thêm credits (xem [Idempotency](/vi/concepts/idempotency)). Cùng khoá mà nội dung khác thì nhận `409 idempotency_conflict`. Các đường dẫn này chỉ đọc header `Idempotency-Key` (không nhận `X-Idempotency-Key` hay `X-Request-Id`), và lần gửi lại không kèm header `Idempotent-Replayed`: câu trả lời là chính đơn đó, `202` nếu đơn còn chờ gửi, `200` nếu không. Khi tính năng đã mở cho tài khoản, `GET /v1/usage` có thêm khối `boost_credits`: | Trường | Ý nghĩa | | - | - | | `limit`, `used`, `remaining` | Mức credits của kỳ, đã dùng, còn lại. `remaining` có thể lớn hơn `limit` khi có credits hoàn vào kỳ | | `period_start`, `period_end` | Đầu và cuối kỳ gói hiện tại | | `is_last_period` | Kỳ này kết thúc cùng hạn của gói | | `reason` | Vì sao kỳ không có credits: `not_eligible`, `plan_inactive`, `no_paid_term`, `trial_or_zero`, `feature_stopped`, `period_forfeited`, `account_disabled`; `null` khi có credits | | `feature_state` | `on`, `paused` (gián đoạn), `suspended` (tạm ngừng), `stopping` (sẽ ngừng từ kỳ sau), `stopped` | | `notice_url` | Thông báo công khai của trạng thái, nếu có | | `feature_name`, `terms_url` | Tên tính năng, điều khoản sử dụng | Các câu trả lời lỗi riêng của nhóm này: | HTTP | `code` | Khi nào | | - | - | - | | 400 | `invalid_request` | Thiếu trường, trường lạ, liên kết sai, số lượng ngoài khoảng, số dòng bình luận khác số lượng | | 403 | `forbidden` | Kỳ không có credits (kèm `reason`), tính năng đã tắt cho tài khoản, hoặc tài khoản đang bị khoá | | 403 | `feature_suspended` | Tính năng đang tạm ngừng (kèm `notice_url`) | | 404 | `not_found` | Loại tương tác không có trong danh sách của kỳ, hoặc đơn không phải của bạn | | 409 | `idempotency_conflict` | `Idempotency-Key` đã dùng cho một đơn khác | | 422 | `content_rejected` | Một dòng bình luận không qua bộ lọc nội dung (kèm `line`, `rule`) | | 429 | `quota_exceeded` | Không đủ credits cho đơn (kèm `remaining`, `renews_at`) | | 503 | `feature_paused` | Tính năng gián đoạn tạm thời, credits vẫn giữ. Thử lại sau `Retry-After` giây | # Kênh Source: https://docs.madiad.com/vi/concepts/channels Toàn bộ kênh MADIAD Hub đăng được, cách kết nối từng kênh, và loại bài mỗi kênh nhận. MADIAD Hub đăng được lên **22 kênh**. Không phải kênh nào cũng là mạng xã hội: danh sách còn có blog, chat nội bộ, fediverse, một nền tảng thương mại và một hệ gửi bản tin email. Mỗi kênh nhận một số loại bài nhất định, và kết nối theo một trong hai cách. ## Hub đăng được lên những nền tảng nào Mười ba mạng xã hội quen thuộc: **TikTok, Instagram, YouTube, Facebook, LinkedIn, X, Threads, Pinterest, Reddit, Bluesky, Google Business Profile, Telegram, Discord**, cộng **Slack, Mastodon, Nostr, Lemmy, dev.to, Hashnode, WordPress, Whop** và **listmonk**. > [!WARNING] > > **Không loại bài nào đi tới được cả 22 kênh.** Con số 22 là số kênh Hub kết nối được, không phải > số kênh một bài đăng chạm tới: bài chữ, ảnh và video mỗi loại tới một tập kênh khác nhau. Bảng > ngay dưới là chỗ duy nhất nên tra. Đừng nói "đăng lên 22 kênh" cho một bài video, API sẽ từ chối > bằng `400 invalid_request` kèm tên kênh sai. ## Mỗi kênh nhận loại bài nào `platforms` được kiểm theo bảng này trước khi gửi đi bất cứ thứ gì. Đưa video cho một blog, hay bài chữ cho YouTube, sẽ nhận `400 invalid_request` có nêu tên kênh sai, chứ không đăng được một nửa rồi mới báo lỗi. | Kênh | `platform` | Chữ | Ảnh | Video | Kết nối bằng | | - | - | :-: | :-: | :-: | - | | TikTok | `tiktok` | - | ✅ | ✅ | OAuth | | Instagram | `instagram` | - | ✅ | ✅ | OAuth | | YouTube | `youtube` | - | - | ✅ | OAuth | | Facebook | `facebook` | ✅ | ✅ | ✅ | OAuth | | LinkedIn | `linkedin` | ✅ | ✅ | ✅ | OAuth | | X | `x` | ✅ | ✅ | ✅ | OAuth | | Threads | `threads` | ✅ | ✅ | ✅ | OAuth | | Pinterest | `pinterest` | - | ✅ | ✅ | OAuth | | Reddit | `reddit` | ✅ | ✅ | ✅ | OAuth | | Bluesky | `bluesky` | ✅ | ✅ | ✅ | Mật khẩu ứng dụng | | Google Business Profile | `google_business` | ✅ | ✅ | ✅ | OAuth | | Telegram | `telegram` | ✅ | ✅ | ✅ | Bot của bạn | | Discord | `discord` | ✅ | ✅ | ✅ | Webhook kênh | | Slack | `slack` | ✅ | - | - | Incoming webhook | | Mastodon | `mastodon` | ✅ | ✅ | ✅ | Access token | | Nostr | `nostr` | ✅ | - | - | Khoá riêng | | Lemmy | `lemmy` | ✅ | ✅ | - | Tài khoản Lemmy | | dev.to | `devto` | ✅ | - | - | API key | | Hashnode | `hashnode` | ✅ | - | - | Personal access token | | WordPress | `wordpress` | ✅ | ✅ | ✅ | Application password | | Whop | `whop` | ✅ | - | - | API key | | listmonk | `listmonk` | ✅ | - | - | API user | > [!NOTE] > > Dùng đúng giá trị `platform` như trong bảng: `x` (không phải `twitter`), `google_business` (có gạch dưới), `devto` (không có dấu chấm). > [!WARNING] > > **Reddit và Bluesky hiện chưa kết nối được** (Reddit kiểm ngày 22/09/2026, > Bluesky ngày 13/09/2026). Hai kênh này đang được nâng cấp, thử lại cũng không giúp được. Trên > trang kết nối, ô của chúng hiện ở dạng mờ kèm ghi chú, thay vì để khách bấm vào ngõ cụt. Gọi kết > nối cho một trong hai kênh sẽ nhận `503 platform_unavailable`. Kênh đã kết nối từ trước vẫn chạy và vẫn đăng bài bình thường, các kênh > còn lại trong bảng không bị ảnh hưởng. ## Hai cách kết nối ### Kênh dùng OAuth Phần lớn kênh được kết nối bằng cách đăng nhập trên trang kết nối: bạn gửi khách một đường dẫn, họ cấp quyền cho MADIAD Hub ngay trên màn hình của chính nền tảng đó, không có thông tin nhạy cảm nào đi qua bạn. Xem [Profile](/vi/concepts/profiles) để biết luồng tạo link kết nối. ### Kênh nhập thông tin thủ công Slack, Mastodon, Nostr, Lemmy, dev.to, Hashnode, WordPress, Whop, listmonk, Telegram và Discord không có màn hình cấp quyền OAuth. Thay vào đó khách cung cấp một thông tin xác thực (webhook URL, API key, hoặc application password) sẽ được kiểm tra với chính nền tảng rồi lưu ở dạng mã hoá. Kết nối bằng `POST /v1/connections/{platform}`. Request nào cũng cần `profile_id` và `name` (tuỳ chọn), kèm các trường riêng của từng kênh: | Kênh | Endpoint | Trường | | - | - | - | | Telegram | `/v1/connections/telegram` | `bot_token`, `chat_id` | | Discord | `/v1/connections/discord` | `webhook_url` | | Slack | `/v1/connections/slack` | `webhook_url` | | Mastodon | `/v1/connections/mastodon` | `instance_url`, `access_token` | | Nostr | `/v1/connections/nostr` | `private_key` · `relays` *(tuỳ chọn)* | | Lemmy | `/v1/connections/lemmy` | `instance_url`, `username`, `password`, `community` | | dev.to | `/v1/connections/devto` | `api_key` | | Hashnode | `/v1/connections/hashnode` | `pat`, `publication_id` | | WordPress | `/v1/connections/wordpress` | `site_url`, `username`, `application_password` | | Whop | `/v1/connections/whop` | `api_key`, `experience_id` | | listmonk | `/v1/connections/listmonk` | `instance_url`, `username`, `password`, `list_id` | ```bash theme={null} curl -X POST https://api.madiad.com/v1/connections/wordpress \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'"$PROFILE_ID"'", "site_url": "https://blog.congty.com", "username": "bientap", "application_password": "xxxx xxxx xxxx xxxx xxxx xxxx", "name": "Blog công ty" }' ``` Gọi thành công sẽ trả về trạng thái kết nối mới nhất của Profile. `instance_url` và `site_url` bắt buộc là URL `https://`. Mọi trường đều bắt buộc, trừ trường được ghi là tuỳ chọn: bỏ trống thì nền tảng dùng mặc định của nó (Nostr dùng danh sách relay mặc định). Gửi chuỗi rỗng KHÔNG giống với bỏ trống. > [!WARNING] > > **Khoá riêng Nostr không đổi và không thu hồi được.** Khác API key hay application password, ai giữ khoá này là nắm quyền với danh tính đó vĩnh viễn. Hãy dùng một danh tính Nostr lập riêng để đăng tự động, đừng dùng khoá cá nhân của bạn. > [!NOTE] > > **WordPress cần Application Password, không phải mật khẩu đăng nhập.** Tạo tại **Người dùng → Hồ sơ → Application Passwords**. Tinh thần tương tự với Lemmy và listmonk: hai kênh này có dùng mật khẩu tài khoản, nên hãy cấp một tài khoản chỉ dùng để đăng bài. ### WordPress báo sai tên đăng nhập dù bạn nhập đúng Hub gửi nguyên ba ô bạn nhập (địa chỉ website, tên đăng nhập, Application Password) để website của bạn xác nhận. Khi cả ba đều đúng mà vẫn nhận câu "Invalid WordPress username or application password", lý do thường nằm ở máy chủ của website, không phải ở thông tin bạn nhập. Tự kiểm bằng một lệnh, chạy trên máy của bạn (đừng gửi Application Password cho ai, kể cả cho MADIAD): ```bash theme={null} curl -u 'TEN_DANG_NHAP:APPLICATION_PASSWORD' https://website-cua-ban/wp-json/wp/v2/users/me ``` | Website trả lời | Nghĩa là | Việc cần làm | | - | - | - | | Thông tin tài khoản, có `"id"` | Website nhận đăng nhập này | Kết nối lại trên Hub; còn lỗi thì gửi MADIAD giờ bạn thử | | `rest_not_logged_in` | Thông tin đăng nhập không tới được WordPress: máy chủ bỏ header `Authorization` dọc đường | Nhờ đơn vị hosting của website cho header `Authorization` đi qua. Với Apache hoặc LiteSpeed, đó là dòng `RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]` trong `.htaccess` | | `incorrect_password` hoặc `invalid_username` | WordPress nhận được và từ chối | Kiểm lại tên đăng nhập (tên tài khoản, không phải tên hiển thị) và tạo Application Password mới | | Trang `403` thay vì JSON | Tường lửa hoặc lớp chống bot của hosting chặn | Nhờ hosting cho phép gọi `/wp-json/` từ máy chủ bên ngoài | Mở được `/wp-json/` bằng trình duyệt chưa đủ để kết luận: trình duyệt không gửi thông tin đăng nhập, và lớp chống bot thường cho trình duyệt qua trong khi chặn yêu cầu từ máy chủ. ## Chọn endpoint nào Ba endpoint đăng bài ứng với ba cột ở bảng trên: * [`POST /v1/posts/text`](/vi/concepts/posts): mọi kênh có ✅ ở cột **Chữ** * [`POST /v1/posts/photos`](/vi/concepts/posts): mọi kênh có ✅ ở cột **Ảnh** * [`POST /v1/posts/video`](/vi/concepts/posts): mọi kênh có ✅ ở cột **Video** Một lần gọi có thể liệt kê bất kỳ nhóm kênh nào cùng nhận loại bài đó. Muốn vừa lên nền tảng video vừa lên blog thì gửi hai lần gọi. Đó là hai loại bài khác nhau, và mỗi lần tính là một lượt đăng. ## Phạm vi của Analytics [Analytics](/vi/concepts/analytics) có số liệu cho mười kênh: TikTok, Instagram, YouTube, Facebook, LinkedIn, X, Threads, Pinterest, Reddit và Bluesky. Google Business Profile và toàn bộ kênh nhập thủ công không trả về số người theo dõi, phạm vi tiếp cận hay tương tác. Một webhook Discord hay một danh sách email thì không có lượng người theo dõi để đọc. Hãy bỏ chúng khỏi `platforms` khi gọi Analytics. # Tuân thủ chính sách nền tảng Source: https://docs.madiad.com/vi/concepts/compliance Cách Hub đưa bài đăng tới từng nền tảng, và cam kết đó dừng lại ở đâu. MADIAD Hub xuất bản qua API chính thức của từng nền tảng, theo đúng luồng OAuth mà nền tảng phê duyệt. Không có bước nào trong đường đi của bài đăng giả lập trình duyệt, mượn phiên đăng nhập hay đi vòng qua một tài khoản trung gian. ## Bài đăng tới nền tảng bằng cách nào * **Chỉ dùng API chính thức.** Mọi nền tảng Hub hỗ trợ đều được gọi qua API xuất bản có tài liệu công khai, dưới một ứng dụng đăng ký chính danh với nền tảng. * **Bạn tự cấp quyền cho tài khoản của mình.** Khi kết nối, nền tảng mở màn hình OAuth của chính họ để bạn xác nhận. Hub không hỏi, không lưu và không dùng lại mật khẩu nền tảng của bạn. * **Nội dung gửi đi khớp yêu cầu từng nền tảng.** Tỉ lệ khung hình, độ dài caption, dung lượng tệp và các tuỳ chọn riêng được điều chỉnh trước khi rời khỏi Hub, nên bài không bị từ chối vì một định dạng mà nền tảng vốn không nhận. * **Nền tảng đổi yêu cầu là việc của MADIAD.** Khi một nền tảng đổi API hoặc đổi quy định về media, MADIAD cập nhật phần tích hợp. Hệ thống của bạn vẫn gọi đúng endpoint cũ. ## Phần thuộc trách nhiệm của bạn Nội dung bạn đăng và tần suất bạn đăng. Hub gửi đúng thứ bạn đưa, tới đúng tài khoản bạn đã kết nối, vào đúng thời điểm bạn chọn. > [!WARNING] > > Hub bảo đảm cách đăng, không bảo đảm nội dung. Nội dung vi phạm chính sách nền tảng, spam hoặc hành vi lạm dụng vẫn có thể khiến bài bị gỡ, tài khoản bị hạn chế hiển thị hoặc bị khoá. Đó là quyết định của nền tảng và MADIAD không thể đảo ngược. Hub cũng không hứa hẹn gì về lượt tiếp cận hay tương tác. Đăng qua API chính thức giúp bạn tránh phần bị trừ điểm mà nền tảng dành cho tự động hoá trái phép, chứ không tác động tới cách nền tảng xếp hạng nội dung. ## Khi nền tảng hạn chế một tài khoản Việc bị hạn chế thường biểu hiện giống hệt một kết nối hỏng: bài đăng của Profile đó bắt đầu thất bại, và Hub bắn webhook `connection.reauth_required`. Bước đầu tiên là kết nối lại tài khoản. Nếu kết nối lại thành công mà bài vẫn thất bại, phần hạn chế nằm ở phía nền tảng và phải xử lý trong công cụ quản lý tài khoản của chính nền tảng đó. Xem [Lỗi](/vi/concepts/errors) để biết cấu trúc phản hồi và [Webhook](/vi/concepts/webhooks) để biết nội dung sự kiện. # Tương tác Source: https://docs.madiad.com/vi/concepts/engagement Đọc bình luận, trả lời công khai hoặc qua DM, và chạy AutoDM monitor. Ngoài việc đăng bài, MADIAD Hub cho phép bạn tương tác với khán giả mà một Profile đã xây dựng, liệt kê media gần đây, đọc và trả lời bình luận, gửi tin nhắn trực tiếp, và chạy AutoDM monitor tự động phản hồi người mới bình luận. Mọi lệnh gọi đều giới hạn theo một Profile của bạn qua `profile_id`. > [!TIP] > > Để tự động để lại bình luận **của chính bạn** ngay khi bài đăng lên (gắn link hay hashtag), hãy dùng trường [`first_comment`](/vi/concepts/posts) của bài đăng thay vào đó. Đó là một phần của việc đăng bài, không phải phần tương tác. > [!NOTE] > > **Trả lời và kiểm duyệt bình luận** có từ gói **Starter** trở lên. **Nhắn tin riêng và AutoDM monitor** có từ gói **Business** trở lên, và mỗi gói có **hạn mức DM theo ngày**, vượt sẽ trả về `429`. Yêu cầu ở gói không bao gồm tính năng trả về `403`. | Thao tác | Gói cần có | | - | - | | Đọc bình luận, liệt kê media | Mọi gói đang hoạt động | | Trả lời công khai dưới bình luận | Starter trở lên | | Viết bình luận mới, xoá bình luận | Starter trở lên | | Ẩn / thích / ghim bình luận | Starter trở lên | | Nhắn riêng cho người bình luận | Business trở lên | | Gửi tin nhắn trực tiếp, xem danh sách hội thoại tin nhắn, chạy AutoDM monitor | Business trở lên | | [Trả lời đánh giá Google](/vi/concepts/reviews) | Business trở lên | Gói `Chưa kích hoạt` không có thao tác ghi nào. Muốn biết gói hiện tại của bạn có gì, đọc [`GET /v1/usage`](/vi/concepts/usage): `comments.included` cho phần bình luận, `direct_messages.included` cho phần nhắn tin và đánh giá. > [!NOTE] > > Tin nhắn trực tiếp và AutoDM cũng chịu giới hạn nhắn tin riêng của từng nền tảng (ví dụ cửa sổ thời gian của Instagram để nhắn cho người chưa nhắn cho bạn gần đây). Khi một nền tảng từ chối gửi, lý do sẽ hiện trong phản hồi. ## Liệt kê media gần đây Lấy các bài đăng, reel và video gần đây trên các tài khoản đã kết nối của một Profile, hữu ích để tìm `media_id` cần đọc bình luận. ```bash theme={null} curl "https://api.madiad.com/v1/engagement/media?profile_id=$PROFILE_ID&platform=instagram" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` > [!WARNING] > > **Danh sách này được phân giải theo TÀI KHOẢN đã kết nối, không theo Profile.** Nếu tài khoản > Facebook (hoặc LinkedIn) bạn kết nối quản nhiều Page, endpoint trả về Page **mặc định** của tài > khoản đó, không phải Page của Profile bạn hỏi. Đo ngày 04/09/2026. Instagram và Threads lọc đúng. > > Bạn chỉ thấy được những Page mà tài khoản của **chính bạn** quản, không bao giờ thấy dữ liệu > của khách hàng khác. Nhưng nếu tài khoản của bạn quản nhiều Page, hãy kiểm tra id chủ sở hữu của > từng mục trước khi dùng. Phản hồi có kèm trường `scope_warning` nhắc lại điều này. > > Cần số liệu bài đăng đúng theo Profile thì dùng [`GET /v1/analytics/posts`](/vi/concepts/analytics). ## Bình luận > [!NOTE] > > **Kênh áp dụng khi đọc bình luận:** Instagram · Facebook · YouTube · LinkedIn · TikTok · X. > Trên X, danh sách là các trả lời có nhắc tới tài khoản. Threads, Pinterest, Reddit, Bluesky, Google Business và > các kênh credential chưa có. Đọc bình luận của một bài. Truyền `platform` và xác định bài bằng `post_id` (media id từ danh sách ở trên) hoặc `post_url`: ```bash theme={null} curl "https://api.madiad.com/v1/engagement/comments?profile_id=$PROFILE_ID&platform=instagram&post_id=$MEDIA_ID" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Trả lời công khai dưới một bình luận, hoặc gửi DM riêng cho người bình luận: ```bash theme={null} # Trả lời công khai curl -X POST https://api.madiad.com/v1/engagement/comments/public-reply \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "comment_id": "'$COMMENT_ID'", "message": "Cảm ơn bạn đã xem!" }' # Trả lời bằng DM riêng curl -X POST https://api.madiad.com/v1/engagement/comments/reply \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "comment_id": "'$COMMENT_ID'", "message": "Đã gửi link cho bạn qua DM 👀" }' ``` Cả hai đều cần `profile_id`, `comment_id`, và `message`. ## Tin nhắn trực tiếp > [!NOTE] > > **Kênh áp dụng:** chỉ Instagram. DM và AutoDM chưa mở trên kênh nào khác. Gửi DM cho một người dùng theo id riêng của nền tảng, và liệt kê các cuộc hội thoại hiện có. Cả hai đều cần `platform`: ```bash theme={null} curl -X POST https://api.madiad.com/v1/engagement/dms/send \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "platform": "instagram", "recipient_id": "17841400000000000", "message": "Chào bạn! Đây là mã giảm giá." }' ``` ```bash theme={null} curl "https://api.madiad.com/v1/engagement/dms/conversations?profile_id=$PROFILE_ID&platform=instagram" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ## AutoDM monitor Một AutoDM monitor theo dõi một bài đăng và tự động DM bất kỳ ai bình luận, có thể chỉ khi bình luận chứa một từ khóa kích hoạt. > [!NOTE] > > AutoDM monitor **chỉ hỗ trợ Instagram**, dùng Private Replies API chính thức của Meta. Monitor chạy nền và tự động khử trùng lặp người bình luận, nên mỗi người chỉ được nhắn một lần. ```bash theme={null} curl -X POST https://api.madiad.com/v1/autodm/monitors \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "post_url": "https://www.instagram.com/p/abc123/", "reply_message": "Đây là link bạn cần: https://madiad.com/offer", "trigger_keywords": ["link", "info"] }' ``` Phản hồi trả về một `monitor_id`. Quản lý monitor bằng id đó: | Hành động | Yêu cầu | | - | - | | Trạng thái | `GET /v1/autodm/monitors/{monitor_id}` | | Log hoạt động | `GET /v1/autodm/monitors/{monitor_id}/logs` | | Tạm dừng | `POST /v1/autodm/monitors/{monitor_id}/pause` | | Tiếp tục | `POST /v1/autodm/monitors/{monitor_id}/resume` | | Dừng | `POST /v1/autodm/monitors/{monitor_id}/stop` | | Xóa | `DELETE /v1/autodm/monitors/{monitor_id}` | | Field | Bắt buộc | Mô tả | | - | - | - | | `profile_id` | có | Profile có tài khoản kết nối chạy monitor. | | `post_url` | có | Bài đăng cần theo dõi. | | `reply_message` | có | DM gửi cho mỗi người mới bình luận. | | `trigger_keywords` | không | Chỉ DM người bình luận có chứa một trong các từ khóa này. | | `monitoring_interval` | không | Số phút giữa các lần kiểm tra. | ## Kiểm duyệt bình luận Ngoài trả lời, bạn có thể **viết**, **xoá** và **ẩn / thích / ghim** bình luận trên bài của chính mình. > [!NOTE] > > Cả ba đều là thao tác ghi bình luận, có từ gói **Starter** trở lên. Đọc bình luận thì mọi gói đang hoạt động đều dùng được. ### Viết bình luận | Kênh | Bình luận mới | Trả lời | Ghi chú | | - | :-: | :-: | - | | Facebook | ✅ | ✅ | | | YouTube | ✅ | ✅ | `post_id` là id video | | LinkedIn | ✅ | ✅ | `post_id` là post URN | | TikTok | ✅ | ✅ | `post_id` (id video) **luôn** bắt buộc | | Instagram | ❌ | ✅ | Chỉ trả lời, phải có `comment_id` | | X | ✅ | ✅ | Trên X, mỗi trả lời là một bài mới: link bị gỡ nếu tài khoản không còn hạn mức link X, và trả lời dùng chung một trần mỗi ngày cho mỗi tài khoản X (`429` `rate_limited` kèm `retry_after_s` tới 00:00 UTC) | Truyền `comment_id` để trả lời công khai dưới bình luận đó. Truyền `post_id` hoặc `post_url` để viết bình luận mới ở cấp cao nhất. ```bash theme={null} curl -X POST https://api.madiad.com/v1/engagement/comments/create \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "platform": "facebook", "post_id": "'$POST_ID'", "message": "Cảm ơn cả nhà đã ghé." }' ``` > [!NOTE] > > **Instagram chỉ cho trả lời**, không cho mở một luồng bình luận mới, nên phải có `comment_id`. > **TikTok luôn cần `post_id`** (id video), kể cả khi đang trả lời một bình luận. Cả hai luật này > được kiểm tại chỗ và trả về `400` kèm lý do, thay vì để nền tảng từ chối. ### Xoá bình luận | Kênh | Xoá | Ghi chú | | - | :-: | - | | Facebook · YouTube · Instagram | ✅ | Chỉ cần `comment_id` | | LinkedIn | ✅ | Cần thêm `post_id` (post URN) | | TikTok | ✅ | Chỉ xoá được bình luận do chính tài khoản viết | | X | ✅ | Chỉ cần `comment_id` | ```bash theme={null} curl -X POST https://api.madiad.com/v1/engagement/comments/delete \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "platform": "facebook", "comment_id": "'$COMMENT_ID'" }' ``` Riêng LinkedIn cần thêm `post_id` (post URN). Xoá là không hoàn tác được trên nền tảng. ### Ẩn, thích, ghim | Kênh | Trạng thái | | - | - | | TikTok | ✅ dùng được | | Mọi kênh khác | ❌ trả `400` `platform_not_supported` | Mỗi hành động mang sẵn hành động ngược của nó. Bạn gửi động từ, không gửi giá trị đúng/sai, nên gửi lại một yêu cầu không bao giờ lật ngược trạng thái. | `action` | Cần `post_id`? | | - | - | | `hide` · `unhide` · `pin` · `unpin` | Có | | `like` · `unlike` | Không | ```bash theme={null} curl -X POST https://api.madiad.com/v1/engagement/comments/action \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "platform": "tiktok", "comment_id": "'$COMMENT_ID'", "post_id": "'$VIDEO_ID'", "action": "pin" }' ``` > [!NOTE] > > Hiện chỉ TikTok trả lời nhóm này. Nền tảng khác trả `400` với `error_code` > `platform_not_supported` kèm danh sách nền tảng đang hỗ trợ. Hãy rẽ nhánh theo mã lỗi, đừng > gắn cứng danh sách trong mã của bạn. # Lỗi Source: https://docs.madiad.com/vi/concepts/errors Mọi lỗi MADIAD Hub trả về đều dùng chung một cấu trúc, đây là bảng tham chiếu đầy đủ. > [!NOTE] > > Trang này dành cho người viết tích hợp: nó tra `code` mà API trả về. Nếu bạn chỉ muốn biết **vì > sao một bài không đăng được** và phải làm gì, hãy đọc > [Vì sao bài không đăng được](/vi/concepts/publish-failures), nơi xếp các câu báo lỗi thật theo > nguyên nhân. Mọi request thất bại đều trả về cùng một cấu trúc JSON, bất kể endpoint nào hay lỗi gì: ```json theme={null} { "error": { "code": "invalid_request", "message": "`platforms` is required (array or comma-separated)." } } ``` `code` là một chuỗi ổn định, có thể đọc bằng máy, bạn có thể dùng nó để rẽ nhánh xử lý. `message` là mô tả dễ đọc cho người, hữu ích khi ghi log, nhưng đừng parse nó vì nội dung có thể thay đổi theo thời gian. Khi nền tảng đích từ chối một bài đăng, `message` mở đầu bằng **nguyên văn của nền tảng đó**, thường là tiếng Anh. Nếu MADIAD Hub có bản dịch tiếng Việt cho câu đó, bản dịch nằm ngay sau `· Tạm dịch:`. Đây là bản dịch đúng nghĩa: nó giữ đủ các mệnh đề và các con số của nền tảng, không thêm lời khuyên nào của chúng tôi. Câu nào chưa có bản dịch thì giữ nguyên tiếng Anh chứ không đoán bừa. Phần duy nhất bị sửa còn lại là những gì thuộc về chúng tôi: địa chỉ hỗ trợ và link tài liệu đều trỏ về MADIAD, còn định danh Profile nội bộ thì được gỡ bỏ. Trường `error` của từng nền tảng trong `results`, ở response của lệnh đăng lẫn khi poll trạng thái, cũng theo đúng dạng này. ## Bảng mã lỗi | `code` | HTTP status | Xảy ra khi nào | Cách xử lý | | - | - | - | - | | `unauthorized` | 401 | API key thiếu, sai định dạng hoặc không hợp lệ (hoặc session dashboard đã hết hạn trên các route dùng browser session). | Kiểm tra header `Authorization: Bearer`. Cấp lại hoặc xoay vòng key từ dashboard. Không nên retry khi chưa sửa thông tin xác thực. | | `forbidden` | 403 | Tài khoản hợp lệ nhưng không được phép thực hiện hành động này, ví dụ tài khoản đang ở trạng thái `expired` hoặc `banned`, hoặc một tính năng không nằm trong gói hiện tại. Với [bình luận](/vi/concepts/engagement), mọi gói trả phí đều ghi được, nên `403` ở đó gần như luôn là tài khoản chưa kích hoạt hoặc đã hết hạn, không phải chuyện gói. Với nhắn tin, AutoDM và [trả lời đánh giá Google](/vi/concepts/reviews) thì đó đúng là chuyện gói. | Không retry nguyên trạng. Đọc [`GET /v1/usage`](/vi/concepts/usage) để biết gói hiện tại có gì, rồi kiểm tra trạng thái tài khoản hoặc nâng cấp gói. | | `not_found` | 404 | Route không tồn tại, hoặc một resource được tham chiếu (Profile...) không thuộc về tài khoản của bạn. Khi gỡ bài, mã này còn có nghĩa là nền tảng không biết id bài đó. | Kiểm tra lại URL và các ID. Không retry được nếu không đổi request. | | `invalid_request` | 400 | Request không hợp lệ, có thể do phía Hub hoặc do chính nền tảng đích từ chối: thiếu field bắt buộc, caption hoặc title vượt giới hạn ký tự của nền tảng, liệt kê nền tảng không hỗ trợ, URL không phải link `https://` hợp lệ, nền tảng từ chối nội dung. | Sửa request theo `message`. Không retry được nếu không đổi payload. | | `quota_exceeded` | 429 | Một hạn mức đã dùng hết: hạn mức lượt [xử lý video](/vi/concepts/ffmpeg) hằng tháng hoặc số DM theo ngày. Đăng bài không còn hạn mức tháng trên gói trả phí, nên với một lệnh đăng thì lỗi này chỉ có nghĩa là tài khoản **chưa có gói đang hoạt động**. Tính năng chưa bao giờ được bật thì trả `403 forbidden`, và cái đó đợi bao lâu cũng không hết. | Không retry được cho tới khi hạn mức reset hoặc gói được kích hoạt/nâng cấp. Kiểm tra [Mức sử dụng](/vi/concepts/usage) và hiển thị thông báo cho người dùng thay vì lặp lại retry. | | `no_active_plan` | 403 | Bạn đang cố kết nối một tài khoản mạng xã hội (tạo connect link) nhưng tài khoản chưa có gói đang hoạt động (hạn mức Profile bằng 0). | Kích hoạt gói dịch vụ từ trang thanh toán của dashboard trước. Không retry nguyên trạng. | | `profile_limit_reached` | 403 | Tạo thêm Profile mới sẽ vượt giới hạn Profile của gói hiện tại. | Nâng cấp gói hoặc xoá bớt một Profile không dùng đến, rồi thử lại. | | `rate_limited` | 429 | Bạn đã vượt giới hạn số request mỗi phút (tách biệt với các hạn mức sử dụng, xem [Rate limit](/vi/concepts/rate-limits)), **hoặc** nền tảng đích chặn tốc độ lời gọi. | Giảm tốc độ gửi và tuân theo header `Retry-After` (tính bằng giây) trước khi thử lại. | | `idempotency_conflict` | 409 | `Idempotency-Key` bạn gửi đã được dùng cho một request với tham số **khác**. | Dùng key mới cho request thực sự mới, hoặc kiểm tra lại payload có khớp với lần gửi đầu tiên không. Không nên retry an toàn với cùng key nhưng khác body. | | `idempotency_in_progress` | 409 | Một request với `Idempotency-Key` này vẫn đang được xử lý (một lần retry đồng thời hoặc chồng lấn xảy ra khi lần thử đầu chưa xong). | Đợi một chút rồi thử lại cùng request với cùng key, nó sẽ nhận kết quả đã lưu ngay khi lần thử đầu hoàn tất. | | `page_pin_conflict` | 409 | Request có nêu một trang hoặc địa điểm (`facebook_page_id`, `target_linkedin_page_id` hoặc `gbp_location_id`) nhưng Profile đang ghim một trang **khác**. Trang được ghim đè lên trang gửi kèm mỗi lần đăng, nên nếu vẫn đăng thì bài sẽ lên trang đã ghim mà vẫn báo thành công. | Bỏ trường đó đi để đăng vào trang đã ghim, hoặc gỡ ghim trước, xem [Ghim Trang vào Profile](/vi/concepts/profiles#ghim-trang-vao-profile). Gửi lại y nguyên thì không thể thành công. | | `page_pin_unverified` | 503 | Request có nêu một trang, nhưng không kiểm tra được Profile đang ghim trang nào. Ghim sẽ đè lên trường đó, nên nếu cứ đăng thì có nguy cơ bài lên nhầm trang. | Thử lại sau một lát, đây là lỗi tạm thời. Hoặc bỏ trường đó đi để đăng vào trang mà profile đang ghim. | | `platform_not_startable` | 400 | Nền tảng có hỗ trợ đăng bài nhưng không khởi tạo được từ endpoint kết nối theo từng nền tảng (Bluesky dùng app password chứ không phải OAuth). | Nền tảng này hiện chưa hỗ trợ tự kết nối. [Liên hệ hỗ trợ](mailto:info@madiad.com) nếu bạn cần dùng. | | `link_not_found` | 404 | Token của liên kết kết nối không tồn tại hoặc đã hết hạn (liên kết sống 48 giờ). Một mã dùng chung cho cả hai trường hợp, để không ai dò được token nào có thật. | Tạo liên kết mới bằng `POST /v1/connections/start`. | | `platform_not_connected` | 409 | Profile chưa kết nối tài khoản cho một nền tảng có trong request, nên bài không thể đăng như đã gửi (hoặc, khi gỡ bài, không gỡ được qua Profile này). | Kết nối tài khoản trong mục **Kết nối** của dashboard, hoặc bỏ nền tảng đó khỏi `platforms`. Gửi lại y nguyên request thì không thể thành công. Nếu cứ sau mỗi bài đăng được là lại gặp lỗi này, nền tảng đang hạn chế tài khoản và kết nối lại sẽ không gỡ được, hãy đọc `message`. | | `location_required` | 400 | Tài khoản Google của Profile có nhiều địa điểm Google Business, request không chỉ định địa điểm nào, và lúc kết nối Google Business chưa chọn địa điểm mặc định. | Truyền `location_id` (`message` liệt kê các id, còn `GET /v1/gbp/locations` trả kèm tên), hoặc chọn địa điểm mặc định khi kết nối Google Business. | | `upstream_error` | 502 | Đường xuất bản của MADIAD Hub thực sự gặp sự cố: lỗi mạng, hoặc lỗi 5xx khi đang gửi bài. Vấn đề về nội dung và quyền **không** nằm ở đây, chúng trả `400` hoặc `409`. | Lỗi tạm thời. An toàn để retry toàn bộ request **chỉ khi** bạn đã gửi `Idempotency-Key` để lần retry không tạo bài đăng trùng. | | `upstream_timeout` | 502 | Việc đăng bài vượt quá thời gian MADIAD Hub chờ, nên **chưa xác định được kết quả**: bài có thể đã lên. Lượt đăng không được hoàn, và `Idempotency-Key` bạn gửi đã được chốt vào câu trả lời này, nên gửi lại cùng key sẽ nhận đúng kết quả này chứ không tạo bài thứ hai. | **Đừng** retry ngay. Câu trả lời có kèm `request_id`: tra [`GET /v1/posts/status`](/vi/concepts/posts#kiểm-tra-trạng-thái-sau) bằng mã đó (`unknown` → `processing` → kết quả), hoặc xem lịch sử đăng, nơi kết quả tự cập nhật. Nếu vẫn quyết định gửi lại thì dùng một `Idempotency-Key` MỚI. Xem [Khi kết quả chưa xác định](/vi/concepts/idempotency#khi-kết-quả-chưa-xác-định). | | `internal_error` | 500 (hiếm khi 502) | Lỗi chưa được xử lý bên trong MADIAD Hub (lỗi database, exception ngoài dự kiến). Hiếm gặp và không phải do bạn gây ra. Một vài thao tác có thể trả về 502 thay vì 500. | Coi cả hai là retryable: thử lại với backoff. Nếu vẫn lặp lại, [liên hệ hỗ trợ](mailto:info@madiad.com) kèm chi tiết request. | > [!TIP] > > Xem [Idempotency](/vi/concepts/idempotency) để hiểu `Idempotency-Key` ngăn tạo bài đăng trùng lặp khi retry như thế nào, và [Rate limit](/vi/concepts/rate-limits) để hiểu giới hạn theo phút đứng sau `rate_limited`. ## Khi nào nên retry? Hãy phân nhánh theo **mã trạng thái**, đừng bao giờ dựa vào câu chữ trong `message`: | Trạng thái | Ý nghĩa | Retry y nguyên request? | | - | - | - | | `4xx` | Vấn đề nằm ở request, ở nội dung, hoặc ở trạng thái tài khoản. | **Không.** Phải sửa gì đó trước, gửi lại y nguyên sẽ hỏng y như cũ. | | `429` | Gửi quá nhanh, hoặc đã hết hạn mức. | Có, sau khoảng thời gian ghi trong `Retry-After`. | | `5xx` | Việc đăng bài thất bại ở phía Hub. | Có, kèm backoff, và nhớ gửi `Idempotency-Key` để lần retry không tạo bài trùng. Ngoại lệ duy nhất là `upstream_timeout`: kết quả chưa xác định, phải kiểm tra trước khi gửi lại. | > [!WARNING] > > Tới trước ngày 07/08/2026, mọi lỗi phát sinh khi đăng bài đều được báo là `502`, kể cả những lỗi vĩnh viễn như caption vượt giới hạn hoặc Profile chưa kết nối tài khoản. Vì vậy các automation retry theo `5xx` đã retry cả những request không bao giờ thành công được. Nếu tích hợp của bạn đang lách chỗ này bằng cách đọc `message`, giờ có thể bỏ đoạn đó và phân nhánh theo mã trạng thái. ## Lỗi không có envelope chuẩn Một request multipart quá lớn có thể bị chặn ở tầng biên mạng **trước khi** tới được MADIAD Hub, phản hồi khi đó là `413 Request Entity Too Large` dạng thuần (không có envelope JSON). Hãy chuyển sang gửi ảnh bằng [`photo_urls[]`](/vi/concepts/posts#đăng-ảnh-bằng-url): MADIAD Hub sẽ tự tải ảnh về phía máy chủ, nên request body luôn nhỏ gọn bất kể ảnh lớn đến đâu. Nén file ảnh lại cũng là một cách nếu chỉ vượt nhẹ. # FFmpeg Source: https://docs.madiad.com/vi/concepts/ffmpeg Chạy các job xử lý video và âm thanh phía server thông qua MADIAD Hub API. FFmpeg API cho phép bạn chuyển mã, cắt xén, chuyển đổi và xử lý file video, âm thanh hoàn toàn trên server. Bạn cung cấp một URL nguồn và một lệnh FFmpeg; MADIAD Hub tải file về, chạy job và cung cấp kết quả để tải về. Không có chức năng tải lên file nhị phân, các file đầu vào phải truy cập được qua HTTPS. API trả về `job_id` ngay lập tức; quá trình xử lý là bất đồng bộ, bạn cần kiểm tra trạng thái định kỳ rồi tải về kết quả. ## FFmpeg không có webhook, phải tự hỏi trạng thái > [!WARNING] > > **Không có webhook cho FFmpeg.** Kiểm tra trạng thái định kỳ (polling) là cách DUY NHẤT để biết một job đã xong. [Webhooks](/vi/concepts/webhooks) chỉ phát sự kiện cho việc đăng bài và cho thay đổi kết nối tài khoản, không có sự kiện nào cho job FFmpeg. Đừng thiết kế hệ thống của bạn dựa trên một callback FFmpeg. > [!NOTE] > > **Xử lý video được bật theo yêu cầu, không đi kèm gói nào.** Mặc định không gói nào có sẵn tính > năng này; tài khoản chưa được bật sẽ nhận `403 forbidden` ở mọi endpoint FFmpeg. Gửi thư tới > [info@madiad.com](mailto:info@madiad.com) kèm mô tả việc bạn định xử lý, chúng tôi sẽ bật cho > tài khoản của bạn. ## Quy trình hoạt động của một job **1. Gửi** POST đến `/v1/ffmpeg/jobs` với một hoặc nhiều URL nguồn và một lệnh FFmpeg. API xác thực lệnh, đặt trước quota và đưa job vào hàng đợi. Bạn nhận được `job_id`. **2. Kiểm tra** GET `/v1/ffmpeg/jobs/:id` cho đến khi `status` là `finished`. **3. Tải về** GET `/v1/ffmpeg/jobs/:id/result` để nhận file đã xử lý dưới dạng stream. ## Gửi một job ```bash theme={null} POST /v1/ffmpeg/jobs Authorization: Bearer $MADIAD_API_KEY Content-Type: application/json ``` ### Body của request | Trường | Kiểu | Bắt buộc | Mô tả | | - | - | - | - | | `file_url` | string | Một trong `file_url` / `file_urls` | Một URL HTTPS nguồn duy nhất | | `file_urls` | string\[] | Một trong `file_url` / `file_urls` | Nhiều URL HTTPS nguồn (ví dụ để trộn nhiều track âm thanh) | | `full_command` | string | Có | Chuỗi lệnh FFmpeg đầy đủ (xem [Quy tắc lệnh](#quy-tac-lenh)) | | `output_extension` | string | Có | Container/định dạng của đầu ra (xem [Phần mở rộng được phép](#phan-mo-rong-dau-ra-duoc-phep)) | ### Response ```json theme={null} { "job_id": "ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2", "status": "queued" } ``` ## Kiểm tra trạng thái ```bash theme={null} GET /v1/ffmpeg/jobs/:id Authorization: Bearer $MADIAD_API_KEY ``` ### Response ```json theme={null} { "job_id": "ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2", "status": "processing", "duration_sec": null } ``` `duration_sec` là `null` cho đến khi job hoàn thành; giá trị thực tế của thời lượng đầu ra sẽ được điền vào sau khi hoàn tất. | Giá trị `status` | Ý nghĩa | | - | - | | `queued` | Job đang chờ trong hàng đợi | | `processing` | Job đang chạy | | `finished` | Đầu ra sẵn sàng để tải về | | `failed` | Job thất bại, hãy thử lại với lệnh đã sửa | ## Tải về kết quả Khi `status` là `finished`, hãy stream file đầu ra: ```bash theme={null} GET /v1/ffmpeg/jobs/:id/result Authorization: Bearer $MADIAD_API_KEY ``` Response là một binary stream với `Content-Disposition: attachment`. Bạn có thể lưu ra file hoặc giữ trong bộ nhớ. ## Quy tắc lệnh Trường `full_command` phải đáp ứng tất cả các quy tắc sau. Request vi phạm sẽ trả về `400 invalid_request` trước khi tiêu tốn bất kỳ quota nào. | Quy tắc | Chi tiết | | - | - | | Bắt đầu bằng `ffmpeg` | Lệnh phải bắt đầu bằng từ `ffmpeg` | | Chứa placeholder `{input}` | Dùng `{input}` cho 1 nguồn, hoặc `{input0}`, `{input1}`, ... cho nhiều nguồn | | Chứa placeholder `{output}` | MADIAD Hub thay thế đường dẫn đầu ra khi chạy | | Độ dài tối đa | 4.000 ký tự | | Không có ký tự đặc biệt của shell | Các ký tự `;`, `\|`, `&`, `$`, `` ` ``, `<`, `>` bị chặn | | Không có token nguy hiểm | `$(`, `rm`, `rmdir`, `mkfs`, `dd` bị chặn | Ký tự xuống dòng trong lệnh được tự động chuyển thành khoảng trắng, nên bạn có thể dán lệnh nhiều dòng trực tiếp. ## Phần mở rộng đầu ra được phép `output_extension` phải là một trong các giá trị sau (không có dấu chấm ở đầu): | Nhóm | Giá trị | | - | - | | Video | `mp4`, `mov`, `webm`, `mkv`, `gif` | | Âm thanh | `mp3`, `wav`, `m4a`, `aac`, `ogg` | | Hình ảnh | `jpg`, `png` | Bất kỳ giá trị nào khác sẽ trả về `400 invalid_request`. ## Điều kiện sử dụng và hạn mức Xử lý video được **cấp theo từng tài khoản, không gộp vào gói**. Mọi gói đều có hạn mức bằng không; chúng tôi đặt hạn mức cho bạn khi bạn yêu cầu. Trước đó, `POST /v1/ffmpeg/jobs` trả về `403 forbidden`. Đây là câu trả lời về quyền sử dụng, không phải về hạn mức đã dùng hết, nên thử lại sẽ không thay đổi được gì. Sau khi được bật, hạn mức tính bằng **lượt**: gửi một job tốn một lượt, bất kể video dài bao nhiêu. Hạn mức được đặt trước tại thời điểm gửi và hoàn lại tự động nếu job không vào được hàng đợi. Hạn mức tính **cho cả tài khoản**: không nhân theo số Profile, mọi thương hiệu dùng chung một hạn mức. Hạn mức đặt lại vào đúng ngày trong tháng mà gói được kích hoạt (giờ Việt Nam, UTC+7), không phải ngày mùng 1. [Mức sử dụng](/vi/concepts/usage) cho biết con số chính xác của bạn, tính năng đã được bật hay chưa, và ngày bắt đầu / gia hạn của chu kỳ hiện tại. Dùng hết hạn mức đã được cấp thì API trả về `429 quota_exceeded`. ## Xác thực và giới hạn tốc độ Tất cả endpoint FFmpeg đều dùng chung cơ chế xác thực Bearer API key với phần còn lại của MADIAD Hub, xem [Xác thực](/vi/authentication). Giới hạn tốc độ là **120 request/phút** mỗi tài khoản trên tất cả endpoint FFmpeg. Vượt quá giới hạn này sẽ nhận `429` kèm header `Retry-After` cho biết thời gian cần chờ. ## Quyền sở hữu job Job chỉ thuộc về tài khoản đã tạo ra nó. Kiểm tra trạng thái hoặc tải về job của tài khoản khác sẽ trả về `404 not_found`, không có quyền truy cập chéo tài khoản. ## Lỗi | HTTP status | Code | Khi nào | | - | - | - | | `400` | `invalid_request` | Thiếu hoặc URL không phải HTTPS, lệnh không hợp lệ, `output_extension` không được hỗ trợ | | `404` | `not_found` | Job ID không tồn tại, hoặc job thuộc tài khoản khác | | `403` | `forbidden` | Tài khoản chưa được bật xử lý video, liên hệ để mở | | `429` | `quota_exceeded` | Đã dùng hết hạn mức lượt xử lý của tháng | | `429` | `rate_limited` | Hơn 120 request/phút, hãy dừng lại và thử lại sau `Retry-After` giây | | `502` | `upstream_error` | Kết quả chưa có hoặc quá trình xử lý thất bại | ## Ví dụ thực tế ### 1. Gửi: chuyển đổi MP4 sang WebM tối ưu cho web ```bash cURL theme={null} curl -X POST https://api.madiad.com/v1/ffmpeg/jobs \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_url": "https://cdn.example.com/raw/interview.mp4", "full_command": "ffmpeg -i {input} -c:v libvpx-vp9 -crf 33 -b:v 0 -c:a libopus {output}", "output_extension": "webm" }' ``` ```js JavaScript theme={null} const res = await fetch("https://api.madiad.com/v1/ffmpeg/jobs", { method: "POST", headers: { Authorization: `Bearer ${process.env.MADIAD_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ file_url: "https://cdn.example.com/raw/interview.mp4", full_command: "ffmpeg -i {input} -c:v libvpx-vp9 -crf 33 -b:v 0 -c:a libopus {output}", output_extension: "webm", }), }); const { job_id, status } = await res.json(); // { job_id: "ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2", status: "queued" } ``` ### 2. Kiểm tra cho đến khi hoàn thành ```bash cURL theme={null} curl https://api.madiad.com/v1/ffmpeg/jobs/ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2 \ -H "Authorization: Bearer $MADIAD_API_KEY" # Tiếp tục kiểm tra cho đến khi "status": "finished" ``` ```js JavaScript theme={null} let job; do { await new Promise((r) => setTimeout(r, 3000)); // chờ 3 giây giữa các lần kiểm tra const r = await fetch( `https://api.madiad.com/v1/ffmpeg/jobs/${job_id}`, { headers: { Authorization: `Bearer ${process.env.MADIAD_API_KEY}` } } ); job = await r.json(); } while (job.status !== "finished" && job.status !== "failed"); ``` ### 3. Tải về kết quả ```bash cURL theme={null} curl https://api.madiad.com/v1/ffmpeg/jobs/ffj_01HZX9G4P6R8S0T2V4W6X8Y0Z2/result \ -H "Authorization: Bearer $MADIAD_API_KEY" \ --output interview.webm ``` ```js JavaScript theme={null} const dl = await fetch( `https://api.madiad.com/v1/ffmpeg/jobs/${job_id}/result`, { headers: { Authorization: `Bearer ${process.env.MADIAD_API_KEY}` } } ); const buffer = await dl.arrayBuffer(); // lưu buffer ra file hoặc stream đến client ``` # Idempotency Source: https://docs.madiad.com/vi/concepts/idempotency Thử lại request an toàn mà không tạo ra bài đăng trùng lặp. Cuộc gọi mạng đôi khi thất bại theo những cách khiến bạn không chắc request đã được xử lý hay chưa. **Idempotency key** giúp việc thử lại trở nên an toàn: gửi lại cùng request với cùng key, MADIAD Hub sẽ trả về kết quả ban đầu thay vì đăng thêm một lần nữa. ## Gửi idempotency key Thêm header `Idempotency-Key` với giá trị do bạn tự chọn. Lý tưởng nhất là dùng một UUID riêng cho mỗi hành động logic. Hub cũng chấp nhận `X-Idempotency-Key` hoặc `X-Request-Id` như các header tương đương. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/photos \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Idempotency-Key: 9f1c2e7a-4b6d-4e2a-9c10-7b3f8d5a1e22" \ -F "profile_id=9f2b4c17-…" \ -F "platforms[]=instagram" \ -F "caption=Launch" \ -F "photos[]=@./launch.jpg" ``` ## Cách hoạt động * **Request đầu tiên** với một key: được xử lý bình thường, kết quả được lưu lại theo key đó. * **Gửi lại** cùng key **và cùng nội dung**: trả về **kết quả đã lưu**, không tạo thêm bài đăng nào, và response kèm header `Idempotent-Replayed: true`. * **Cùng key nhưng khác nội dung**: bị từ chối với `409 idempotency_conflict`. Không có gì được đăng. * **Key khác nhau**: được xử lý như một request hoàn toàn mới. * **Bài mà mọi nền tảng đều từ chối** (`status: "failed"`): không có gì được đăng nên key được giải phóng. Hãy sửa nội dung rồi gửi lại, dùng cùng key hoặc key mới. > [!TIP] > > Tạo key **trước** lần gửi đầu tiên và dùng lại cho mọi lần thử lại của cùng hành động đó. Tạo key mới khi thử lại sẽ làm mất tác dụng của cơ chế này. ## Phân biệt kết quả phát lại với một lần đăng thật Response phát lại giống hệt response gốc: cùng `request_id`, cùng trạng thái. Đó chính là mục đích của cơ chế, nhưng nó cũng có nghĩa là một automation bị kẹt key sẽ liên tục nhận về những response trông như thành công trong khi **không có gì mới được đăng**. Vì vậy mọi response phát lại đều kèm một header: ```http theme={null} Idempotent-Replayed: true ``` Một lần đăng thật không bao giờ có header này. Nếu bạn chạy đăng bài tự động hoặc theo lịch, hãy coi header này là tín hiệu cảnh báo: nó nghĩa là key bạn vừa gửi đã được dùng rồi, nên nội dung bạn vừa gửi lên **chưa** được đăng. > [!WARNING] > > **Mỗi bài mới phải có một key mới.** Bộ đếm ngừng tăng, một item workflow bị chạy lại, hay một template ghi cứng key đều dẫn tới việc gửi nội dung mới dưới một key đã dùng. Từ 26/07/2026, trường hợp nội dung khác nhau sẽ trả về `409 idempotency_conflict`; trước đó hệ thống lặng lẽ trả lại kết quả cũ. ## Khi kết quả chưa xác định Việc đăng bài có thể lâu hơn khoảng thời gian MADIAD Hub được phép chờ. Khi đó request **không** bị báo là thất bại, vì bài rất có thể đã lên: bên mình ngừng chờ, còn nền tảng thì không ngừng làm việc. Bạn nhận `502` với mã `upstream_timeout`, không có gì được hoàn lại, và key bạn gửi được **chốt vào câu trả lời đó**. Gửi lại cùng key sẽ nhận đúng `502` và đúng nội dung cũ. Đây là chủ ý: một automation phân nhánh theo mã trạng thái sẽ không thể ghi nhận nhầm một bài chưa rõ thành công. ```json theme={null} { "error": { "code": "upstream_timeout", "message": "…" }, "status": "unknown", "platforms": ["instagram"], "request_id": "5f29e9927d654c30a4246b56e081bdc1", "job_id": null, "results": null } ``` `request_id` trong câu trả lời đó là mã của bài. MADIAD Hub tiếp tục theo dõi bài này, và bạn cũng tra được. Việc cần làm: 1. Tra [`GET /v1/posts/status`](/vi/concepts/posts#kiểm-tra-trạng-thái-sau) bằng `request_id` đó. Lệnh trả `unknown` cho tới khi phía nền tảng nhận được bài, sau đó là `processing`, rồi kết quả (`completed`, `draft`, `partial` hoặc `failed`). Lịch sử đăng cũng tự cập nhật: khi nền tảng báo kết quả về, chậm nhất là ở lượt kiểm tra hằng ngày kế tiếp. 2. Đừng gửi lại khi bài còn `unknown` hoặc `processing`: bài đầu có thể đã lên hoặc đang lên. 3. Nếu kết quả cuối là `failed` thì không có gì được đăng: lượt đăng được hoàn và `Idempotency-Key` cũ dùng lại được. 4. Bài chữ hoặc bài video đăng ngay mà chưa tới được nền tảng sẽ được lượt kiểm tra hằng ngày chốt là `failed`, không sớm hơn. Bài ảnh, bài tài liệu, và mọi bài hẹn giờ hoặc xếp hàng rơi vào trường hợp này thì vẫn là `unknown`: hãy kiểm tra lịch sử đăng hoặc kiểm tra thẳng trên nền tảng, và nếu bài **chưa** lên thì gửi lại với một `Idempotency-Key` **mới**. 5. Đừng để automation tự retry cùng key rồi mong kết quả khác đi: key cũ vẫn trả đúng câu `502` này, kể cả khi bài đã có kết quả. Kết quả nằm ở lệnh tra trạng thái và ở lịch sử đăng. > [!NOTE] > > Bài hẹn giờ hoặc bài xếp hàng (`scheduled_at`, `add_to_queue`) kết thúc theo kiểu này có `job_id: null`: > chưa có mã lịch. Sau khi MADIAD Hub đọc lại được (khi phía nền tảng báo về, chậm nhất ở lượt kiểm > tra hằng ngày), lịch sử đăng sẽ có `job_id` để huỷ hoặc dời bài. Một ngoại lệ: bài có nội dung > riêng cho một nền tảng được gửi thành hai lịch. Nếu một lịch được nhận còn câu trả lời của lịch > kia bị mất, câu trả lời mang `job_id` của lịch đã nhận, và huỷ bằng mã đó chỉ huỷ phần đó; phần > còn lại được đọc lại sau giờ đăng của bài. ## Khi nào nên dùng Luôn dùng với bất kỳ request nào có tạo dữ liệu mới, đặc biệt là bài đăng. Cơ chế này thiết yếu khi: * Một client hoặc công cụ workflow (n8n, queues, cron) **tự động thử lại** khi timeout. * Một webhook hoặc job có thể **được giao nhiều hơn một lần**. * Người dùng **nhấp đúp** vào nút đăng bài. Key được giới hạn trong phạm vi tài khoản của bạn và được ghi nhớ đủ lâu để bao phủ các khung thời gian thử lại thực tế. # Tùy chọn theo nền tảng Source: https://docs.madiad.com/vi/concepts/platform-options Tùy chọn xuất bản theo từng nền tảng: quyền riêng tư, bản nháp, loại video, poll, CTA và nhiều hơn nữa. MADIAD Hub chuyển tiếp nguyên vẹn mọi tùy chọn mà nền tảng hỗ trợ, nên bạn không bị giới hạn ở các field liệt kê ở đây. Các field dưới đây là những tùy chọn phổ biến và quan trọng nhất. Thêm tiền tố tên nền tảng khi tùy chọn đó đặc thù cho nền tảng (`_`). Các field dạng mảng dùng quy ước `field[]`, lặp lại key một lần cho mỗi giá trị. > [!TIP] > > Mọi field trên trang này đều là **tùy chọn** trừ khi được đánh dấu **bắt buộc**. Bỏ qua một field sẽ dùng giá trị mặc định của nền tảng. ## TikTok | Field | Kiểu | Tác dụng | | - | - | - | | `privacy_level` | String | Ai có thể xem bài: `PUBLIC_TO_EVERYONE` (mặc định), `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY` | | `post_mode` | String | `DIRECT_POST` (mặc định) đăng ngay lập tức; `MEDIA_UPLOAD` gửi media vào hộp thư đến của TikTok dưới dạng bản nháp để creator chỉnh sửa trong ứng dụng | | `auto_add_music` | Boolean | Thêm nhạc nền cho bài ảnh (mặc định `false`) | | `tiktok_music_id` | String | Chọn bài nhạc cho bài ảnh: `id` của một bài lấy từ `GET /v1/tiktok/music/trending` (hoặc `/search`). Nếu kết nối TikTok của Profile chưa có quyền dùng nhạc, bài vẫn lên (hoặc được lưu nháp) nhưng không có nhạc, và kết quả có một mục `warnings` mã `music_not_applied`: kết nối lại TikTok cho Profile đó. Xem [Cảnh báo](/vi/concepts/posts#cảnh-báo-bài-đã-lên-nhưng-chưa-đúng-hẳn-như-yêu-cầu) | | `disable_comment` | Boolean | Tắt bình luận trên bài đăng (mặc định `false`) | | `disable_duet` | Boolean | Tắt Duet trên bài đăng (mặc định `false`) | | `disable_stitch` | Boolean | Tắt Stitch trên bài đăng (mặc định `false`) | | `brand_content_toggle` | Boolean | Đặt `true` cho nội dung hợp tác thương hiệu trả phí từ bên thứ ba (mặc định `false`) | | `brand_organic_toggle` | Boolean | Đặt `true` khi quảng bá thương hiệu của chính creator (mặc định `false`) | | `photo_cover_index` | Integer | Chỉ số của ảnh dùng làm ảnh bìa/thumbnail trong bài ảnh (mặc định `0`) | | `tiktok_title` | String | Tiêu đề bài ảnh (≤ 90 ký tự), xem [Giới hạn ký tự](/vi/concepts/posts) | | `tiktok_description` | String | Caption dài cho bài ảnh (≤ 4.000 ký tự), xem [Giới hạn ký tự](/vi/concepts/posts) | > [!NOTE] > > `brand_content_toggle` và `brand_organic_toggle` có thể cùng là `true`, ví dụ creator quảng bá thương hiệu của chính mình trong một hợp tác trả phí. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/video \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'"$PROFILE_ID"'", "platforms": ["tiktok"], "caption": "Hậu trường sản xuất", "video_url": "https://cdn.example.com/clip.mp4", "privacy_level": "FOLLOWER_OF_CREATOR", "disable_duet": true }' ``` ## Instagram | Field | Kiểu | Tác dụng | | - | - | - | | `media_type` | String | `IMAGE` (mặc định) hoặc `STORIES`; media hỗn hợp tự động được xử lý thành carousel hoặc reel | | `collaborators` | String | Danh sách username cộng tác viên, phân cách bằng dấu phẩy | | `user_tags` | String (JSON) | Tag người dùng vào ảnh; cần một mảng JSON gồm các đối tượng `{username, x, y}` trong đó `x` và `y` là số thực 0.0-1.0 thể hiện vị trí trên ảnh | | `location_id` | String | Id địa điểm Instagram để đính kèm bài đăng | | `instagram_first_comment` | String | Tự động đăng bình luận ngay sau khi xuất bản, xem [Bình luận đầu tiên](/vi/concepts/posts) | ```bash theme={null} -F "media_type=STORIES" \ -F 'user_tags=[{"username":"jane_doe","x":0.5,"y":0.3}]' \ -F "location_id=213385402" ``` ## Facebook | Field | Kiểu | Tác dụng | | - | - | - | | `facebook_page_id` | String | **Bắt buộc** khi profile có nhiều hơn một Facebook Page: chọn Page sẽ đăng lên (tự động chọn khi chỉ có một Page) | | `facebook_media_type` | String | `REELS` (mặc định, 9:16, ≤ 90 giây; video dài hơn bị cắt còn 90 giây), `STORIES` (tồn tại 24 giờ), hoặc `VIDEO` (video Page thông thường, bất kỳ tỷ lệ khung hình, tối đa 4 giờ, hỗ trợ thumbnail tùy chỉnh) | | `thumbnail_url` | String | URL thumbnail tùy chỉnh, chỉ áp dụng khi `facebook_media_type=VIDEO` | | `video_state` | String | `PUBLISHED` (mặc định) hoặc `DRAFT` (lưu video Page dưới dạng bản nháp để đăng thủ công) | | `facebook_description` | String | Mô tả dài hiển thị bên dưới video | | `facebook_first_comment` | String | Tự động đăng bình luận đầu tiên ngay sau khi xuất bản (chỉ áp dụng cho bài đăng Page), xem [Bình luận đầu tiên](/vi/concepts/posts) | > [!WARNING] > > `facebook_page_id` bị bỏ qua khi Profile đang **ghim** một Page: bài vẫn đi tới Page đã ghim và vẫn báo thành công. Xem [Ghim một Page vào Profile](/vi/concepts/profiles#ghim-một-page-vào-profile). > [!NOTE] > > Để đăng video Page dài (dọc hoặc ngang, bất kỳ độ dài lên đến 4 giờ), hãy đặt `facebook_media_type=VIDEO`. Nếu không, mặc định là `REELS` và video bị giới hạn còn 90 giây. ```bash theme={null} -F "facebook_media_type=VIDEO" \ -F "thumbnail_url=https://cdn.example.com/thumb.jpg" \ -F "facebook_description=Hướng dẫn đầy đủ — xem đến hết nhé." ``` ## YouTube | Field | Kiểu | Tác dụng | | - | - | - | | `privacyStatus` | String | `public` (mặc định), `unlisted` hoặc `private` | | `tags[]` | Array | Tags video (lặp lại key một lần mỗi tag) | | `categoryId` | String | Id danh mục YouTube (mặc định `"22"`, People & Blogs) | | `selfDeclaredMadeForKids` | Boolean | Khai báo video dành cho trẻ em (mặc định `false`) | | `embeddable` | Boolean | Cho phép nhúng video trên các trang khác (mặc định `true`) | | `license` | String | `youtube` (mặc định) hoặc `creativeCommon` | | `defaultLanguage` | String | Mã ngôn ngữ BCP-47 cho metadata video (ví dụ `"en"`, `"vi"`) | | `defaultAudioLanguage` | String | Mã ngôn ngữ BCP-47 cho bản audio của video | | `thumbnail` | File | Thumbnail tùy chỉnh dạng file nhị phân (JPG/PNG/GIF/BMP, ≤ 2 MB; không hỗ trợ trên Shorts) | | `thumbnail_url` | String | Thumbnail tùy chỉnh qua URL công khai (cùng yêu cầu định dạng như `thumbnail`) | | `youtube_subtitle_file` | File | File phụ đề SRT hoặc VTT để tải lên | | `youtube_subtitle_language` | String | Mã BCP-47 cho track phụ đề, **bắt buộc** khi đặt `youtube_subtitle_file` | | `youtube_subtitle_name` | String | Tên hiển thị của track phụ đề | | `youtube_title` | String | Tiêu đề video (≤ 100 ký tự), xem [Giới hạn ký tự](/vi/concepts/posts) | | `youtube_description` | String | Mô tả video (≤ 5.000 ký tự), xem [Giới hạn ký tự](/vi/concepts/posts) | ```bash theme={null} -F "privacyStatus=unlisted" \ -F "tags[]=tutorial" \ -F "tags[]=coffee" \ -F "categoryId=26" \ -F "thumbnail=@./thumb.jpg" \ -F "youtube_subtitle_file=@./captions.srt" \ -F "youtube_subtitle_language=vi" \ -F "youtube_subtitle_name=Tiếng Việt" ``` ## Pinterest | Field | Kiểu | Tác dụng | | - | - | - | | `pinterest_board_id` | String | **Bắt buộc**: mọi pin phải thuộc một board | | `pinterest_link` | String | URL đích mà pin dẫn đến | | `pinterest_alt_text` | String | Alt text hỗ trợ tiếp cận cho ảnh | | `pinterest_cover_image_url` | String | URL ảnh bìa cho video pin | | `pinterest_title` | String | Tiêu đề pin (≤ 100 ký tự), xem [Giới hạn ký tự](/vi/concepts/posts) | | `pinterest_description` | String | Mô tả pin (≤ 500 ký tự), xem [Giới hạn ký tự](/vi/concepts/posts) | ```bash theme={null} -F "pinterest_board_id=123456789" \ -F "pinterest_link=https://example.com/product" \ -F "pinterest_alt_text=Ảnh flat-lay sản phẩm mới của chúng tôi" ``` ## Reddit | Field | Kiểu | Tác dụng | | - | - | - | | `subreddit` | String | **Bắt buộc**: subreddit đích, không có tiền tố `r/` | | `reddit_title` | String | Tiêu đề bài đăng, riêng biệt với phần thân; dùng `caption` nếu không truyền, xem [Giới hạn ký tự](/vi/concepts/posts) | | `flair_id` | String | Id flair để gán cho bài đăng | | `first_comment_media[]` | File(s) | Ảnh đính kèm bình luận đầu tiên (chỉ Reddit), xem [Bình luận đầu tiên](/vi/concepts/posts) | ```bash theme={null} -F "subreddit=coffee" \ -F "reddit_title=Rang cà phê tại nhà — lần đầu thử" \ -F "flair_id=abc123" \ -F "first_comment_media[]=@./roast-detail.jpg" ``` ## X (Twitter) | Field | Kiểu | Tác dụng | | - | - | - | | `poll_options[]` | Array | 2-4 lựa chọn cho poll, mỗi mục ≤ 25 ký tự, không dùng được cùng `quote_tweet_id` | | `poll_duration` | Integer | Thời gian poll tính bằng phút (5-10.080; mặc định `1440` = 24 giờ) | | `reply_settings` | String | Ai có thể trả lời: `following`, `mentionedUsers`, `subscribers` hoặc `verified` | | `quote_tweet_id` | String | Quote một tweet có sẵn bằng id (không dùng được cùng media) | | `reply_to_id` | String | Đăng tweet dưới dạng reply cho tweet id này | | `community_id` | String | Đăng vào một X Community cụ thể | | `x_long_text_as_post` | Boolean | Đăng văn bản dài thành một post duy nhất (X Premium) thay vì tự tách thành thread (mặc định `false`) | | `x_thread_image_layout` | String | Danh sách phân cách bằng dấu phẩy, mỗi số cho biết bao nhiêu ảnh xuất hiện trong từng tweet của thread (mỗi giá trị 0-4, tổng phải bằng số ảnh), ví dụ `"0,4"` | | `tagged_user_ids[]` | Array | Tag tối đa 10 người dùng trong media bằng user id của họ | | `geo_place_id` | String | Đính kèm id địa điểm địa lý vào bài đăng | > [!NOTE] > > X loại bỏ mọi URL sẽ trở thành liên kết bấm được khỏi nội dung bài trước khi đăng. Xem [Giới hạn ký tự](/vi/concepts/posts). Hãy để link trong bio của profile hoặc trong ảnh/video. ```bash theme={null} -F "poll_options[]=Rang nhẹ" \ -F "poll_options[]=Rang đậm" \ -F "poll_options[]=Không có ý kiến" \ -F "poll_duration=4320" \ -F "reply_settings=following" ``` ## Threads | Field | Kiểu | Tác dụng | | - | - | - | | `threads_long_text_as_post` | Boolean | Đăng văn bản dài thành một post duy nhất thay vì tự tách thành thread (mặc định `false`) | | `threads_thread_media_layout` | String | Danh sách phân cách bằng dấu phẩy, mỗi số cho biết bao nhiêu media xuất hiện trong từng post của thread (mỗi giá trị 0-10, tổng phải bằng số file), ví dụ `"0,5"` | | `threads_topic_tag` | String | Một topic tag, 1-50 ký tự, không chứa `"."` hoặc `"&"`, giúp tăng tiếp cận | ```bash theme={null} -F "threads_topic_tag=coffeetime" \ -F "threads_long_text_as_post=true" ``` ## LinkedIn | Field | Kiểu | Tác dụng | | - | - | - | | `visibility` | String | Phạm vi hiển thị bài đăng (`PUBLIC`) | | `target_linkedin_page_id` | String | Đăng lên trang tổ chức thay vì profile cá nhân | | `linkedin_link_url` | String | Link đính kèm bài đăng | | `linkedin_first_comment` | String | Tự động đăng bình luận đầu tiên, xem [Bình luận đầu tiên](/vi/concepts/posts) | ```bash theme={null} -F "target_linkedin_page_id=urn:li:organization:12345678" \ -F "linkedin_link_url=https://example.com/launch" \ -F "visibility=PUBLIC" ``` `target_linkedin_page_id` nhận URN đầy đủ dạng `urn:li:organization:…`. Liệt kê các tổ chức mà một Profile đăng bài được bằng `GET /v1/connections/linkedin/pages`, xem [LinkedIn Pages](/vi/concepts/profiles#linkedin-pages). Trường này bị bỏ qua khi Profile đang **ghim** một trang tổ chức, xem [Ghim một Page vào Profile](/vi/concepts/profiles#ghim-một-page-vào-profile). ## Bluesky | Field | Kiểu | Tác dụng | | - | - | - | | `bluesky_link_url` | String | Link đính kèm bài đăng | ```bash theme={null} -F "bluesky_link_url=https://example.com/post" ``` ## Google Business > [!NOTE] > > Bài đăng Google Business Profile xuất hiện trên Google Maps và trong kết quả Tìm kiếm của doanh nghiệp bạn. Tiền tố `gbp_` áp dụng cho tất cả các tùy chọn của nền tảng này. | Field | Kiểu | Tác dụng | | - | - | - | | `gbp_topic_type` | String | `STANDARD` (mặc định), `EVENT` hoặc `OFFER` | | `gbp_cta_type` | String | Nút CTA: `BOOK`, `ORDER`, `SHOP`, `LEARN_MORE`, `SIGN_UP` hoặc `CALL` | | `gbp_cta_url` | String | URL cho nút CTA, bắt buộc khi đặt `gbp_cta_type` | | `gbp_location_id` | String | Địa điểm sẽ đăng lên, tự động chọn khi tài khoản chỉ có một địa điểm | **Các field cho sự kiện** (khi `gbp_topic_type=EVENT`): | Field | Kiểu | Tác dụng | | - | - | - | | `gbp_event_title` | String | **Bắt buộc**: tên sự kiện | | `gbp_event_start_date` | String | **Bắt buộc**: ngày bắt đầu theo định dạng `YYYY-MM-DD` | | `gbp_event_end_date` | String | **Bắt buộc**: ngày kết thúc theo định dạng `YYYY-MM-DD` | | `gbp_event_start_time` | String | Giờ bắt đầu theo định dạng 24 giờ `HH:MM` | | `gbp_event_end_time` | String | Giờ kết thúc theo định dạng 24 giờ `HH:MM` | **Các field cho ưu đãi** (khi `gbp_topic_type=OFFER`): | Field | Kiểu | Tác dụng | | - | - | - | | `gbp_coupon_code` | String | Mã phiếu giảm giá hoặc khuyến mãi | | `gbp_redeem_url` | String | URL để đổi ưu đãi | | `gbp_terms` | String | Nội dung điều khoản và điều kiện | ```bash theme={null} # Bài đăng tiêu chuẩn có CTA -F "gbp_topic_type=STANDARD" \ -F "gbp_cta_type=LEARN_MORE" \ -F "gbp_cta_url=https://example.com/menu" # Bài đăng sự kiện -F "gbp_topic_type=EVENT" \ -F "gbp_event_title=Khai trương cơ sở mới" \ -F "gbp_event_start_date=2025-09-01" \ -F "gbp_event_end_date=2025-09-01" \ -F "gbp_event_start_time=09:00" \ -F "gbp_event_end_time=17:00" ``` ## Kênh nhập thông tin thủ công Mười một kênh được kết nối bằng cách dán thông tin xác thực của chính bạn thay vì qua OAuth. Xem [Kênh](/vi/concepts/channels) để biết danh sách trường và endpoint kết nối. Chúng nhận `caption` dùng chung, hoặc một `_caption` riêng (xem [Ghi đè theo từng nền tảng](/vi/concepts/posts#ghi-đè-theo-từng-nền-tảng)); Mastodon và các nền tảng bài viết có quy tắc riêng cho nội dung đó, nêu bên dưới. Không có tuỳ chọn sáng tạo nào khác, và không kênh nào trả về dữ liệu analytics. Nhiều kênh trong nhóm này chỉ nhận một loại bài. Gửi sai loại sẽ bị từ chối tại chỗ bằng `400 invalid_request` trước khi có gì được đăng. Bảng ở [Kênh](/vi/concepts/channels) là chỗ tra. ### Kênh chat Văn bản vượt quá giới hạn nền tảng sẽ bị **cắt bớt**, không bị từ chối, nên hãy giữ caption trong giới hạn ghi ở [Giới hạn ký tự](/vi/concepts/posts): * **Telegram**: 4.096 ký tự cho tin nhắn văn bản thuần; 1.024 ký tự cho caption kèm media. * **Discord**: 2.000 ký tự. * **Slack**: chỉ nhận bài chữ. ### Fediverse * **Mastodon**: nhận chữ, ảnh và video. Giới hạn mỗi bài do chính máy chủ bạn kết nối quy định: mặc định 500 ký tự, và máy chủ có thể nới. `caption` dùng chung dài quá 500 thì chỉ bị cắt còn 500 cho riêng Mastodon. Muốn gửi dài hơn tới máy chủ cho phép, hãy đặt `mastodon_caption`: trường này được gửi đúng như bạn viết, không kiểm tra độ dài, và máy chủ sẽ từ chối chữ vượt giới hạn của nó. * **Lemmy**: nhận chữ và ảnh. Cộng đồng đích được cố định lúc kết nối. * **Nostr**: chỉ nhận bài chữ, phát tới các relay bạn khai lúc kết nối. ### Kênh xuất bản * **WordPress**: nhận chữ, ảnh và video. * **dev.to**, **Hashnode**: chỉ nhận bài chữ. * **Whop**: chỉ nhận bài chữ, đăng vào experience bạn khai lúc kết nối. * **listmonk**: chỉ nhận bài chữ, gửi tới danh sách bạn khai lúc kết nối. Kênh này *gửi email thật tới người nhận thật*, hãy thử với một danh sách nhỏ trước. Trên các nền tảng bài viết (WordPress, dev.to, Hashnode, Lemmy, listmonk và Whop), chữ bạn gửi là thân bài, và tiêu đề được lấy từ dòng đầu tiên trừ khi bạn đặt `_title`. `_caption` thay cho `caption` dùng chung làm thân bài của kênh đó và được gửi thành job riêng của kênh đó, xem [Ghi đè theo từng nền tảng](/vi/concepts/posts#ghi-đè-theo-từng-nền-tảng). # Bài đăng Source: https://docs.madiad.com/vi/concepts/posts Xuất bản văn bản, ảnh hoặc video lên nhiều nền tảng chỉ trong một request. Một **bài đăng** xuất bản nội dung lên một hoặc nhiều nền tảng được kết nối với Profile. Bạn gửi một request, MADIAD Hub phân phối nội dung đến từng nền tảng bạn liệt kê và trả về kết quả theo từng nền tảng. > [!NOTE] > > **Đăng bài không giới hạn trên mọi gói trả phí**, không có hạn mức số bài đăng hằng tháng. Mức sử dụng vẫn được ghi nhận, và **mỗi request được tính là một lượt đăng bất kể nhắm tới bao nhiêu nền tảng**: đăng lên TikTok, Instagram và Facebook trong một lần gọi được ghi nhận **1** lượt đăng, không phải 3. Xem [Mức sử dụng](/vi/concepts/usage) để đọc số liệu hiện tại. ## Chọn endpoint theo loại media | Endpoint | Dùng cho | | - | - | | `POST /v1/posts/text` | Cập nhật chỉ có văn bản | | `POST /v1/posts/photos` | Một hoặc nhiều ảnh (bao gồm carousel ảnh) | | `POST /v1/posts/video` | Một video duy nhất | Endpoint ảnh dùng `multipart/form-data` khi tải file lên, hoặc JSON thuần khi ảnh đến từ URL (`photo_urls[]`, xem [Đăng ảnh bằng URL](#đăng-ảnh-bằng-url)). Văn bản và video dùng JSON. ## Số ảnh mỗi bài MADIAD Hub không tự đặt giới hạn số ảnh bạn đính kèm vào `photos[]`, ảnh được chuyển thẳng qua nền tảng. Mỗi nền tảng tự áp giới hạn tối đa riêng, nên giới hạn thực tế cho một bài fan-out là giới hạn **nhỏ nhất** trong số các nền tảng bạn nhắm tới: | Nền tảng | Số ảnh tối đa | Khi vượt giới hạn | | - | - | - | | Pinterest | 5 | Giới hạn cứng, nền tảng từ chối bài đăng | | Instagram | 10 | Giới hạn cứng, nền tảng từ chối bài đăng | | Telegram | 10 | Giới hạn cứng, nền tảng từ chối bài đăng | | Discord | 10 | Giới hạn cứng, nền tảng từ chối bài đăng | | X | 4 mỗi tweet | Ảnh dư được tự động phân bổ vào một thread, xem `x_thread_image_layout` trong [Tùy chọn theo nền tảng](/vi/concepts/platform-options) | | Threads | 10 mỗi post | Media dư được tự động phân bổ vào một thread, xem `threads_thread_media_layout` trong [Tùy chọn theo nền tảng](/vi/concepts/platform-options) | | Bluesky | 4 | Giới hạn cứng, nền tảng từ chối bài đăng | | TikTok, LinkedIn, Facebook, Reddit | Không có giới hạn cố định do dịch vụ xuất bản áp đặt | Không áp dụng | > [!NOTE] > > Đây là giới hạn do chính các nền tảng công bố, không phải giới hạn MADIAD Hub áp đặt, các con số này có thể thay đổi phía nền tảng mà không báo trước. Nếu chưa chắc, hãy thử với một batch nhỏ trước. ## Phân phối với `platforms[]` Liệt kê tất cả nền tảng đích. Nội dung giống nhau được gửi đến từng nền tảng, trừ khi bạn ghi đè theo từng nền tảng. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/photos \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Idempotency-Key: 9f1c2e7a-4b6d-4e2a-9c10-7b3f8d5a1e22" \ -F "profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -F "platforms[]=instagram" \ -F "platforms[]=facebook" \ -F "platforms[]=tiktok" \ -F "caption=New blend dropping today" \ -F "photos[]=@./launch.jpg" ``` > [!NOTE] > > **Một request dùng chung phần media cho tất cả nền tảng được liệt kê.** Bạn có thể ghi đè phần > chữ theo từng nền tảng (`caption` → `_caption`, xem bên dưới) và truyền tuỳ chọn riêng > của nền tảng, nhưng `photos[]` / `photo_urls[]` / `video` bạn đính kèm sẽ đi tới **mọi** nền tảng trong request đó, > **không có** field media riêng cho từng nền tảng. Muốn đăng **ảnh hoặc video khác nhau cho mỗi nền > tảng**, hãy gửi **một request riêng cho từng nền tảng** (mỗi request kèm `photos[]`/`video` của > riêng nó). ## Đăng ảnh bằng URL Thay vì tải file lên, bạn có thể truyền link công khai qua `photo_urls[]`. MADIAD Hub sẽ tự tải từng ảnh về phía máy chủ và đăng y hệt như khi bạn gửi file trực tiếp. Nên dùng cách này cho **ảnh dung lượng lớn hoặc nhiều ảnh một lúc**: phần truyền tải nặng diễn ra ở phía chúng tôi, request của bạn luôn gọn nhẹ và không bao giờ gặp lỗi vượt kích thước request. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/photos \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3ab77c14-08e5-4d69-b2f1-6c5904ad7e83" \ -d '{ "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["instagram", "facebook"], "caption": "New blend dropping today", "photo_urls": [ "https://cdn.example.com/launch-1.jpg", "https://cdn.example.com/launch-2.jpg" ] }' ``` * Mỗi URL phải là **link ảnh trực tiếp, công khai, dùng `https://`** (hostname, không dùng địa chỉ IP). Link trang chia sẻ mở ra một trang web (ví dụ trang xem của drive) sẽ bị từ chối kèm thông báo lỗi rõ ràng. Hãy dùng URL file trực tiếp. Redirect được theo tối đa 5 bước, nhưng mọi bước đều phải là `https://`. * Tối đa **40 URL mỗi request**, **30 MB mỗi ảnh** và **tổng 120 MB**. * Cũng dùng được trong multipart: gửi `photo_urls[]` dưới dạng field text, và trộn thoải mái với `photos[]` dạng file trong cùng một request. Với carousel, file tải lên đứng trước, rồi tới ảnh từ URL, mỗi nhóm theo thứ tự gửi. ## Ghi đè theo từng nền tảng `caption` là chú thích chung mặc định. Ghi đè cho một nền tảng cụ thể bằng field `_caption`, tiện lợi cho hashtag, mention hoặc giới hạn độ dài. ```bash theme={null} -F "caption=New blend dropping today" \ -F "instagram_caption=New blend dropping today ☕ #specialtycoffee" \ -F "x_caption=New blend out now →" ``` Cùng kiểu tiền tố này áp dụng cho các tùy chọn theo nền tảng khác, chẳng hạn `instagram_first_comment`. > [!NOTE] > > **Ghi đè cho Instagram được đăng thành một job riêng.** Khi `instagram_caption` khác với > `caption` chung và request còn nhắm tới nền tảng khác, MADIAD Hub gửi Instagram tách riêng để > nó nhận đúng nội dung bạn viết. Với bài đăng thường, bạn không thấy gì khác biệt: vẫn **một > lượt đăng** tính vào hạn mức, một `request_id`, một trạng thái để theo dõi. Với bài **hẹn > giờ** thì có một điểm nhìn thấy được: danh sách lịch đăng hiển thị Instagram thành một mục > riêng, nên huỷ hoặc sửa nó là thao tác tách khỏi các nền tảng còn lại. > > **Bài ảnh và video lên Google Business cũng vậy.** Nội dung riêng của Google Business > (`google_business_caption`, hoặc caption đã cắt còn 1.500) được gửi thành một job Google Business > riêng, vì với hai loại bài này nền tảng chỉ đọc nội dung chung. Bài văn bản lên Google Business > vẫn nằm chung một job. > > **Các nền tảng dạng bài viết cũng vậy: WordPress, Dev.to, Hashnode, Lemmy, Listmonk và Whop.** Ở > các nền tảng này, `_title` là tiêu đề bài viết còn `_caption` là phần thân bài. > Khi `wordpress_caption` khác với `caption` chung, nó được gửi thành một job WordPress riêng để bài > viết mang đúng nội dung đó chứ không phải nội dung chung. Quy tắc `add_to_queue` bên dưới cũng áp > dụng cho các nền tảng này. > > Có một tổ hợp bị từ chối: `add_to_queue` đi kèm `instagram_caption` khác với `caption` chung, > **khi request chọn Instagram cùng các nền tảng khác**. Khi đó Instagram sẽ chiếm một slot hàng > đợi riêng và lên vào thời điểm khác với phần còn lại, nên request bị từ chối thay vì đăng lệch > giờ. Hãy đưa Instagram vào hàng đợi bằng một request riêng, hoặc dùng `caption` chung. > `google_business_caption` trên bài ảnh, video cũng vậy. Nếu request chỉ chọn riêng nền tảng đó > thì không cần job riêng: caption của nó được dùng làm nội dung bài và vào hàng đợi bình thường. ## Giới hạn ký tự `caption` ánh xạ vào trường văn bản chính của mỗi nền tảng. Giới hạn khác nhau theo nền tảng, và ở một vài nền tảng trường đó là **tiêu đề** ngắn, còn nội dung dài nằm ở `description` riêng: | Nền tảng | Giới hạn `caption` | Ghi chú | | - | - | - | | Facebook | 63.206 | | | Instagram | 2.200 | | | LinkedIn | 3.000 | | | TikTok (video) | 2.200 | | | TikTok (ảnh) | 90 | Đây là **tiêu đề**; caption còn được giữ làm `tiktok_description` (≤ 4.000) | | YouTube | 100 | `caption` trở thành **tiêu đề** video, và cả mô tả nếu bạn không đặt `description` (≤ 5.000 byte) | | Pinterest | 100 | `caption` trở thành **tiêu đề** pin, và cả mô tả pin nếu bạn không đặt `pinterest_description` (≤ 500) | | Reddit | 300 | `caption` trở thành **tiêu đề** bài; phần thân dùng `description` (≤ 5.000) | | X | 280 | Văn bản dài hơn sẽ tự tách thành chuỗi (thread) | | Threads | 500 | Văn bản dài hơn sẽ tự tách thành chuỗi | | Bluesky | 300 | Văn bản dài hơn sẽ tự tách thành chuỗi | | Google Business | 1.500 | | | Mastodon | 500 | Mức mặc định; mỗi máy chủ tự đặt giới hạn riêng. Máy chủ của bạn cho phép dài hơn thì đặt `mastodon_caption` để gửi đủ: trường này được gửi đúng như bạn viết, không kiểm tra độ dài trước, và máy chủ sẽ từ chối nếu vượt giới hạn của nó. Mastodon tính mỗi đường link là 23 ký tự | | Telegram | 4.096 (text) · 1.024 (caption kèm media) | Dài hơn sẽ bị cắt | | Discord | 2.000 | Dài hơn sẽ bị cắt | Một số quy tắc rút ra từ bảng trên: * **`caption` dài quá giới hạn của nền tảng nào thì chỉ bị cắt cho riêng nền tảng đó.** Các nền tảng khác vẫn nhận caption đầy đủ, và request không bị từ chối. Quy tắc này áp cho tiêu đề (YouTube, Pinterest, Reddit, bài ảnh TikTok), cho phần thân bài (Instagram, LinkedIn, Google Business, video TikTok, Mastodon), và cho phần mô tả lấy từ caption (Pinterest 500, YouTube 5.000 byte, bài ảnh TikTok 4.000). Tiêu đề được gộp thành một dòng; thân bài giữ nguyên xuống dòng. Chỗ cắt rơi vào cuối một từ nếu vẫn giữ được ít nhất 60% giới hạn, không bao giờ cắt đôi emoji, và kết thúc bằng một dấu "…". * **Phản hồi cho biết đã cắt những gì.** Mảng `truncated` liệt kê từng nền tảng và phần chữ bị cắt (`text`: `title`, `caption` hoặc `description`), kèm `length` của caption và `limit`. Nền tảng nào không có trong danh sách thì đã nhận đủ caption. Muốn tự chọn nội dung, hãy đặt trường riêng của nền tảng đó: `_caption` (hoặc `_title`), hay các trường mô tả trong bảng trên. * **Nội dung bạn đặt riêng cho một nền tảng thì không bao giờ bị cắt.** `_title` hay `_caption` bạn gửi được đăng đúng như bạn viết. Vì vậy, nếu nó dài quá giới hạn của nền tảng đó, request bị từ chối kèm lỗi nêu rõ trường. Các trường mô tả bạn tự đặt (`description`, `pinterest_description`, `youtube_description`, `tiktok_description`) cũng được gửi đúng như bạn viết, và không được kiểm tra độ dài trước khi đăng. * **Một ngoại lệ: bài Instagram (và bài ảnh, video Google Business hoặc Mastodon) vào hàng đợi.** Phần chữ đã cắt cho Instagram đi trong một job Instagram riêng (xem ghi chú ở trên), và với bài ảnh, video thì Google Business và Mastodon cũng vậy. Vì thế request có `add_to_queue`, chọn một trong hai nền tảng này **cùng các nền tảng khác** và caption dài quá giới hạn của nó sẽ bị từ chối. Hãy rút ngắn caption, hoặc đưa nền tảng đó vào hàng đợi bằng một request riêng. Nếu chỉ đưa riêng nền tảng đó vào hàng đợi thì caption vẫn được cắt và xếp lịch bình thường. * **Trên dashboard MADIAD Hub**, bộ đếm dưới ô Nội dung đo theo giới hạn thấp nhất trong các nền tảng bạn đã chọn. Chưa chọn nền tảng nào thì bộ đếm đo theo mọi nền tảng đã kết nối với profile. Ngay dưới đó là danh sách nền tảng sẽ bị cắt, cắt còn bao nhiêu ký tự. Đây chỉ là thông báo; bài vẫn đăng bình thường. Trình soạn bài còn cho bạn chọn viết **một nội dung riêng cho từng nền tảng**, thay vì một caption chung ("Riêng từng nền tảng"). Khi đó mỗi nền tảng đã chọn có một ô riêng và được gửi nội dung trong ô đó. Hub không cắt chữ này, trừ một trường hợp: với bài Pinterest, bài YouTube và bài ảnh TikTok, ô tiêu đề để trống sẽ được điền một tiêu đề cắt từ chính nội dung của nền tảng đó. Nội dung vượt giới hạn cứng của nền tảng sẽ bị từ chối kèm tên ô trước khi gửi đi, và ô để trống cũng bị từ chối. Ô của Mastodon được gửi đúng như đã gõ, giống `mastodon_caption`. * **Facebook, X, Threads và Bluesky không bao giờ bị cắt.** X, Threads và Bluesky tự tách văn bản dài thành chuỗi bài, giới hạn của Facebook là 63.206, còn Telegram và Discord tự cắt ở phía họ. * **Phần ghi đè theo từng nền tảng được kiểm tra**, với những nền tảng có giới hạn cứng: Instagram, LinkedIn, TikTok, YouTube, Reddit, Pinterest và Google Business. Một `_title` (hoặc `_caption`) vượt giới hạn (ví dụ `tiktok_title` > 90 cho bài ảnh, `reddit_title` > 300, hay `instagram_caption` > 2.200) sẽ bị báo lỗi rõ ràng trước khi đăng. X, Threads và Bluesky không kiểm trước vì chúng tự tách thành chuỗi bài thay vì từ chối; Telegram và Discord thì cắt bớt; còn ghi đè cho Facebook chỉ bị giới hạn ở Reels. Những trường hợp này được gửi thẳng tới nền tảng và tự báo kết quả riêng. * **Link trên X.** Mọi URL mà X biến thành liên kết bấm được sẽ bị gỡ khỏi bài X trước khi đăng. Hãy để link trong hồ sơ hoặc trong ảnh/video. ## Tùy chọn nâng cao theo nền tảng Bất kỳ tùy chọn nào một nền tảng hỗ trợ đều có thể truyền qua, MADIAD Hub chuyển tiếp nguyên vẹn, nên bạn không bị giới hạn ở các field liệt kê ở đây. Thêm tiền tố tên nền tảng khi tùy chọn đó đặc thù cho nền tảng. Một vài ví dụ phổ biến: | Field | Nền tảng | Tác dụng | | - | - | - | | `instagram_first_comment` | Instagram | Tự động đăng bình luận đầu tiên sau khi xuất bản | | `media_type` | Instagram | `REELS`, `STORIES`, … | | `privacy_level` | TikTok | `PUBLIC_TO_EVERYONE`, `SELF_ONLY`, … | | `disable_comment`, `disable_duet`, `disable_stitch` | TikTok | Công tắc tương tác | | `auto_add_music` | TikTok | Thêm nhạc nền cho bài ảnh (`true`/`false`, mặc định `false`) | | `tags[]` | YouTube | Tags cho video | | `privacyStatus` | YouTube | `public`, `unlisted`, `private` | | `visibility` | LinkedIn | Phạm vi hiển thị bài đăng | | `poll_options[]` + `poll_duration` | X | Đính kèm poll | Các field dạng mảng dùng quy ước `field[]`, lặp lại key một lần cho mỗi giá trị. Để xem toàn bộ các tùy chọn theo từng nền tảng (mức độ riêng tư, loại video, poll, CTA, tải phụ đề và nhiều hơn nữa), xem [Tùy chọn theo nền tảng](/vi/concepts/platform-options). ## Bình luận đầu tiên (first comment) Tự động đăng một bình luận ngay khi bài vừa xuất bản, tiện để gắn link, hashtag hoặc lời kêu gọi hành động mà bạn không muốn nhét vào caption chính. Đặt `first_comment` làm giá trị chung, và ghi đè theo từng nền tảng bằng `_first_comment` (giá trị riêng từng nền tảng được ưu tiên khi có cả hai). ```bash theme={null} -F "caption=New blend dropping today" \ -F "first_comment=Mua ngay tại 👉 link trong bio" \ -F "instagram_first_comment=Bấm link trong bio ☕ #specialtycoffee" ``` Hỗ trợ trên Instagram, Facebook, Threads, Bluesky, Reddit, X, YouTube và LinkedIn. Một vài hành vi riêng theo nền tảng: * **X và Threads**: đăng dưới dạng reply cho bài. Với bài X tự tách chuỗi, nó reply vào tweet cuối của chuỗi. * **YouTube**: đăng thành bình luận cấp cao nhất dưới video. * **Instagram**: chỉ áp dụng cho bài ảnh và video (Instagram không có bài chỉ văn bản). Để đính ảnh vào bình luận, gửi `first_comment_media[]` dạng file nhị phân, y hệt `photos[]`. Hiện chỉ hỗ trợ **Reddit**, và không dùng được cho bài đã lên lịch hoặc đang trong hàng đợi. ```bash theme={null} -F "first_comment=Bộ ảnh chi tiết bên dưới 👇" \ -F "first_comment_media[]=@./detail-1.jpg" \ -F "first_comment_media[]=@./detail-2.jpg" ``` ## Caption sinh bằng AI Để MADIAD Hub tự viết caption thay vì bạn cung cấp. Hữu ích nhất với bài ảnh và video: ```bash theme={null} -F "autogenerate=true" \ -F "autogenerate_language=vi" ``` | Field | Tác dụng | | - | - | | `autogenerate` | Sinh cả title lẫn description | | `autogenerate_title` | Chỉ sinh title | | `autogenerate_description` | Chỉ sinh description | | `autogenerate_language` | Ép ngôn ngữ đầu ra (ví dụ `en`, `vi`) | ## Yêu cầu của từng nền tảng > [accordion] Facebook đăng lên Page > > Bài đăng Facebook đi đến một **Page**, không phải Profile cá nhân. Nếu Profile chỉ có một Page thì tự động dùng Page đó; nếu có nhiều Page, truyền `facebook_page_id` để chọn. Để liệt kê các Page của một Profile và lấy ID của chúng, dùng `GET /v1/connections/facebook/pages`, xem [Tìm ID của Page & board](/vi/concepts/profiles). Nếu Profile đang ghim một Page thì Page đó thắng `facebook_page_id`, xem [Ghim một Page vào Profile](/vi/concepts/profiles#ghim-một-page-vào-profile). > [accordion] Ảnh: tải file lên hoặc truyền URL; video dùng URL > > Gửi ảnh dưới dạng file nhị phân (`photos[]=@file`) hoặc link công khai (`photo_urls[]`), xem [Đăng ảnh bằng URL](#đăng-ảnh-bằng-url). Với video, truyền một `video_url` công khai. > [accordion] Reddit cần tiêu đề > > Bài đăng Reddit luôn có tiêu đề. Mặc định caption được dùng làm tiêu đề; truyền `reddit_title` nếu muốn đặt tiêu đề riêng. ## Đọc response Lệnh đăng trả về `status` của bài, các nền tảng đã nhắm tới, và các id để bạn poll: ```json theme={null} { "status": "processing", "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["instagram", "facebook", "tiktok"], "request_id": "req_01HZX9H6S8T0V2W4X6Y8Z0A2B4", "job_id": null, "results": null } ``` * `completed`: mọi nền tảng đã đăng bài, `results` chứa kết quả theo từng nền tảng. Ở response của lệnh đăng, trạng thái này nghĩa là tất cả đã trả lời ngay. * `processing`: đã được tiếp nhận và đang được đăng; đây là câu trả lời đầu tiên thông thường với mọi loại bài. Poll bằng `request_id`, hoặc đăng ký [webhooks](/vi/concepts/webhooks). * `scheduled`: đã lên lịch cho thời điểm tương lai, poll bằng `job_id`. `job_id` chỉ có ở bài gửi để đăng sau: đó là id dùng để [hủy hoặc dời bài](/vi/concepts/scheduling#quản-lý-bài-đã-lên-lịch). * `partial`: một số nền tảng đã đăng thành công và ít nhất một nền tảng bị từ chối, kiểm tra từng mục trong `results`. * `draft`: không nền tảng nào từ chối, nhưng ít nhất một nền tảng nhận bài vào mục nháp của tài khoản thay vì đăng, nên ở đó bài chưa hiện công khai. Thường gặp nhất là TikTok: khi tài khoản chạm giới hạn đăng trực tiếp trong ngày của TikTok, bài vào hộp thư của ứng dụng TikTok và chờ chủ tài khoản bấm đăng. Mục của nền tảng đó trong `results` có `"draft": true`, `success: true` và không có `url`. Đây là kết quả cuối: **đừng** gửi lại, gửi lại chỉ thêm một bản nháp nữa. * `failed`: tất cả nền tảng nhắm tới đều từ chối bài đăng. Mã HTTP vẫn là `200`, lý do theo từng nền tảng nằm trong `results`. * `canceled`: bài hẹn giờ bạn đã [hủy](/vi/concepts/scheduling#quản-lý-bài-đã-lên-lịch) trước khi nó chạy. Không có gì được đăng, `results` là `null`, và trạng thái này không đổi nữa. Lệnh đăng không bao giờ trả trạng thái này; lệnh tra trạng thái và lịch sử bài đăng thì có. * `unknown`: chưa xác định được kết quả trong thời gian chờ (trả về HTTP `502` với mã `upstream_timeout`). Bài có thể đã lên, **đừng** retry ngay. Câu trả lời có kèm `request_id`: tra trạng thái bằng mã đó sẽ nhận `unknown` cho tới khi phía nền tảng nhận được bài, sau đó là `processing` rồi kết quả. Xem [Lỗi](/vi/concepts/errors) và [Idempotency](/vi/concepts/idempotency#khi-kết-quả-chưa-xác-định). Poll trạng thái (`GET /v1/posts/status`) dùng cùng bộ giá trị này. > [!NOTE] > > **Response đầu tiên thường là `processing`, với bài chữ và bài ảnh cũng như video.** Lệnh đăng > thường trả lời `processing` kèm `request_id`, và kết quả lấy từ poll trạng thái hoặc > [webhook](/vi/concepts/webhooks). Có hai trường hợp đã là kết quả cuối ngay ở response đầu: > > * `failed`: mọi nền tảng bạn chọn đều từ chối bài. Không có gì được đăng, lượt đăng > không bị tính, và bạn có thể gửi lại cùng `Idempotency-Key` sau khi sửa nội dung. Response vẫn > có thể kèm `request_id`. > * `scheduled`: bài đã được tiếp nhận cho thời điểm sau (`scheduled_at` hoặc `add_to_queue`). > Response đó chưa phải kết luận nào; thường nó mang cả `job_id` lẫn `request_id`. Khi có `results`, nó được phân theo từng nền tảng. Mỗi mục có `success`, kèm `publish_id` và `url` khi bài đã lên, hoặc `error` và `error_code` khi nền tảng từ chối. Mục có `"draft": true` là bài đã vào mục nháp của tài khoản và không có `url` (xem `draft` ở trên): ```json theme={null} { "results": { "instagram": { "success": true, "publish_id": "17912345678901234", "url": "https://instagram.com/p/abc123" }, "tiktok": { "success": false, "error": "You have reached the daily posting cap on tiktok. Used 15/15 in the last 24 hours. · Tạm dịch: Bạn đã đạt giới hạn đăng bài trong ngày trên TikTok. Đã dùng 15/15 trong 24 giờ qua.", "error_code": "daily_cap_reached" } } } ``` `error` là câu của chính nền tảng. Nếu MADIAD Hub có bản dịch tiếng Việt cho câu đó, bản dịch nằm sau `· Tạm dịch:`, ở response của lệnh đăng lẫn khi poll trạng thái, cùng dạng với `error.message` (xem [Lỗi](/vi/concepts/errors)). `error_code` là mã lý do ngắn gọn dành cho máy đọc (ví dụ `account_restricted`, `daily_cap_reached`, `media_invalid_format`), hãy phân nhánh theo mã này thay vì theo câu chữ của `error`. Mã chỉ xuất hiện khi nền tảng có trả về. Response của lệnh đăng và kết quả poll trạng thái dùng đúng cùng một cấu trúc này, trừ một trường: `truncated` (những gì đã được cắt cho vừa, xem mục Giới hạn ký tự ở trên) chỉ có trong response của lệnh đăng. Khi một số (nhưng không phải tất cả) nền tảng thất bại, `status` cuối cùng là `partial`. Ở response của lệnh đăng, mảng `failed_platforms` liệt kê các nền tảng được báo là thất bại cho đến lúc này, thường đi kèm `processing`; poll trạng thái sau đó báo `partial`, với từng lần từ chối nằm trong `results`. > [!TIP] > > Một bài đăng có thể thành công một phần, khi một nền tảng xuất bản được còn nền tảng khác thất bại. Hãy kiểm tra `results` theo từng nền tảng thay vì chỉ dựa vào `status` ở cấp cao nhất. ### Cảnh báo: bài đã lên nhưng chưa đúng hẳn như yêu cầu Có lúc nền tảng nhận bài nhưng bỏ qua một phần bạn gửi. Ví dụ: bài ảnh TikTok có chọn nhạc bằng `tiktok_music_id`, mà kết nối TikTok của Profile đó chưa có quyền dùng nhạc, thì bài vẫn lên (hoặc được lưu nháp, với `post_mode: MEDIA_UPLOAD`) nhưng **không có nhạc**. `status` vẫn giữ nguyên như khi không có cảnh báo, nên chỉ mảng `warnings` cho bạn biết chuyện này: ```json theme={null} { "status": "completed", "results": { "tiktok": { "success": true, "url": "https://www.tiktok.com/@shop/photo/7401" } }, "warnings": [ { "platform": "tiktok", "code": "music_not_applied", "message": "Bài được nhận nhưng KHÔNG gắn nhạc: …" } ] } ``` * `warnings` chỉ xuất hiện khi có ít nhất một cảnh báo. Bài không có cảnh báo thì không có khoá `warnings`. * `code` là `music_not_applied` (chỉ TikTok: kết nối lại TikTok cho Profile đó), `first_comment_skipped` (`first_comment` không được đăng, ví dụ vì bài được lưu nháp) hoặc `platform_warning` (lưu ý khác, đọc `message`). Có thể có thêm mã mới, nên gặp mã lạ thì xử lý như `platform_warning`. * `platform` là `null` khi lệnh đăng nhiều kênh trả về cảnh báo mà không nêu kênh nào. * Cảnh báo không có nghĩa là bài lỗi: **đừng** đăng lại bài chỉ vì một cảnh báo. * Với bài nhận `processing`, cảnh báo đến cùng kết quả poll trạng thái, không có ở response đầu tiên của lệnh đăng. Cảnh báo chỉ trả kèm câu trả lời đầu tiên của bài thì chỉ có ở response của lệnh đăng, nên hãy lưu lại từ đó. ### Video: trả lời ngay, đăng ở chế độ nền Một video có thể khiến nền tảng xử lý cả phút hoặc lâu hơn. Mặc định, lệnh đăng chờ nền tảng một lúc, và bài nào chậm sẽ kết thúc bằng `502 upstream_timeout` (`unknown`). Gửi `"async_upload": true` trong `POST /v1/posts/video` thì lệnh trả lời trong vài giây: luôn là `processing` kèm `request_id`, không bao giờ kèm `results` của từng nền tảng. Kết quả lấy từ lệnh tra trạng thái hoặc [webhook](/vi/concepts/webhooks). Lỗi của chính request (đường dẫn không phải video, thiếu tiêu đề YouTube) vẫn được trả ngay. `request_id` luôn do MADIAD Hub đặt. Giá trị bạn gửi trong body sẽ bị bỏ qua. ## Kiểm tra trạng thái sau Poll một bài bất đồng bộ hoặc đã lên lịch bằng `request_id` hoặc `job_id` từ response của lệnh đăng: ```bash theme={null} curl "https://api.madiad.com/v1/posts/status?request_id=req_01HZX9H6S8T0V2W4X6Y8Z0A2B4" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Để nhận cập nhật chủ động thay vì kiểm tra định kỳ, xem [Webhooks](/vi/concepts/webhooks). ## Bài tài liệu (LinkedIn) > [!NOTE] > > **Kênh áp dụng:** chỉ LinkedIn. Định dạng: PDF, PPT, PPTX, DOC, DOCX. LinkedIn hiển thị PDF, PPT, PPTX, DOC, DOCX thành một tài liệu lật được ngay trong bảng tin. Đây là chỗ duy nhất trên các kênh đang kết nối mà một tệp nhiều trang là nội dung thật sự, không phải cái link. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/document \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "platforms": ["linkedin"], "document_url": "https://.../ho-so-nang-luc.pdf", "caption": "Hồ sơ năng lực 2026", "description": "Mười trang, không cần tải về." }' ``` > [!NOTE] > > Cách đặt tên trường ở đây đọc hơi lạ lần đầu: **`caption` là TIÊU ĐỀ tài > liệu** hiện trên thẻ, còn `description` là đoạn chữ phía trên. Cả hai đều theo cách LinkedIn hiển > thị. `caption` là bắt buộc. > > Chỉ LinkedIn nhận loại bài này, nên `platforms` phải đúng `["linkedin"]`. Tối đa 100 MB, 300 trang. ## Đăng lại phần bị lỗi > [!NOTE] > > **Kênh áp dụng:** mọi kênh. Retry chỉ gửi lại đúng những kênh đã lỗi trong bài đó. Khi một bài đi nhiều kênh mà chỉ vài kênh lỗi, gọi retry để gửi lại **đúng những kênh đã lỗi**. Tệp không phải tải lên lần nữa và **không bị tính thêm lượt đăng**: lượt đó đã trừ khi bài được nhận lần đầu, còn một lần giao không thành thì không phải một lần giao mới. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/retry \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request_id": "'$REQUEST_ID'" }' ``` Truyền đúng một trong `request_id` hoặc `job_id`. Phản hồi có mảng `legs`, vì một bài bị tách caption là hai việc ở tầng dưới, và cả hai đều được thử lại. Kênh nào đã thành công trả về "không có gì để thử lại"; đó là câu trả lời bình thường, không phải lỗi. **Đừng** gửi lại một bài lỗi một phần bằng cách gọi lại endpoint đăng bài với cùng `platforms`: đó là một bài mới, và bài sẽ lên lần thứ hai ở mọi nền tảng đã đăng thành công. Trên dashboard, nút **Gửi lại kênh lỗi** cạnh bài ở trang **Tổng quan** làm đúng việc của lệnh gọi này; trang đó chỉ hiện các hoạt động gần nhất. ## Gỡ bài đã đăng | Kênh | Gỡ được | | - | :-: | | Facebook · YouTube · X · LinkedIn · Pinterest · Google Business | ✅ | | Instagram · TikTok | ❌ nền tảng không mở endpoint xoá | | Threads | ❌ gỡ trong ứng dụng Threads | | Reddit · Bluesky · kênh credential | ❌ chưa hỗ trợ | ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/unpublish \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "platform": "facebook", "post_id": "'$PLATFORM_POST_ID'" }' ``` `post_id` là id **của nền tảng**, không phải `request_id` của chúng tôi. Nhờ vậy gỡ được cả bài bạn đã đăng bằng tay, không qua Hub. Đó là `publish_id` trong kết quả của bài. Với Google Business, id là tên đầy đủ của bài, `accounts/…/locations/…/localPosts/…`; chỉ gửi dãy số thì trả `400`. Phản hồi có trường `removal`, cho biết Hub biết chắc đến đâu: | `removal` | Nghĩa | | - | - | | `confirmed` | Nền tảng báo bài đã được gỡ. | | `unverified` | Nền tảng đã nhận lệnh gỡ. Hub không kiểm lại được là bài đã mất. | Chưa kiểm được: `x`, `linkedin`. Hai kênh này nhận lệnh gỡ cả khi id bài không tồn tại, nên `200` ở đây chưa chứng minh bài đã mất: hãy mở bài để xem. Ở các kênh còn lại, khi chúng tôi đo, id mà nền tảng không biết đều bị từ chối (`400` ở Facebook, `404 not_found` ở YouTube, Pinterest và Google Business). `confirmed` là lời báo của chính nền tảng. Hub không tự đọc lại bài. Bài Google Business chỉ được gỡ khi nằm ở một địa điểm của chính Profile đó; địa điểm khác trả `404 not_found`. > [!WARNING] > > Gỡ là **không hoàn tác được** trên nền tảng. Chúng tôi không đăng lại giúp bạn. > > Hỗ trợ: `facebook`, `youtube`, `x`, `linkedin`, `pinterest`, `google_business`. **Instagram và TikTok không có endpoint xoá**, > và bài **Threads** cũng chưa gỡ được qua Hub: bài ở ba kênh này chỉ gỡ được trong ứng dụng. > Gửi ba kênh này trả `400` ngay tại chỗ. # Profiles Source: https://docs.madiad.com/vi/concepts/profiles Mô hình hóa từng thương hiệu, khách hàng hoặc tài khoản con thành một Profile. Một **Profile** là một thương hiệu, khách hàng hoặc tài khoản con mà bạn đăng bài thay mặt, tức một nhóm tài khoản mạng xã hội đã kết nối. Mỗi bài đăng chỉ gửi qua đúng một Profile. Đây là cùng một đối tượng, dù bạn làm việc theo cách nào: * **Trên dashboard**: mỗi thương hiệu hiện ra trong mục **Kết nối** dưới dạng một Profile. Phần lớn khách hàng tạo và kết nối Profile tại đây, không cần code. * **Qua API**: bạn tham chiếu chính Profile đó bằng `profile_id` (`9f2b4c17-…`) trong mỗi lần đăng bài. Một API key quản lý tất cả Profile của bạn, không cần key riêng cho từng thương hiệu: ``` API key của bạn ├── Profile: Acme Coffee → Instagram, Facebook (3 Page), TikTok ├── Profile: Acme Tea → Instagram, X └── Profile: Khách hàng — Nova → LinkedIn, YouTube ``` > [!NOTE] > > Một Profile chứa được **nhiều Facebook Page**, bạn không cần tạo Profile riêng cho từng > Page. Sau khi kết nối, dashboard liệt kê đủ các Page của tài khoản Facebook đó và mỗi bài > đăng chọn một Page bằng `facebook_page_id` (tự chọn nếu Profile chỉ có một Page). Muốn đăng > cùng nội dung lên nhiều Page thì gửi nhiều bài, mỗi bài một Page. Xem mục **Tìm ID của Page > & board** bên dưới. Có một ngoại lệ: nếu một Page được **ghim** vào Profile thì ghim thắng > `facebook_page_id`, xem mục **Ghim một Page vào Profile** bên dưới. ## Số Profile theo từng gói Mỗi gói bao gồm một số lượng Profile cố định: | Gói | Số Profile | | - | - | | Starter | 1 | | Growth | 4 | | Business | 10 | | Custom | Không giới hạn | Khi đạt giới hạn của gói, nâng cấp để có thêm Profile. ## Tạo Profile ### Trên dashboard Vào **Kết nối → Kết nối tài khoản** và xác thực từng nền tảng bạn muốn đăng lên. Profile sẽ được tạo tự động, bạn không cần tự quản lý `profile_id`. ### Qua API Khi bắt đầu một kết nối, hệ thống sẽ tạo Profile và trả về URL kết nối (đã khoác áo thương hiệu, có thời hạn) trong cùng một lần gọi: ```bash theme={null} curl -X POST https://api.madiad.com/v1/connections/start \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "friendly_name": "Acme Coffee" }' ``` ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "friendly_name": "Acme Coffee", "access_url": "https://connect.madiad.com/o/abc123def456", "expires_in": "48h" } ``` Sử dụng `profile_id` (`9f2b4c17-…`) được trả về mỗi khi bạn đăng bài. ### Bỏ qua bước chọn nền tảng `POST /v1/connections/start` trả về liên kết tới trang để người dùng tự chọn nền tảng muốn kết nối. Nếu bạn muốn tự hiển thị lựa chọn đó trong giao diện của mình, hãy yêu cầu đúng một nền tảng và nhận về luôn URL cấp quyền của chính nền tảng đó: ```bash theme={null} curl -X POST https://api.madiad.com/v1/connections/start-platform \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platform": "instagram" }' ``` ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platform": "instagram", "authorize_url": "https://www.instagram.com/oauth/authorize?...", "expires_in": 900 } ``` Giống `/v1/connections/start`, endpoint này tự tạo Profile khi bạn bỏ trống `profile_id`, và nhận thêm `friendly_name` tuỳ chọn. > [!WARNING] > > `authorize_url` mang một state **dùng một lần**, hết hạn sau khoảng 15 phút (`expires_in`, tính > bằng giây). Hãy chuyển hướng người dùng tới đó ngay, tuyệt đối không tạo sẵn rồi lưu lại, khác > với liên kết 48 giờ của `/v1/connections/start`. Mọi nền tảng đều khởi tạo được theo cách này, trừ **Bluesky**: nó xác thực bằng app password chứ không phải OAuth nên sẽ trả về `400 platform_not_startable`. Với nền tảng đó hãy dùng liên kết kết nối chung. ## Liệt kê Profile qua API Có hai endpoint cùng trả về Profile. Dùng cái nào là tùy bạn có cần biết mỗi Profile đang kết nối những gì hay không. ### Chỉ lấy danh sách `GET /v1/profiles` trả về mọi Profile trên tài khoản, không kèm gì thêm: ```bash theme={null} curl https://api.madiad.com/v1/profiles \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ```json theme={null} { "profiles": [ { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "friendly_name": "Acme Coffee" }, { "profile_id": "3d81a06f-5b24-4e97-9c1a-7f0d2e8b6a45", "friendly_name": "Acme Tea" } ] } ``` Mỗi phần tử chỉ có đúng hai trường là `profile_id` và `friendly_name`. Danh sách sắp theo thứ tự tạo, cũ trước. Tài khoản được lấy từ chính API key nên endpoint này không nhận tham số nào và không bao giờ trả về Profile của tài khoản khác. Đây là một lệnh `GET`: nó không tiêu tốn lượt đăng hay phút [xử lý video](/vi/concepts/ffmpeg), chỉ tính vào [rate limit](/vi/concepts/rate-limits) theo phút. Hãy dùng endpoint này để đổ dữ liệu cho một ô chọn Profile, hoặc bất cứ khi nào bạn chỉ cần `profile_id` mà mọi lệnh đăng bài, kết nối và phân tích đều đòi. Nó chỉ đọc dữ liệu của chính MADIAD Hub nên trả lời nhanh, và vẫn trả lời được cả khi một nền tảng mạng xã hội đang không truy cập được. ### Danh sách kèm chi tiết kết nối `GET /v1/connections/status`, gọi mà **bỏ trống `profile_id`**, trả về mọi Profile cùng những nền tảng từng Profile đã nối: ```bash theme={null} curl "https://api.madiad.com/v1/connections/status" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Mỗi phần tử của mảng `profiles` có `profile_id` và `friendly_name` như trên, kèm thêm `connected` và `accounts` (xem chi tiết ở [Kiểm tra các nền tảng đã kết nối](#kiểm-tra-các-nền-tảng-đã-kết-nối) bên dưới). Để dựng được phần chi tiết đó, lệnh phải tra cứu trực tiếp các tài khoản đã nối, nên nó chậm hơn `GET /v1/profiles` và có thể báo lỗi trong lúc việc tra cứu ấy không sẵn sàng. Khi chỉ cần ID thì hãy gọi `GET /v1/profiles`. Muốn biết **còn bao nhiêu slot Profile** trong gói thì gọi [`GET /v1/usage`](/vi/concepts/usage): trường `profiles` trả về `used` / `limit` / `remaining`. ## Kết nối tài khoản mạng xã hội Mỗi Profile bắt đầu ở trạng thái trống. Mở trang kết nối trong trình duyệt và đăng nhập vào từng nền tảng bạn muốn đăng nội dung. MADIAD Hub lưu trữ thông tin ủy quyền, vì vậy bạn không cần tự xử lý token của các nền tảng. **1. Mở trang kết nối** Trên dashboard, nhấn **Kết nối tài khoản**. Qua API, gửi cho người dùng (hoặc chính bạn) `access_url` được trả về từ `/v1/connections/start`. **2. Ủy quyền từng nền tảng** Đăng nhập vào Instagram, Facebook, TikTok và các nền tảng khác. Các kết nối sẽ tồn tại cho đến khi bị thu hồi. **3. Đăng bài** Tham chiếu Profile bằng `profile_id` trong mọi request đăng bài. ## Các nền tảng được hỗ trợ Một Profile có thể kết nối với bất kỳ kênh nào trong 22 kênh liệt kê ở [Kênh](/vi/concepts/channels). Trang đó cũng ghi rõ mỗi kênh nhận loại bài nào. Bài đăng chỉ thành công trên các nền tảng mà Profile đích đã thực sự kết nối. Nếu liệt kê một nền tảng chưa được kết nối, hệ thống sẽ trả về lỗi cho nền tảng đó. ### Kết nối kênh nhập thủ công Các kênh OAuth được kết nối bằng cách đăng nhập trên trang kết nối. **Mười một kênh hoạt động khác**: Telegram, Discord, Slack, Mastodon, Nostr, Lemmy, dev.to, Hashnode, WordPress, Whop và listmonk không có đăng nhập OAuth, nên thay vào đó bạn liên kết bot, webhook, API key hoặc application password của chính mình bằng `POST /v1/connections/{kênh}`. [Kênh](/vi/concepts/channels) liệt kê đầy đủ trường cần cho cả mười một kênh. Hai ví dụ dưới đây cho thấy dạng chung; các kênh còn lại theo đúng dạng đó. > [accordion] Telegram: dùng bot của bạn > > 1. Nhắn **@BotFather** trên Telegram và gửi `/newbot` để tạo bot. Sao chép **bot token**. > 2. Thêm bot vào kênh hoặc nhóm đích với quyền **quản trị viên (admin)** để bot có thể đăng bài. > 3. Trên dashboard, mở **Kết nối → Telegram**, dán **bot token** và **chat ID** (`@tenkenh` hoặc id dạng số như `-100123456789`), rồi kết nối. > > **Qua API** > > ```bash theme={null} > curl -X POST https://api.madiad.com/v1/connections/telegram \ > -H "Authorization: Bearer $MADIAD_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", > "bot_token": "123456:ABC-DEF...", > "chat_id": "@tenkenh", > "name": "Thông báo" > }' > ``` > > Các field bắt buộc: `profile_id`, `bot_token`, `chat_id`. `name` là nhãn tùy chọn. Bot phải có quyền quản trị viên trong kênh hoặc nhóm đích. `chat_id` là `@tenkenh` hoặc id dạng số như `-100123456789`. Trả về 400 nếu thiếu bất kỳ field bắt buộc nào. > > ```json theme={null} > { > "profiles": [ > { > "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", > "friendly_name": "Acme Coffee", > "connected": ["telegram"], > "accounts": [ > { "platform": "telegram", "display_name": "Thông báo", "username": null, "avatar": null } > ] > } > ] > } > ``` > [accordion] Discord: webhook của kênh > > 1. Trong Discord, vào **Cài đặt máy chủ → Tích hợp → Webhook → Tạo webhook**, chọn kênh muốn đăng, rồi bấm **Sao chép URL webhook**. > 2. Trên dashboard, mở **Kết nối → Discord**, dán **URL webhook**, rồi kết nối. > > **Qua API** > > ```bash theme={null} > curl -X POST https://api.madiad.com/v1/connections/discord \ > -H "Authorization: Bearer $MADIAD_API_KEY" \ > -H "Content-Type: application/json" \ > -d '{ > "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", > "webhook_url": "https://discord.com/api/webhooks/...", > "name": "Kênh marketing" > }' > ``` > > Các field bắt buộc: `profile_id`, `webhook_url`. `name` là nhãn tùy chọn. Trả về 400 nếu thiếu bất kỳ field bắt buộc nào. > > ```json theme={null} > { > "profiles": [ > { > "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", > "friendly_name": "Acme Coffee", > "connected": ["discord"], > "accounts": [ > { "platform": "discord", "display_name": "Kênh marketing", "username": null, "avatar": null } > ] > } > ] > } > ``` > [!NOTE] > > Các kênh nhập thủ công **không** nằm trong [Analytics](/vi/concepts/analytics): số liệu người theo dõi, phạm vi tiếp cận và mức độ tương tác không có sẵn cho kênh nào trong nhóm này, Google Business Profile cũng vậy. > [!NOTE] > > **Lượt đăng không giới hạn trên mọi gói trả phí.** [Xử lý video](/vi/concepts/ffmpeg) vẫn có hạn mức ở nơi nó được bật, và hạn mức đó áp dụng **cho cả tài khoản**: mọi thương hiệu dùng chung một hạn mức hàng tháng, nên thương hiệu nào bận rộn có thể "mượn" phần của thương hiệu ít hoạt động hơn. Số Profile mới là con số cần cân nhắc khi hoạch định. Xem [Mức sử dụng](/vi/concepts/usage). ## Tìm ID của Page & board Một số nền tảng yêu cầu ID bạn cần tra cứu trước khi đăng. Dùng các endpoint sau để lấy ID đó cho một Profile. ### Facebook Pages Nếu một Profile có nhiều hơn một Facebook Page đã kết nối, truyền `facebook_page_id` trong request đăng bài để chọn Page muốn đăng. Liệt kê các Page khả dụng bằng: ```bash theme={null} curl "https://api.madiad.com/v1/connections/facebook/pages?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "pages": [ { "id": "1029384756102938", "name": "Acme Coffee", "picture": "https://..." } ] } ``` Mỗi page có `id`, `name`, và `picture` (URL ảnh đại diện, có thể là `null`). Truyền `id` đã chọn vào `facebook_page_id` khi đăng bài. Nếu Profile chỉ có đúng một Page thì tự động được chọn. Endpoint này chỉ cần dùng khi có nhiều Page. Trả về 400 nếu thiếu `profile_id`, 404 nếu Profile không thuộc về bạn. ### LinkedIn Pages LinkedIn đăng bài dưới danh nghĩa người dùng đã kết nối, hoặc dưới danh nghĩa một trang tổ chức mà người đó quản trị. Muốn đăng dưới danh nghĩa tổ chức thì truyền ID của trang đó vào `target_linkedin_page_id` (xem [Tùy chọn theo nền tảng](/vi/concepts/platform-options)). Liệt kê các tổ chức khả dụng của một Profile bằng: ```bash theme={null} curl "https://api.madiad.com/v1/connections/linkedin/pages?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "success": true, "pages": [ { "id": "urn:li:organization:12345678", "name": "Acme Coffee", "picture": "https://...", "vanityName": "acme-coffee", "followers": 1280 } ] } ``` `id` ở đây là URN đầy đủ dạng `urn:li:organization:…`, không phải một dãy số trần, nên hãy truyền nguyên văn vào `target_linkedin_page_id`. Bỏ trống trường đó thì bài đi dưới danh nghĩa cá nhân đã kết nối. Chỉ `id` và `name` là phần cam kết của API, các trường còn lại là thông tin phụ và có thể đổi. Trả về `409 platform_not_connected` nếu Profile chưa nối tài khoản LinkedIn, và `404 not_found` nếu Profile không thuộc về bạn. ### Pinterest boards Pinterest bắt buộc phải có board ID cho mỗi lần ghim. Liệt kê các board khả dụng của một Profile bằng: ```bash theme={null} curl "https://api.madiad.com/v1/connections/pinterest/boards?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "boards": [ { "id": "987654321012345678", "name": "Seasonal Menu" } ] } ``` Mỗi board có `id` và `name`. Truyền `id` đã chọn vào `pinterest_board_id` khi đăng bài. Field này luôn bắt buộc với Pinterest. Trả về 400 nếu thiếu `profile_id`, 404 nếu Profile không thuộc về bạn. ## Ghim một Page vào Profile Nối Facebook là nối một **tài khoản**, mà một tài khoản có thể quản trị nhiều Page; LinkedIn cũng vậy với các trang tổ chức, và Google Business với các địa điểm. Ghim một Page (hoặc địa điểm) vào Profile là cố định đích đến: mọi bài từ Profile đó đều đi tới đó, không còn chuyện rơi về Page mà tài khoản đang lấy làm mặc định. > [!WARNING] > > **Page đã ghim đè lên ID Page bạn gửi kèm bài đăng.** Khi một Page đang được ghim vào Profile, > trường `facebook_page_id` trong request đăng bài bị bỏ qua và bài vẫn đi tới Page đã ghim. > Request vẫn báo thành công, nên không có gì trong phản hồi cho bạn biết đích đến đã đổi. LinkedIn > với `target_linkedin_page_id`, và Google Business với `gbp_location_id`, cũng y như vậy. Để việc > đó không xảy ra trong im lặng, bài nêu một trang hay địa điểm *khác* với ghim sẽ bị từ chối với > `409 page_pin_conflict`. > > Vì thế chỉ nên ghim trên Profile phục vụ **một** thương hiệu. Với Profile bạn cố ý dùng để đăng > lên nhiều Page, hãy để trống ghim và chỉ định từng bài bằng `facebook_page_id`, hoặc bỏ ghim > trước đã. Bạn cũng đặt và bỏ ghim được ngay trên dashboard, trong mục **Kết nối**. Mọi lệnh dưới đây dùng chung cho `facebook`, `linkedin` và `google_business`, chỉ cần đổi tên nền tảng trong đường dẫn. ### Xem Page đang ghim ```bash theme={null} curl "https://api.madiad.com/v1/connections/facebook/page?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "success": true, "pages": [ { "id": "1029384756102938", "name": "Acme Coffee", "picture": "https://..." } ], "selected_page_id": "1029384756102938", "selected_page_name": "Acme Coffee" } ``` `pages` là toàn bộ Page mà tài khoản đã nối có thể đăng lên. `selected_page_id` và `selected_page_name` chính là ghim hiện tại, cả hai bằng `null` khi chưa ghim gì. Ở biến thể LinkedIn, các `id` là URN dạng `urn:li:organization:…`. ### Đặt ghim ```bash theme={null} curl -X POST https://api.madiad.com/v1/connections/facebook/page \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "page_id": "1029384756102938" }' ``` Cả `profile_id` và `page_id` đều bắt buộc, thiếu `page_id` sẽ nhận `400 invalid_request`. `page_id` phải là một Page mà tài khoản đã nối có quyền quản trị, hãy chọn từ danh sách `pages` ở trên. Phản hồi trả về đúng đối tượng như lệnh đọc, với `selected_page_id` đã được đặt. Ghim là thuộc tính của Profile, được đọc vào lúc bài thực sự lên sóng chứ không phải lúc bạn tạo bài, nên nó chi phối cả những bài bạn đã lên lịch từ trước. ### Bỏ ghim ```bash theme={null} curl -X DELETE https://api.madiad.com/v1/connections/facebook/page \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" }' ``` Bỏ ghim xong thì `facebook_page_id` của từng bài lại là thứ quyết định đích đến. ### Địa điểm Google Business `google_business` ghim một **địa điểm** chứ không phải một trang. Lệnh đọc trả về các trường địa điểm thay cho `pages`: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "success": true, "locations": [ { "name": "locations/15783951491896349152", "title": "Acme Coffee Quận 1" } ], "selected_location_id": "locations/15783951491896349152", "selected_location_name": "Acme Coffee Quận 1" } ``` Để đặt ghim, gửi `name` của địa điểm vào `page_id` (hoặc `location_id`). Khi tài khoản Google đã nối quản nhiều địa điểm, mỗi bài phải đi tới địa điểm đã ghim hoặc kèm `gbp_location_id`; nếu chỉ có một địa điểm thì không cần gì. Địa điểm đã ghim cũng là địa điểm mặc định của Profile cho phần đánh giá và phân tích. ## Kiểm tra các nền tảng đã kết nối Để xem một Profile hiện đang kết nối những nền tảng nào, gọi: ```bash theme={null} curl "https://api.madiad.com/v1/connections/status?profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ```json theme={null} { "profiles": [ { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "friendly_name": "Acme Coffee", "connected": ["instagram", "facebook", "tiktok"], "accounts": [ { "platform": "instagram", "display_name": "Acme Coffee", "username": "acmecoffee" } ] } ] } ``` `connected` liệt kê slug các nền tảng đang được kết nối vào Profile, còn `accounts` kèm tên/handle tài khoản đã kết nối theo từng nền tảng. Bỏ `profile_id` để lấy trạng thái của **tất cả** Profile trong tài khoản. Đây là cách kiểm tra trạng thái theo yêu cầu; muốn được **thông báo** ngay khi một kết nối thay đổi, hãy đăng ký [webhook](/vi/concepts/webhooks) `connection.connected` / `connection.disconnected`. ## Khi nào nên dùng nhiều Profile * **Agency**: một Profile cho mỗi khách hàng. * **Nhóm đa thương hiệu**: một Profile cho mỗi thương hiệu. * **Môi trường**: các Profile riêng biệt cho đối tượng thử nghiệm và sản xuất. Khi **không** cần thêm Profile: nhiều Facebook Page của cùng một thương hiệu, hoặc nhiều tài khoản khác nền tảng của cùng một thương hiệu. Tất cả nằm gọn trong một Profile. # Vì sao bài không đăng được Source: https://docs.madiad.com/vi/concepts/publish-failures Danh mục các thông báo lỗi thật khi đăng bài, vì sao nó xảy ra và cách xử lý từng loại. Trang này liệt kê những câu báo lỗi bạn thực sự nhìn thấy khi một bài không lên được, kèm nguyên nhân và cách xử lý. Nội dung lấy từ nhật ký đăng bài thật của hệ thống, không phải câu hỏi tự nghĩ ra. Cần phân biệt hai thứ: * **Trang này** nói về việc **một bài cụ thể không đăng được**, thường do nền tảng đích từ chối. * [Bảng mã lỗi](/vi/concepts/errors) nói về **mã lỗi API** (`401`, `429`, `platform_not_connected`...) mà code của bạn nhận được. Nếu bạn đang viết tích hợp thì đọc trang đó. > [!NOTE] > > Câu báo lỗi hiển thị nguyên văn từ nền tảng đích, thường bằng tiếng Anh. Khi Hub có bản dịch > tiếng Việt, API thêm bản dịch đó sau `· Tạm dịch:`, còn màn hình **Đăng bài** hiển thị bản dịch > thay cho câu tiếng Anh. Tìm nhanh: bấm Ctrl+F (hoặc Cmd+F) rồi dán vài chữ trong câu lỗi của bạn. ## Profile chưa nối tài khoản của nền tảng đó **Bạn thấy:** `None of the requested platforms are valid for profile ... has no Instagram account configured` Đây là nguyên nhân phổ biến nhất, bỏ xa các loại còn lại. **Vì sao:** bài được gửi tới một nền tảng mà Profile đó chưa nối tài khoản, hoặc đã nối nhưng kết nối bị mất hiệu lực. Kết nối rụng là chuyện bình thường: bạn đổi mật khẩu, thu hồi quyền, nền tảng yêu cầu xác thực lại, hoặc chính nền tảng huỷ quyền sau một thời gian. **Cách xử lý:** 1. Mở **Kết nối** trên dashboard và chọn đúng Profile đang dùng. Một tài khoản có nhiều Profile thì rất dễ gửi bài bằng Profile khác với Profile đã nối. 2. Nếu nền tảng đó không xuất hiện trong danh sách đã nối, hãy nối lại và cấp quyền. 3. Nếu vừa nối lại mà vẫn hỏng, xem mục [Nền tảng đang hạn chế tài khoản](#nền-tảng-đang-hạn-chế-tài-khoản) bên dưới. Nối lại không gỡ được trường hợp đó. > [!WARNING] > > Gửi lại y nguyên request sẽ hỏng y như cũ. Phải nối tài khoản hoặc bỏ nền tảng đó khỏi `platforms` > trước khi thử lại. ## Nền tảng đang hạn chế tài khoản **Bạn thấy:** * `Instagram: Action suspected as spam. Activity is restricted.` * `You have reached the daily posting cap on tiktok. Used 15/15 in the last 24 hours.` **Vì sao:** nền tảng đang giới hạn chính tài khoản của bạn, không phải Hub gặp sự cố. Instagram hạ tốc độ khi thấy hành vi giống spam (đăng dày, nội dung lặp, tài khoản mới). TikTok có trần số bài trong 24 giờ. **Cách xử lý:** giãn nhịp đăng, đa dạng nội dung, và với trần theo ngày thì dùng [lên lịch](/vi/concepts/scheduling) để rải sang hôm sau. Không có cách nào từ phía Hub gỡ được giới hạn này, và nối lại tài khoản cũng không giúp gì. ## File nguồn không hợp lệ **Bạn thấy:** * `Google Drive URL does not point to a video file. Content-Type: audio/mp4` * `Google Drive URL does not point to a video file. Content-Type: text/html` * `Video URL is not accessible` **Vì sao:** Hub tải file từ link bạn gửi. Link chia sẻ của Google Drive thường trả về **trang xem trước** (`text/html`) chứ không phải file, hoặc trả về luồng chỉ có tiếng (`audio/mp4`) khi file gốc không đúng định dạng video. Link riêng tư thì Hub cũng không mở được. **Cách xử lý:** 1. Đặt quyền chia sẻ thành **bất kỳ ai có đường liên kết đều xem được**. 2. Dùng link tải trực tiếp, không dùng link xem trước. 3. Tự kiểm nhanh trước khi gửi: mở link ở cửa sổ ẩn danh, nếu ra trang xem trước thì Hub cũng sẽ nhận đúng trang đó. 4. Chắc chắn nhất là để file trên một nơi trả thẳng file video (S3, R2, CDN, hosting của bạn). ## Vượt giới hạn của nền tảng **Bạn thấy:** * `X: Tweet creation failed (403): This user is not allowed to post a video longer than 2 minutes.` * `Pinterest description is too long (509 characters). Maximum allowed is 500.` * `Pinterest title is too long (509 characters). Maximum allowed is 100.` * `Tiêu đề gửi cho TikTok dài 784 ký tự, vượt giới hạn 90 của nền tảng này.` * `Facebook title is too long (575 characters). Maximum allowed is 255.` * `Mastodon status is too long (724 characters). This instance allows a maximum of 500 characters.` * `Invalid platforms for photo upload: ['youtube']` **Vì sao:** mỗi nền tảng có giới hạn riêng về độ dài chữ, thời lượng video và loại nội dung được nhận. Caption dùng chung quá dài với một nền tảng thì được cắt cho nền tảng đó trước khi gửi (xem [Giới hạn ký tự](/vi/concepts/posts#giới-hạn-ký-tự)), nên lỗi vượt độ dài thường nằm ở chữ viết riêng cho một nền tảng: `_caption`, `_title`, hoặc một ô "Riêng từng nền tảng" trong trình soạn bài. Chữ đó được gửi đúng như bạn viết. Mastodon là trường hợp hay gặp: 500 chỉ là mức mặc định, mỗi máy chủ tự đặt giới hạn riêng, và máy chủ từ chối chữ vượt mức đó. **Cách xử lý:** rút ngắn chữ bạn đã viết riêng cho nền tảng đó. Với Mastodon, hãy rút ngắn theo con số trong thông báo, hoặc quay về một caption dùng chung, vì caption này được cắt còn 500 cho Mastodon. Tiêu đề hoặc mô tả quá dài có trường riêng trong [tùy chọn theo nền tảng](/vi/concepts/platform-options). Video quá dài với X thì cắt riêng một bản cho X, hoặc bỏ X khỏi lần đăng đó và giữ các nền tảng còn lại. Bài chỉ có ảnh thì không gửi tới YouTube, vì YouTube chỉ nhận video (xem [Kênh](/vi/concepts/channels)). ## Thiếu trường bắt buộc **Bạn thấy:** `Title is required for Youtube. Provide a title.` **Vì sao:** YouTube bắt buộc có tiêu đề riêng và không tự lấy từ nội dung bài. **Cách xử lý:** điền tiêu đề trong phần tùy chọn riêng của YouTube trước khi gửi. Xem [tùy chọn theo nền tảng](/vi/concepts/platform-options) để biết trường nào bắt buộc ở đâu. ## Lỗi tạm thời **Bạn thấy:** * `TikTok photo upload timed out or failed. Last seen status: PROCESSING_DOWNLOAD` * `X API request failed during video upload: 401 Client Error: Unauthorized` * `Request failed (502)` **Vì sao:** nền tảng đích hoặc đường truyền gặp trục trặc nhất thời. Không phải do nội dung của bạn. **Cách xử lý:** thử lại sau ít phút. Nếu gọi qua API, hãy gửi [`Idempotency-Key`](/vi/concepts/idempotency) để lần thử lại không tạo ra bài trùng. Lặp lại nhiều lần thì [liên hệ hỗ trợ](mailto:info@madiad.com) kèm thời điểm và nền tảng. > [!WARNING] > > Riêng `upstream_timeout` thì **kết quả chưa xác định**: bài có thể đã lên. Đừng gửi lại: hãy tra > trạng thái bằng `request_id` trong chính câu trả lời đó, hoặc xem lịch sử đăng, nơi kết quả tự > cập nhật. Chi tiết ở [Bảng mã lỗi](/vi/concepts/errors). ## Một số nền tảng thất bại **Bạn thấy:** từng nền tảng đã từ chối kèm lý do riêng, ví dụ * ở cột **Kết quả** của màn hình **Đăng bài**: `Mastodon: Bài đăng Mastodon quá dài (724 ký tự). Máy chủ Mastodon này cho phép tối đa 500 ký tự.` * qua API, và ở trang **Tổng quan**: `Mastodon: Mastodon status is too long (724 characters). This instance allows a maximum of 500 characters. · Tạm dịch: Bài đăng Mastodon quá dài (724 ký tự). Máy chủ Mastodon này cho phép tối đa 500 ký tự.` * `TikTok: Nền tảng từ chối bài đăng.` khi nền tảng không nêu lý do * `Một số nền tảng thất bại: tiktok, youtube` ở các bài từ trước 01/10/2026 **Vì sao:** một lần đăng lên nhiều nền tảng chỉ hỏng ở vài nơi. Bài đã lên ở những nền tảng còn lại, **đừng gửi lại toàn bộ** vì sẽ tạo bài trùng ở nơi đã thành công. **Cách xử lý:** khung kết quả ở màn hình **Đăng bài** nêu tên từng nền tảng và lý do ngay sau khi bạn đăng, còn cột **Kết quả** dưới **Bài đăng gần đây** lưu lại thông tin đó. Tra đúng mục ở trên ứng với từng lý do, rồi gửi lại CHỈ những nền tảng đã hỏng: bấm **Gửi lại kênh lỗi** cạnh bài ở trang **Tổng quan** (trang này chỉ hiện các hoạt động gần nhất), hoặc gọi [`POST /v1/posts/retry`](/vi/concepts/posts#đăng-lại-phần-bị-lỗi) kèm `request_id` của bài, lấy từ câu trả lời lúc đăng, thư báo lỗi hoặc [`GET /v1/posts/history`](/vi/concepts/analytics#lịch-sử-đăng-bài). Cả hai cách đều không tính thêm lượt đăng. Nếu một luồng tự động (n8n, Make, code của bạn) gửi lại bài bằng cách gọi lại endpoint đăng bài, bài sẽ lên thêm một bản ở mọi nơi bản đầu đã lên. Hãy cho luồng đó gọi retry. Khi **mọi** nền tảng đều từ chối, bài được ghi nhận là "Không đăng được": không có gì được đăng, nên sửa nội dung rồi gửi lại cả bài là an toàn. ## Thư báo bài lỗi Khi có bài không đăng được, Hub gửi thư cho chủ tài khoản, thường mỗi ngày một lần vào khoảng 08:30 giờ Việt Nam. Mỗi dòng trong thư nêu lý do, số bài gặp lỗi đó và giờ từng bài bị lỗi theo giờ Việt Nam (bài đã lên lịch thì là giờ đã lên lịch đăng), kèm `request_id` để gọi retry với bài đã lên được một số nền tảng. Thư có thể đến sau lúc lỗi nhiều giờ, và thư không biết những bài bạn đã tự đăng lại: bài nào bạn đã đăng lại thành công thì bỏ qua dòng của bài đó. Bài đã lên được một số nền tảng thì chỉ gửi lại những nền tảng đã lỗi, như mục [Một số nền tảng thất bại](#một-số-nền-tảng-thất-bại). Bài không lên được nền tảng nào thì sửa lỗi rồi gửi lại cả bài. ## Vẫn chưa rõ Gửi cho chúng tôi thời điểm đăng, Profile, các nền tảng đã chọn và nguyên văn câu báo lỗi, qua [info@madiad.com](mailto:info@madiad.com) hoặc hotline trong mục **Trợ giúp** trên dashboard. Người thật trả lời. # Rate limit Source: https://docs.madiad.com/vi/concepts/rate-limits Số request tối đa mỗi phút bạn có thể gọi, và điều gì xảy ra khi vượt quá. MADIAD Hub áp dụng rate limit theo từng khách hàng. Vì đăng bài không giới hạn trên mọi gói trả phí, với một lệnh đăng thì rate limit là thứ duy nhất bạn có thể chạm phải: * **Rate limit** là tốc độ bạn có thể gọi API, được kiểm tra trên mọi request đã xác thực. * **Các hạn mức còn lại** gồm số Profile và số DM mỗi ngày vẫn giới hạn theo gói; riêng số bài đăng thì không. [Xử lý video](/vi/concepts/ffmpeg) cũng có hạn mức, nhưng cấp theo từng tài khoản chứ không theo gói. Xem [Mức sử dụng](/vi/concepts/usage) để biết số liệu hiện tại. Hai loại giới hạn này độc lập với nhau, bạn có thể còn nguyên hạn mức FFmpeg nhưng vẫn bị rate limit vì gửi quá nhiều request trong thời gian ngắn. ## Giới hạn cụ thể * **Cửa sổ cố định 60 giây**, tính theo từng tài khoản khách hàng (không tính theo từng API key hay từng profile). * **60 request/phút.** Đây là mức tiêu chuẩn, không phải giới hạn tuyệt đối. MADIAD Hub có thể nâng trần này cho tích hợp có lưu lượng lớn, [liên hệ hỗ trợ](mailto:info@madiad.com) nếu bạn cần cao hơn. * **Kết nối kênh nhập thủ công: 10 request/phút**, đếm riêng, không nằm trong 60. `POST /v1/connections/{platform}` gửi một thông tin xác thực tới máy chủ do bạn chỉ định rồi báo có được chấp nhận hay không, nên nó được giữ chậm có chủ đích để không dùng được vào việc dò mật khẩu. Kết nối một kênh là việc làm một lần cho mỗi kênh; dùng bình thường không bao giờ chạm mức này. Xem [Kênh](/vi/concepts/channels). * Đây là **cửa sổ cố định**: bộ đếm được reset ở mỗi mốc 60 giây, nên lượng dùng không cộng dồn sang cửa sổ kế tiếp. ## Khi vượt giới hạn API trả về: ``` HTTP/1.1 429 Too Many Requests Retry-After: 60 ``` ```json theme={null} { "error": { "code": "rate_limited", "message": "Rate limit exceeded (60/min). Slow down and retry shortly." } } ``` Xem [Lỗi](/vi/concepts/errors) để biết đầy đủ bảng mã lỗi. ## Xử lý lỗi 429 * **Tuân theo `Retry-After`.** Header này cho biết chính xác cần chờ bao nhiêu giây trước khi thử lại, đừng gửi lại sớm hơn. * **Backoff theo cấp số nhân** nếu bạn gọi API trong vòng lặp hoặc từ một worker xử lý hàng đợi và liên tục gặp 429, thay vì dồn dập gửi request mỗi khi cửa sổ reset. * **Giãn cách các đợt gửi liên tiếp.** Nếu bạn đang tạo nhiều bài đăng cùng lúc (ví dụ lên lịch hàng loạt từ một script), hãy thêm một khoảng trễ nhỏ giữa các request thay vì thực hiện đồng thời tất cả. > [!TIP] > > Nếu một request tạo dữ liệu mới (như đăng bài) bị rate limit và bạn thử lại, hãy gửi cùng `Idempotency-Key` đã dùng ở lần đầu, xem [Idempotency](/vi/concepts/idempotency). Nhờ vậy, thao tác đó sẽ không bao giờ sinh ra bài đăng trùng lặp. ## Khi bản thân rate limiter gặp sự cố Rate limiter được thiết kế để ưu tiên cho phép truy cập khi gặp sự cố: nếu việc kiểm tra bên dưới gặp lỗi vì bất kỳ lý do gì, MADIAD Hub vẫn cho request đi qua thay vì chặn lại. Sự cố ở phía rate limiter sẽ không bao giờ là lý do khiến lưu lượng hợp lệ của bạn bị chặn. # Đánh giá Google Source: https://docs.madiad.com/vi/concepts/reviews Đọc đánh giá trên Google Business Profile và trả lời công khai bằng chủ tài khoản. Khách tìm doanh nghiệp bạn trên Google hay Maps thì thứ họ đọc đầu tiên là đánh giá. MADIAD Hub đọc được toàn bộ đánh giá của một địa điểm và đăng câu trả lời của chủ, qua đúng kết nối Google mà Profile đang dùng để đăng bài Google Business. Không cần cấp thêm quyền nào. > [!NOTE] > > Cần Profile đã kết nối **Google Business**. Xem [Kết nối kênh](/vi/concepts/channels). ## Liệt kê địa điểm Một tài khoản Google có thể sở hữu nhiều địa điểm. Gọi endpoint này trước để lấy `location_id`. ```bash theme={null} curl "https://api.madiad.com/v1/gbp/locations?profile_id=$PROFILE_ID" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` ## Đọc đánh giá > [!NOTE] > > **Kênh áp dụng:** chỉ Google Business. Đánh giá trên Facebook, TikTok Shop hay sàn thương mại > điện tử không đọc được qua đây. ```bash theme={null} curl "https://api.madiad.com/v1/gbp/reviews?profile_id=$PROFILE_ID&location_id=locations/1578..." \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` | Tham số | Bắt buộc | Ghi chú | | - | - | - | | `profile_id` | Có | Profile của bạn | | `location_id` | Tuỳ | Bỏ trống được nếu Profile chỉ có một địa điểm, hoặc đã chọn địa điểm mặc định lúc kết nối Google Business | | `pageSize` · `pageToken` | Không | Phân trang; `pageToken` lấy từ `nextPageToken` của lần gọi trước | | `orderBy` | Không | Ví dụ `updateTime desc`, `rating desc` | Phản hồi kèm `averageRating` và `totalReviewCount` của địa điểm. Mỗi đánh giá có trường `name` là đường dẫn đầy đủ, và đó chính là thứ bạn truyền vào khi trả lời. > [!WARNING] > > Nếu Profile có **nhiều hơn một** địa điểm mà bạn không truyền `location_id`, Hub dùng địa điểm mặc > định đã chọn lúc kết nối Google Business. Chưa có địa điểm mặc định thì yêu cầu trả về `400` với > `code: "location_required"` kèm danh sách địa điểm: tự chọn giùm bạn một địa điểm nghĩa là có ngày trả lời công khai nhầm cửa hàng. > Trường `location_source` trong phản hồi cho biết đã áp dụng cách nào (`requested`, `only_location` > hoặc `connection_default`), `location_id` là địa điểm đã dùng, còn `locations` liệt kê mọi địa điểm > của Profile (`location_id` + `title`). > > Một `location_id` không thuộc Profile trả về `404`. ## Trả lời một đánh giá > [!NOTE] > > Trả lời đánh giá cần gói **Business** trở lên, cùng nhóm với nhắn tin riêng và AutoDM. Đọc > đánh giá thì mọi gói đang hoạt động đều được. > > Khác với [trả lời bình luận](/vi/concepts/engagement), vốn có từ gói Starter: một bình luận nằm > dưới bài của chính bạn, còn một đánh giá nằm trên hồ sơ Google của doanh nghiệp và câu trả lời > đứng đó lâu dài bên cạnh điểm sao. ```bash theme={null} curl -X POST https://api.madiad.com/v1/gbp/reviews/reply \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "review_name": "accounts/1180.../locations/1578.../reviews/AbFvOqk", "comment": "Cảm ơn anh đã dành thời gian viết đánh giá. Hẹn gặp lại anh." }' ``` Truyền `review_name` (lấy nguyên văn từ danh sách đánh giá) hoặc cặp `review_id` + `location_id`. Dùng `review_name` gọn hơn: địa điểm đã nằm sẵn trong đường dẫn nên không thể lệch. Gọi lại lần nữa trên cùng một đánh giá sẽ **thay** câu trả lời cũ, không tạo thêm câu mới. # Lên lịch Source: https://docs.madiad.com/vi/concepts/scheduling Đăng ngay lập tức, vào một thời điểm cụ thể, hoặc thông qua hàng đợi định kỳ. Mọi endpoint đăng bài đều có thể phát hành ngay lập tức hoặc vào thời điểm sau. Bạn chọn chế độ thời gian cho từng request. ## Đăng ngay Đây là mặc định. Bỏ qua mọi trường lên lịch và bài đăng sẽ được phát hành ngay lập tức. ## Lên lịch cho một thời điểm cụ thể Thêm `scheduled_at` (ISO-8601) và tùy chọn `timezone` (tên IANA). MADIAD Hub giữ bài đăng và phát hành vào đúng thời điểm đó. ```bash theme={null} -F "scheduled_at=2026-06-01T15:00:00Z" \ -F "timezone=Asia/Ho_Chi_Minh" ``` * `scheduled_at` được đọc theo UTC khi kết thúc bằng `Z`. Cung cấp `timezone` để xử lý chính xác giờ địa phương và giờ mùa hè. * Các bài đăng đã lên lịch trả về `status: "scheduled"`. Chúng sẽ được đăng sau, bạn có thể theo dõi kết quả qua [webhooks](/vi/concepts/webhooks). * Bạn có thể lên lịch trước tối đa một năm. ## Dùng hàng đợi định kỳ Thay vì một mốc thời gian cụ thể, hãy đưa bài đăng vào hàng đợi của Profile bằng `add_to_queue=true`. Bài sẽ được phát hành vào khe thời gian mở tiếp theo theo lịch đăng đó. ```bash theme={null} -F "add_to_queue=true" ``` > [!WARNING] > > `scheduled_at` và `add_to_queue` **loại trừ lẫn nhau**: chỉ đặt một trong hai, không dùng cả hai cùng lúc. ### Cấu hình hàng đợi Mỗi Profile có cấu hình hàng đợi riêng (timezone, ngày hoạt động, khe thời gian). Đọc cấu hình: ```bash theme={null} curl "https://api.madiad.com/v1/queue/settings?profile_id=$PROFILE_ID" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Cập nhật bằng cách gửi ID của Profile kèm các trường bạn muốn đổi: ```bash theme={null} curl -X POST https://api.madiad.com/v1/queue/settings \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_id": "'$PROFILE_ID'", "timezone": "Asia/Ho_Chi_Minh", "days": ["mon","wed","fri"], "slots": ["09:00","18:00"] }' ``` ## Quản lý bài đã lên lịch Liệt kê các bài đang chờ lên lịch của một Profile: ```bash theme={null} curl "https://api.madiad.com/v1/schedule?profile_id=$PROFILE_ID" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Lên lịch lại hoặc sửa một bài trước khi phát hành, truyền bất kỳ trong `scheduled_at`, `timezone`, hoặc `caption`: ```bash theme={null} curl -X PATCH https://api.madiad.com/v1/schedule/$JOB_ID \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "scheduled_at": "2026-07-01T09:00:00Z", "timezone": "Asia/Ho_Chi_Minh" }' ``` Hủy một bài: ```bash theme={null} curl -X DELETE https://api.madiad.com/v1/schedule/$JOB_ID \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` `$JOB_ID` là `job_id` mà lệnh đăng trả về khi `status` là `scheduled`. Lệnh đăng không lên lịch trả `job_id: null`. Chỉ hủy hoặc sửa được một bài khi nó còn đang chờ trong lịch. Khi bài đã bắt đầu đăng, đã chạy xong, hoặc đã bị hủy trước đó, cả hai lệnh trả `400 invalid_request` và không có gì thay đổi. Nếu lúc đó không đọc được lịch thì lệnh trả `502` và cũng không có gì thay đổi: hãy gọi lại. Xem một bài đang ở đâu bằng `GET /v1/posts/status?job_id=$JOB_ID`: câu trả lời là `scheduled` khi bài còn chờ tới giờ, sau đó là các trạng thái như mọi bài khác. Sau khi hủy thành công, câu trả lời là `canceled`, dù tra bằng `job_id` hay bằng `request_id` của bài, và giữ nguyên như vậy: bài sẽ không chạy nữa, nên vòng poll dừng ở đó. `GET /v1/posts/history` cũng ghi bài này là `canceled`. Nếu một phần của bài đã kịp đăng trước khi lệnh hủy tới, trạng thái là `partial`, và nền tảng đã đăng nằm trong `results`; lịch sử có thể vẫn ghi bài đó là `canceled` cho tới lần kiểm tra hằng ngày kế tiếp. > [!NOTE] > > Hủy một bài đã lên lịch không xoá lượt đăng đã được ghi nhận từ lúc đó. Vì đăng bài không giới hạn trên các gói trả phí nên điều này không gây thiệt hại gì, khe đã hủy chỉ đơn giản vẫn nằm trong số liệu sử dụng của bạn. ## Xem trước khung giờ hàng đợi > [!NOTE] > > **Kênh áp dụng:** mọi kênh. Hàng đợi tính theo Profile, không theo kênh. `GET /v1/queue/settings` cho biết khung giờ bạn đã đặt. Hai endpoint dưới trả lời câu khác: **bài tiếp theo sẽ đi lúc nào**. ```bash theme={null} curl "https://api.madiad.com/v1/queue/preview?profile_id=$PROFILE_ID&count=10" \ -H "Authorization: Bearer $MADIAD_API_KEY" curl "https://api.madiad.com/v1/queue/next-slot?profile_id=$PROFILE_ID" \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` `count` từ 1 đến 50 (mặc định 10). Mỗi khung trong `preview` cho biết đã có bao nhiêu bài, sức chứa là bao nhiêu, và khung đó còn trống hay không. > [!NOTE] > > `next_slot` trả về `null` khi không còn khung trống trong 30 ngày tới. Đó là câu trả lời, không > phải lỗi. > [!WARNING] > > **Múi giờ mặc định của hàng đợi là `America/New_York`**, khung 9:00 / 12:00 / 17:00. Với Việt Nam > đó là 20:00 / 23:00 / 04:00. Nếu bạn dùng `add_to_queue`, hãy đặt lại múi giờ bằng > `POST /v1/queue/settings` trước. # Tình trạng dịch vụ Source: https://docs.madiad.com/vi/concepts/service-status Xem tình hình đăng bài của từng kênh, và kiểm tra API có đang phục vụ hay không. Có hai endpoint trả lời câu hỏi về tình trạng dịch vụ, và cả hai đều không cần API key. Bạn gọi chúng từ công cụ giám sát cũng được: chúng không gắn với tài khoản nào và không tiêu tốn [rate limit](/vi/concepts/rate-limits) của bạn. ## Tình hình đăng bài theo từng kênh `GET /v1/status` cho biết trong 24 giờ gần nhất, bài đăng đi tới từng kênh trong số 22 [kênh](/vi/concepts/channels) có tới nơi hay không. Đây cũng chính là dữ liệu vẽ nên [trang tình trạng](https://hub.madiad.com/status) công khai. ```bash theme={null} curl https://api.madiad.com/v1/status ``` ```json theme={null} { "status": "operational", "window_minutes": 1440, "platforms": [ { "platform": "instagram", "display_name": "Instagram", "status": "operational", "failure_rate": 0.125, "sample": 8, "failed": 1, "unknown": 0, "platform_wide": false } ], "updated_at": "2026-09-06T09:31:21.227Z" } ``` | Trường | Ý nghĩa | | - | - | | `status` (ngoài cùng) | Kết luận xấu nhất trong cả danh sách kênh: `operational`, `degraded` hoặc `outage`. | | `window_minutes` | Khoảng thời gian quan sát. Hiện là `1440`, tức 24 giờ. | | `platform` / `display_name` | Mã kênh bạn điền vào `platforms[]`, và tên hiển thị của nó. | | `sample` | Số bài đăng tới kênh đó đã có kết quả rõ ràng trong khoảng quan sát. | | `failed` | Trong số đó, bao nhiêu bài bị kênh từ chối. | | `unknown` | Số bài chưa bao giờ xác định được kết quả. Không tính là thành công cũng không tính là thất bại. | | `failure_rate` | `failed / sample`, làm tròn 3 chữ số thập phân. Bằng `null` khi mẫu quá nhỏ để nói được tỷ lệ. | | `platform_wide` | Bằng `true` khi kênh đó đang gặp sự cố ở phạm vi rộng hơn MADIAD Hub. | | `updated_at` | Thời điểm câu trả lời được tính. | Trường `status` của mỗi kênh có 5 giá trị, và 2 trong số đó cố ý không phải là kết luận về tình hình: * **`operational`**: số bài lỗi dưới 25% mẫu. * **`degraded`**: từ 25% mẫu trở lên bị lỗi. * **`outage`**: từ 50% mẫu trở lên bị lỗi. * **`no_activity`**: không có bài nào đăng tới kênh đó trong khoảng quan sát. Đây là thông tin về lưu lượng, không phải kết luận về tình hình. * **`monitoring`**: có bài đăng nhưng chưa tới 8, quá ít để một tỷ lệ phần trăm nói lên điều gì. Các con số vẫn hiện, chỉ có kết luận là được giữ lại. > [!NOTE] > > Các con số này là tổng hợp toàn bộ lượt đăng đi qua MADIAD Hub, không nhận ra được tài khoản, Profile hay bài đăng nào trong đó. Cũng vì vậy mà một kênh có thể đang `operational` ở đây, trong khi bài của riêng bạn tới kênh đó vẫn hỏng: một tài khoản bị nền tảng hạn chế thì con số chung không hề nhúc nhích. Muốn biết kết quả của chính mình, hãy đọc kết quả từng kênh trong mỗi bài đăng và xem [Vì sao bài không đăng được](/vi/concepts/publish-failures). Câu trả lời được cache tối đa một phút, nên gọi dày hơn mức đó cũng chỉ nhận lại đúng nội dung cũ. ## API có đang chạy không? `GET /v1/health` là phép thử sống. Nó không đọc dữ liệu nào và trả lời ngay: ```bash theme={null} curl https://api.madiad.com/v1/health ``` ```json theme={null} { "ok": true, "service": "madiad-hub-gateway", "time": "2026-09-06T09:31:21.227Z" } ``` Nhận `200` nghĩa là API đang phục vụ request. Nó không nói gì về chuyện đăng bài có trơn tru hay không, câu hỏi đó thuộc về `GET /v1/status`. Hãy trỏ công cụ giám sát uptime vào đây thay vì vào một route cần xác thực, để phép thử mỗi phút không ăn vào số request mà tích hợp của bạn được dùng. # Mức sử dụng Source: https://docs.madiad.com/vi/concepts/usage Đọc hạn mức của gói và phần bạn đã dùng trong chu kỳ hiện tại. `GET /v1/usage` trả về toàn bộ hạn mức tài khoản của bạn cùng phần đã dùng trong chu kỳ thanh toán hiện tại: gói, mốc thời gian chu kỳ và một bộ đếm cho từng loại tài nguyên. Dùng nó để hiển thị widget mức sử dụng, quyết định có gửi job FFmpeg hay không, hoặc kiểm tra còn bao nhiêu slot Profile trước khi tạo connect link. > [!NOTE] > > Đăng bài không giới hạn trên mọi gói trả phí (xem [Bài đăng](/vi/concepts/posts)), nên `uploads.limit` luôn là `null`, bộ đếm `used` vẫn chạy như một số liệu ghi nhận, không phải hạn mức. ## Request ```bash theme={null} curl https://api.madiad.com/v1/usage \ -H "Authorization: Bearer $MADIAD_API_KEY" ``` Endpoint này chỉ hoạt động trong phạm vi tài khoản sở hữu API key: không nhận tham số nào và không bao giờ trả về số liệu của tài khoản khác. Đây là request `GET`, không tiêu tốn lượt xử lý video hay lượt đăng nào, chỉ tính vào [rate limit](/vi/concepts/rate-limits) mỗi phút. ## Response ```json theme={null} { "plan": { "id": "growth", "name": "Growth", "price_vnd_monthly": 390000 }, "status": "active", "period": { "start": "2026-07-07", "renews_on": "2026-08-07", "expires_on": null }, "uploads": { "used": 180, "limit": null, "remaining": null }, "ffmpeg_minutes": { "used": 0, "limit": 0, "remaining": 0, "included": false }, "profiles": { "used": 4, "limit": 4, "remaining": 0 }, "direct_messages": { "used": 0, "limit": 0, "remaining": 0, "day": "2026-07-20", "included": false }, "comments": { "included": true }, "api_keys": { "used": 2, "limit": null, "remaining": null }, "features": { "schedule": true, "queue": true, "analytics": true, "webhooks": true } } ``` ### Bộ đếm Mọi tài nguyên có hạn mức đều dùng chung ba field: | Field | Ý nghĩa | | - | - | | `used` | Đã dùng trong chu kỳ hiện tại (riêng `direct_messages` là đã dùng trong ngày hôm nay). | | `limit` | Hạn mức. **`null` nghĩa là không giới hạn.** `0` nghĩa là gói của bạn không bao gồm tài nguyên đó. | | `remaining` | `limit - used`, không xuống dưới 0. **Là `null` khi `limit` là `null`.** | > [!WARNING] > > Hãy hiểu `limit: null` là không giới hạn, không phải 0: fallback `remaining ?? 0` sẽ hiển thị sai với người dùng của bạn rằng họ đã hết hạn mức. ### Các field | Field | Ý nghĩa | | - | - | | `plan` | Id, tên hiển thị và giá theo tháng (VND) của gói đang hoạt động (`null` với gói Custom). | | `status` | Trạng thái tài khoản. Ở response này luôn là `active`: tài khoản `expired` hoặc `banned` bị trả `403` trước khi endpoint chạy (xem phần lỗi bên dưới). | | `period.start` | Ngày đầu tiên của chu kỳ hiện tại, dạng `YYYY-MM-DD`. | | `period.renews_on` | Ngày chu kỳ chuyển sang kỳ mới và các bộ đếm được reset. | | `period.expires_on` | Ngày kết thúc thời hạn đã thanh toán nếu có, ngược lại là `null`. | | `uploads` | Số bài đã đăng trong chu kỳ. `limit` là `null` trên mọi gói trả phí. | | `ffmpeg_minutes` | Số lượt [xử lý video](/vi/concepts/ffmpeg) trong chu kỳ, mỗi job một lượt, bất kể video dài bao nhiêu. `included: false` nghĩa là tài khoản chưa được bật tính năng này (cấp theo yêu cầu, không đi kèm gói), nên `limit` bằng 0 không phải là đã dùng hết. Khi đã bật, hạn mức tính cho cả tài khoản: mọi thương hiệu dùng chung. | | `profiles` | Số [Profiles](/vi/concepts/profiles) đã kết nối so với giới hạn của gói. | | `direct_messages` | Số DM hôm nay. `included: false` nghĩa là gói của bạn không có phần nhắn tin: gửi DM, trả lời riêng cho người bình luận, chạy AutoDM monitor, và [trả lời đánh giá Google](/vi/concepts/reviews). | | `comments` | Không có bộ đếm, chỉ có `included`. `true` nghĩa là gói của bạn ghi được [bình luận](/vi/concepts/engagement): trả lời công khai, viết, xoá, ẩn/thích/ghim. Đọc bình luận thì mọi gói đang hoạt động đều được, không phụ thuộc field này. Không có hạn mức theo ngày cho phần này, chỉ có [giới hạn lệnh mỗi phút](/vi/concepts/rate-limits). | | `api_keys` | Số key đang hoạt động (chưa thu hồi). | | `features` | Các cờ tính năng của gói đang hoạt động. | ## Mốc thời gian chu kỳ Chu kỳ của bạn được neo theo ngày kích hoạt gói, tính theo giờ Việt Nam (UTC+7), **không phải** ngày mùng 1 của tháng dương lịch. Gói kích hoạt ngày 7 sẽ reset vào ngày 7 hằng tháng. Nếu ngày neo không tồn tại trong tháng ngắn hơn, hệ thống lùi về ngày cuối cùng của tháng đó. `period.start` và `period.renews_on` luôn báo đúng ngày thực tế, nên hãy đọc hai field này thay vì tự tính mốc đầu/cuối tháng. ## Kiểm tra trước khi thao tác ```bash theme={null} # Chỉ gửi job FFmpeg nếu còn phút (null = không giới hạn). REMAINING=$(curl -s https://api.madiad.com/v1/usage \ -H "Authorization: Bearer $MADIAD_API_KEY" | jq '.ffmpeg_minutes.remaining') if [ "$REMAINING" = "null" ] || [ "$REMAINING" -gt 0 ]; then echo "Có thể gửi job" fi ``` ## Lỗi | Status | Code | Ý nghĩa | | - | - | - | | `401` | `unauthorized` | API key thiếu hoặc không hợp lệ. | | `403` | `forbidden` | Tài khoản đang ở trạng thái `expired` hoặc `banned`. Tài khoản hết hạn sẽ đăng bài lại ngay khi khoản gia hạn được ghi nhận. | | `429` | `rate_limited` | Vượt giới hạn số request mỗi phút. | Xem [Lỗi](/vi/concepts/errors) để biết danh sách đầy đủ. # Webhooks Source: https://docs.madiad.com/vi/concepts/webhooks Nhận callback được ký khi bài đăng được xuất bản hoặc thất bại. Webhooks đẩy sự kiện đến server của bạn nên không phải kiểm tra định kỳ. Đây là cách đáng tin cậy để biết kết quả của các bài đăng đã lên lịch và các lần tải lên bất đồng bộ (video). ## Đăng ký Tạo một webhook endpoint trong dashboard tại [hub.madiad.com/dashboard/webhooks](https://hub.madiad.com/dashboard/webhooks): nhập URL sẽ nhận các lần phân phối và chọn các loại sự kiện bạn muốn theo dõi. Dashboard sẽ sinh một `secret` cho endpoint đó, hãy lưu lại. Bạn sẽ dùng giá trị này để xác minh mỗi lần phân phối. ## Sự kiện | Sự kiện | Kích hoạt khi | | - | - | | `post.completed` | Một lần tải lên đã xử lý xong trên nền tảng: thành công hay thất bại nằm trong payload | | `connection.connected` | Một tài khoản mạng xã hội vừa được kết nối vào Profile | | `connection.disconnected` | Một tài khoản mạng xã hội vừa bị ngắt khỏi Profile | | `connection.reauth_required` | Một tài khoản đã kết nối cần được cấp quyền lại | Mỗi lần phân phối là một JSON body dạng `{ "id", "type", "created_at", "data": { … } }`. Với `post.completed`, `data` chứa `profile_id`, `platform`, `media_type`, `success`, `draft`, `url`, `publish_id` và `error`. `draft: true` nghĩa là nền tảng đã nhận bài vào mục nháp của tài khoản thay vì đăng (thường gặp nhất là TikTok, khi chạm giới hạn đăng trực tiếp trong ngày): `success` vẫn là `true`, `url` là `null`, và bài chưa hiện công khai cho tới khi chủ tài khoản mở ứng dụng bấm đăng. Hãy coi bài là chưa đăng, và đừng gửi lại. ## Xác minh chữ ký Mỗi request đều mang header `X-MADIAD-Signature` ở dạng `sha256=`: một HMAC-SHA256 của **raw request body**, được ký bằng `secret` đăng ký của bạn. Hãy tính lại và so sánh trước khi tin tưởng payload. Mỗi lần phân phối cũng kèm header `X-MADIAD-Event` và `X-MADIAD-Delivery`. ```js theme={null} import crypto from "node:crypto"; function verify(rawBody, signatureHeader, secret) { const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); const a = Buffer.from(signatureHeader); const b = Buffer.from(expected); return a.length === b.length && crypto.timingSafeEqual(a, b); } ``` > [!WARNING] > > Xác minh dựa trên body **thô, chưa qua parse**. Việc serialize lại JSON đã parse sẽ thay đổi bytes và chữ ký sẽ không khớp. ## Phản hồi và thử lại * Trả về mã trạng thái `2xx` trong vòng **5 giây** để xác nhận đã nhận. Hệ thống không đi theo chuyển hướng, nên mã `3xx` cũng tính là thất bại. * Phản hồi không phải `2xx`, hoặc quá thời gian chờ, làm lượt gửi đó thất bại. Mỗi lượt gửi thử nhanh tối đa 3 lần, cách nhau 0,5 giây rồi 1 giây. * Mỗi lần phân phối có **tối đa 5 lượt gửi**. Sau một lượt thất bại, lượt kế tiếp chờ **ít nhất** 1 phút, rồi 5 phút, 30 phút, 2 giờ. Việc gửi lại chạy theo các đợt xử lý nền nên khoảng cách thực tế có thể dài hơn; muộn nhất là đợt chạy hằng ngày lúc 01:30 UTC (8:30 sáng giờ Việt Nam). * Sau lượt thứ 5 vẫn thất bại, lần phân phối bị đánh dấu hỏng và không được gửi lại nữa. * Hãy làm cho trình xử lý của bạn **idempotent** vì một lần phân phối có thể đến nhiều hơn một lần. Khử trùng lặp theo `id` của lần phân phối (cũng được gửi qua header `X-MADIAD-Delivery`). > [!TIP] > > Thực hiện các tác vụ chậm (ghi cơ sở dữ liệu, gọi dịch vụ khác) *sau khi* bạn đã phản hồi `2xx` (ví dụ bằng cách đưa sự kiện vào hàng đợi) để không bao giờ vượt quá thời gian chờ phân phối. # Tổng quan Source: https://docs.madiad.com/vi/index Xuất bản lên 22 kênh (mạng xã hội, blog, chat và bản tin email) từ một REST API duy nhất. MADIAD Hub là một REST endpoint duy nhất có khả năng xuất bản lên 22 kênh: TikTok, Instagram, YouTube, Facebook, LinkedIn, X, Threads, Pinterest, Reddit, Bluesky và Google Business Profile, cùng với chat (Telegram, Discord, Slack), fediverse (Mastodon, Nostr, Lemmy), blog (WordPress, dev.to, Hashnode), Whop và listmonk. Quản lý nhiều thương hiệu hoặc tài khoản khách hàng dưới dạng các **Profiles**, đăng bài chỉ trong một lần gọi, và để Hub xử lý việc lên lịch, idempotency cùng callback báo kết quả gửi bài. > [card] [Bắt đầu nhanh](/vi/quickstart) > > Tạo bài đăng đa nền tảng đầu tiên trong vòng chưa đầy năm phút. > [card] [Xác thực](/vi/authentication) > > Tạo API key và ủy quyền cho các request. > [card] [Profiles](/vi/concepts/profiles) > > Mô hình hóa thương hiệu, khách hàng hoặc tài khoản phụ dưới dạng các Profiles. > [card] [Webhooks](/vi/concepts/webhooks) > > Nhận callback có chữ ký khi bài đăng được xuất bản hoặc thất bại. ## Kênh hỗ trợ Đăng lên 22 kênh từ một request, chỉ cần liệt kê những kênh bạn muốn. > [card] TikTok > [card] Instagram > [card] YouTube > [card] Facebook > [card] LinkedIn > [card] X > [card] Threads > [card] Pinterest > [card] Reddit > [card] Bluesky > [card] Google Business > [card] Telegram > [card] Discord > [card] Slack > [card] Mastodon > [card] Nostr > [card] Lemmy > [card] WordPress > [card] dev.to > [card] Hashnode > [card] Whop > [card] listmonk > [!NOTE] > > **Mỗi kênh nhận loại bài khác nhau, và có hai cách kết nối.** YouTube chỉ nhận video; Slack, dev.to và listmonk chỉ nhận bài chữ; mười một kênh không có đăng nhập OAuth mà được liên kết bằng webhook, API key hoặc application password của chính bạn. Bảng đầy đủ nằm ở [Kênh](/vi/concepts/channels). ## Tại sao chọn MADIAD Hub * **Một tích hợp, 22 kênh.** Gửi cùng một request; Hub sẽ phân phối đến từng kênh trong danh sách. * **Profiles cho quy trình đa thương hiệu.** Các agency, creator và đội vận hành có thể đăng bài thay mặt nhiều tài khoản chỉ với một API key. * **Lên lịch tích hợp sẵn.** Chọn timestamp chính xác hoặc đưa bài đăng vào hàng đợi định kỳ. * **Idempotent theo thiết kế.** An toàn khi retry, các bản trùng lặp sẽ gộp lại theo key do bạn kiểm soát. * **Webhooks có chữ ký.** Mọi sự kiện đều được ký HMAC để đảm bảo payload đáng tin cậy. ## Base URL ``` https://api.madiad.com/v1 ``` Tất cả endpoint của Hub đều nhận và trả về `application/json`, trừ khi có ghi chú khác. Tải lên media sử dụng `multipart/form-data`. ## SDK và công cụ * **HTTP**: hoạt động với bất kỳ ngôn ngữ nào. Xem [Bắt đầu nhanh](/vi/quickstart) để có ví dụ `curl`. * **n8n**: gọi API từ node HTTP Request tích hợp sẵn. Xem [hướng dẫn n8n](/vi/integrations/n8n). * **Make**: đăng bài từ một scenario bằng module HTTP. Xem [hướng dẫn Make](/vi/integrations/make). > [!NOTE] > > Cần hỗ trợ? Liên hệ qua [info@madiad.com](mailto:info@madiad.com). # Make Source: https://docs.madiad.com/vi/integrations/make Đăng lên mọi nền tảng từ một scenario Make (Integromat) bằng REST API của MADIAD Hub. Make giao tiếp với MADIAD Hub qua module chung **HTTP → Make a request**, không cần cài app tuỳ chỉnh nào. Thêm module này vào bất kỳ scenario nào, bạn có thể đăng khi Google Sheet thay đổi, theo lịch, khi có form gửi về, hoặc theo bất kỳ sự kiện kích hoạt nào khác mà Make hỗ trợ. > [!NOTE] > > Hướng dẫn này giả định bạn đã có API key và ít nhất một profile được kết nối. Nếu chưa, hãy làm [Bắt đầu nhanh](/vi/quickstart) trước (5 phút). ## 1. Khối cơ bản: HTTP → Make a request Mọi lệnh gọi bên dưới đều dùng chung một module là **HTTP → Make a request**. Các thiết lập chung: | Trường | Giá trị | | - | - | | URL | `https://api.madiad.com/v1/…` (endpoint theo loại media) | | Method | `POST` | | Headers | `Authorization: Bearer mdc_live_…` | | Parse response | **Yes**, để phản hồi JSON được map vào các module sau | > [!WARNING] > > Tạo **một API key riêng** cho Make (đặt tên `make` trong dashboard). Nếu bị lộ, bạn chỉ thu hồi đúng key đó mà không ảnh hưởng các tích hợp khác. Lưu key trong một biến scenario hoặc data store của Make thay vì dán vào từng module. ## 2. Đăng một bài chỉ có chữ Thêm module **HTTP → Make a request**: * **URL** là `https://api.madiad.com/v1/posts/text` * **Method** là `POST` * **Headers** là thêm `Authorization` = `Bearer mdc_live_…` * **Body type** là `Raw` * **Content type** là `application/json` * **Request content**: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["facebook", "linkedin", "x"], "caption": "Bài viết mới đã lên sóng, đọc ngay trên blog." } ``` Bài chỉ có chữ dùng JSON. **Ảnh và video dùng hai định dạng khác nhau**, xem [Đăng file](#4-đăng-ảnh-hoặc-video). ## 3. Chống trùng lặp bằng Idempotency-Key Make chạy lại một module khi gặp lỗi hoặc khi bạn thực thi lại scenario. Để tránh đăng trùng, thêm một header có giá trị ổn định cho mỗi mục. Hub sẽ coi lần lặp lại cùng key là cùng một yêu cầu. Thêm header: | Name | Value | | - | - | | `Idempotency-Key` | map một trường duy nhất cho mỗi bài và không bao giờ bị dùng lại: một cột UUID hoặc mã bản ghi, **không** dùng `Row number` | Dùng một giá trị **duy nhất cho mỗi bài định đăng** và **không đổi khi thử lại chính bài đó**. Lựa chọn tốt: một UUID bạn sinh một lần rồi ghi ngược lại vào dòng dữ liệu, hoặc một hash của chính nội dung. > [!WARNING] > > **Tuyệt đối không dùng giá trị bị tái sử dụng.** Số dòng bảng tính sẽ dịch chuyển khi bạn xoá dòng, khiến nội dung hôm nay mang một key mà bài hôm qua đã chiếm. Khi gặp key đã dùng, Hub trả về kết quả **cũ** và không đăng gì cả, nên một key hỏng trông y hệt một bài đăng thành công. Hãy để ý header `Idempotent-Replayed: true`, và xem [Idempotency](/vi/concepts/idempotency). ## 4. Đăng ảnh hoặc video **Ảnh** dùng `POST /v1/posts/photos`. Đặt **Body type** thành `Multipart/form-data`, lặp lại `platforms[]` một lần cho mỗi nền tảng, và đính kèm ảnh qua một trường **file** map từ module trước (ví dụ *Google Drive → Download a file*, hoặc *HTTP → Get a file*): | Tên trường | Kiểu | Giá trị | | - | - | - | | `profile_id` | Text | `9f2b4c17-…` | | `platforms[]` | Text | `instagram` | | `platforms[]` | Text | `facebook` | | `caption` | Text | `Ra mắt hương vị mới hôm nay` | | `photos[]` | File | `data` + `file name` từ module download | Đã có sẵn URL ảnh công khai thay vì file? Bỏ qua bước tải lên và gửi `photo_urls[]` dưới dạng **Raw JSON**, xem [Đăng ảnh bằng URL](/vi/concepts/posts#đăng-ảnh-bằng-url). **Video** dùng `POST /v1/posts/video`, chỉ nhận JSON chứ không nhận file tải lên. Đặt **Body type** thành `Raw` / `application/json` và truyền một `video_url` công khai: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["tiktok", "youtube"], "video_url": "https://cdn.example.com/launch.mp4" } ``` ## 5. Xử lý phản hồi bất đồng bộ **Đăng bài là bất đồng bộ.** Phần lớn bài, kể cả bài chữ và bài ảnh, lúc đầu trả về `"status": "processing"` và một `request_id`; chỉ bài được trả lời ngay mới trả về `"status": "completed"`. Có hai cách hoàn tất: **Poll** là thêm module **Sleep**, rồi một module **HTTP → Make a request** khác: ``` GET https://api.madiad.com/v1/posts/status?request_id={{request_id}} ``` Bọc nó trong một **Repeater** và dừng khi `status` là `completed`, `draft`, `partial`, hoặc `failed` (`draft`: một nền tảng đang giữ bài ở mục nháp của tài khoản, xem [Bài đăng](/vi/concepts/posts)). Bài hẹn giờ đã bị hủy thì dừng ở `canceled`, nên nếu scenario có hẹn giờ thì dừng cả ở trạng thái đó. **Webhook (khuyến nghị)** là thêm module **Custom webhook** trong Make, sao chép địa chỉ của nó, và đăng ký trong dashboard tại [hub.madiad.com/dashboard/webhooks](https://hub.madiad.com/dashboard/webhooks) cho sự kiện `post.completed`. Hub ký mỗi lần gửi bằng HMAC-SHA256, hãy xác thực trước khi tin vào payload. Xem [Webhooks](/vi/concepts/webhooks). ## 6. Đọc kết quả theo từng nền tảng Khi bật **Parse response**, bạn map thẳng phản hồi. Một bài đăng có thể thành công một phần, hãy rẽ nhánh theo `success` của từng nền tảng thay vì chỉ nhìn `status` tổng: ```json theme={null} { "status": "partial", "results": { "facebook": { "success": true, "url": "https://facebook.com/12345/posts/67890" }, "x": { "success": false, "error": "Upload rejected by the platform" } }, "failed_platforms": ["x"] } ``` Dùng một **Router** với bộ lọc theo `success` để chuyển các lỗi sang một cảnh báo. ## Ví dụ scenario: Google Sheets → mọi nền tảng 1. **Google Sheets → Watch rows** (hoặc *Search rows* theo lịch với `status = ready`). 2. **HTTP → Make a request** → `POST /v1/posts/photos` với `profile_id`, `platforms[]`, và `caption` map từ dòng đó; `Idempotency-Key` là một cột UUID trên dòng đó (không dùng số dòng, xem cảnh báo ở trên). 3. **Router** theo phản hồi → nếu thành công, **Google Sheets → Update a row** thành `published`; nếu lỗi, gửi cảnh báo. ## Bước tiếp theo > [card] [Tuỳ chọn theo nền tảng](/vi/concepts/posts) > > Ghi đè caption, tiêu đề và tuỳ chọn cho từng nền tảng. > [card] [Lên lịch](/vi/concepts/scheduling) > > Để Hub tự đăng vào thời điểm tương lai. > [card] [Idempotency](/vi/concepts/idempotency) > > Chạy lại an toàn cho mọi quy trình tự động hóa. > [card] [Webhooks](/vi/concepts/webhooks) > > Nhận callback có chữ ký khi bài đăng hoàn tất. # n8n Source: https://docs.madiad.com/vi/integrations/n8n Tự động đăng lên mọi nền tảng từ workflow n8n bằng REST API của MADIAD Hub. n8n giao tiếp với MADIAD Hub qua HTTP thuần, không cần cài node đặc biệt nào. Bạn lưu API key một lần dưới dạng credential, sau đó điều khiển Hub bằng node **HTTP Request** trong bất kỳ workflow nào: đăng khi có dòng mới trong bảng tính, khi có bài RSS mới, theo lịch, hoặc từ ứng dụng của bạn. > [!NOTE] > > Hướng dẫn này giả định bạn đã có API key và ít nhất một profile được kết nối. Nếu chưa, hãy làm [Bắt đầu nhanh](/vi/quickstart) trước (5 phút). ## 1. Lưu API key thành credential Giữ key ngoài file workflow bằng cách lưu nó thành credential dùng lại được. 1. Trong n8n mở **Credentials → New**, chọn **Header Auth**. 2. Đặt: * **Name** là `Authorization` * **Value** là `Bearer mdc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` 3. Lưu lại với tên ví dụ **MADIAD Hub**. Mỗi node HTTP Request bên dưới sẽ chọn credential này ở **Authentication → Generic Credential Type → Header Auth**. > [!WARNING] > > Tạo **một key riêng cho mỗi workflow** (đặt tên `n8n` trong dashboard). Nếu key bị lộ, bạn chỉ cần thu hồi đúng key đó mà không ảnh hưởng phần còn lại. ## 2. Đăng một bài chỉ có chữ Thêm node **HTTP Request**: | Trường | Giá trị | | - | - | | Method | `POST` | | URL | `https://api.madiad.com/v1/posts/text` | | Authentication | Header Auth → *MADIAD Hub* | | Send Body | Bật, **JSON** | Body (JSON): ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["facebook", "linkedin", "x"], "caption": "Bài viết mới đã lên sóng, đọc ngay trên blog." } ``` Bài chỉ có chữ dùng JSON. Ảnh có thể gửi bằng file upload (multipart) hoặc URL ảnh công khai (JSON); video luôn dùng JSON với URL công khai, xem [Đăng ảnh hoặc video](#4-đăng-ảnh-hoặc-video) bên dưới. ## 3. Chống trùng lặp bằng Idempotency-Key Nếu n8n chạy lại một node (timeout, nhánh lỗi), bạn không muốn tạo ra bài đăng trùng. Thêm một header có giá trị ổn định cho mỗi item, Hub sẽ coi lần lặp lại cùng key là cùng một yêu cầu. Ở phần **Headers**, thêm: | Name | Value | | - | - | | `Idempotency-Key` | `={{ $json.id }}` | Dùng một giá trị **duy nhất cho mỗi bài định đăng** và **không đổi khi thử lại chính bài đó**. Lựa chọn tốt: một UUID bạn sinh một lần rồi ghi ngược lại vào dòng dữ liệu, hoặc một hash của chính nội dung. > [!WARNING] > > **Tuyệt đối không dùng giá trị bị tái sử dụng.** Số dòng bảng tính sẽ dịch chuyển khi bạn xoá dòng, còn `$itemIndex` quay về 0 ở mỗi lần chạy. Cả hai đều khiến nội dung hôm nay mang một key mà bài hôm qua đã chiếm. Khi gặp key đã dùng, Hub trả về kết quả **cũ** và không đăng gì cả, nên một key hỏng trông y hệt một bài đăng thành công. Hãy để ý header `Idempotent-Replayed: true`, và xem [Idempotency](/vi/concepts/idempotency). ## 4. Đăng ảnh hoặc video Endpoint là `https://api.madiad.com/v1/posts/photos` cho ảnh và `https://api.madiad.com/v1/posts/video` cho một video. **Ảnh, tải file lên**: chọn **Send Body → Form-Data Multipart** rồi thêm từng trường thành từng entry riêng. Lặp lại `platforms[]` một lần cho mỗi nền tảng, và gửi ảnh dưới dạng trường **binary**: | Tên trường | Kiểu | Giá trị | | - | - | - | | `profile_id` | Text | `9f2b4c17-…` | | `platforms[]` | Text | `instagram` | | `platforms[]` | Text | `facebook` | | `caption` | Text | `Ra mắt hương vị mới hôm nay` | | `photos[]` | Binary | binary property từ node trước (ví dụ `data`) | **Ảnh, dùng URL công khai**: nếu ảnh đã có link `https://` công khai, bỏ qua bước xử lý file luôn. Chọn **Send Body → JSON** và truyền link vào `photo_urls[]`, đây là cách đơn giản nhất cho n8n vì không cần node binary nào: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["instagram", "facebook"], "caption": "Ra mắt hương vị mới hôm nay", "photo_urls": ["https://cdn.example.com/launch.jpg"] } ``` **Video**: luôn dùng JSON, không dùng multipart. Chọn **Send Body → JSON** và truyền một `video_url` công khai: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["facebook", "instagram"], "caption": "Ra mắt hương vị mới hôm nay", "video_url": "https://cdn.example.com/launch.mp4" } ``` > [!TIP] > > Cần tải file ảnh lên thay vì dùng URL? Đặt một node **HTTP Request** (Response Format → *File*) hoặc node **Read/Download** trước node này, rồi tham chiếu binary property của nó trong `photos[]`. ## 5. Xử lý phản hồi bất đồng bộ **Đăng bài là bất đồng bộ.** Phần lớn bài, kể cả bài chữ và bài ảnh, lúc đầu trả về `"status": "processing"` và một `request_id`; chỉ bài được trả lời ngay mới trả về `"status": "completed"` kèm `results` theo từng nền tảng. Có hai cách lấy kết quả cuối: **Poll** là thêm node **Wait**, rồi node **HTTP Request**: ``` GET https://api.madiad.com/v1/posts/status?request_id={{ $json.request_id }} ``` Lặp cho tới khi `status` là `completed`, `draft`, `partial`, hoặc `failed` (`draft`: một nền tảng đang giữ bài ở mục nháp của tài khoản, xem [Bài đăng](/vi/concepts/posts)). Bài hẹn giờ đã bị hủy thì dừng ở `canceled`, nên nếu workflow có hẹn giờ thì dừng cả ở trạng thái đó. **Webhook (khuyến nghị)** là nhận thông báo đẩy thay vì poll. Thêm node **Webhook** trong n8n, sao chép Production URL của nó, và đăng ký trong dashboard tại [hub.madiad.com/dashboard/webhooks](https://hub.madiad.com/dashboard/webhooks) cho sự kiện `post.completed`. Hub ký mọi lần gửi bằng HMAC-SHA256, hãy xác thực chữ ký ở node kế tiếp. Xem [Webhooks](/vi/concepts/webhooks). ## 6. Đọc kết quả theo từng nền tảng Một bài đăng có thể thành công một phần: khi bài đã xử lý xong, hãy kiểm tra `results` theo từng nền tảng thay vì chỉ nhìn `status` tổng. (`failed_platforms` chỉ có trong response của lệnh đăng; poll trạng thái trả về `status` và `results`.) ```json theme={null} { "status": "partial", "results": { "facebook": { "success": true, "url": "https://facebook.com/12345/posts/67890" }, "x": { "success": false, "error": "Upload rejected by the platform" } } } ``` Rẽ nhánh theo `success` bằng node **IF** để chuyển các lỗi sang một cảnh báo Slack hoặc email. ## 7. Xử lý lỗi mà không retry vô tận **Retry On Fail** của n8n coi mọi phản hồi không phải 2xx là đáng thử lại, và điều đó sai với phần lớn lỗi ở đây. Hãy rẽ nhánh theo mã trạng thái HTTP, đừng dựa vào câu chữ của thông báo: | Trạng thái | Nghĩa là gì | Workflow nên làm gì | | - | - | - | | `400` | Nội dung hoặc request sai (caption vượt giới hạn nền tảng, URL hỏng). | Dừng lại. Gửi lại y nguyên thì không thể thành công, hãy đẩy sang nhánh lỗi. | | `409 platform_not_connected` | Profile chưa có tài khoản cho nền tảng đó. | Báo cho người thật. Nếu cứ sau mỗi bài đăng được là lại gặp, nền tảng đang hạn chế tài khoản và kết nối lại không giải quyết được. | | `409 idempotency_in_progress` | Lần gửi trước với cùng key vẫn đang chạy. | Đợi vài giây rồi thử lại **với đúng key cũ**, bạn sẽ nhận kết quả của lần gửi đầu. | | `429` | Bị giới hạn tốc độ. | Chờ đủ số giây trong `Retry-After` rồi thử lại. | | `5xx` | Việc đăng bài thất bại ở phía Hub. | Thử lại kèm backoff, và **luôn gửi `Idempotency-Key`** để lần retry không tạo bài trùng. | Trong node HTTP Request, hãy bật **Never Error** (hoặc đặt *On Error* thành *Continue using error output*) để đọc được `$json.error.code` và rẽ nhánh theo nó, thay vì để node làm hỏng cả lượt chạy. > [!WARNING] > > Trước ngày 07/08/2026, mọi lỗi khi đăng bài đều trả về `502`, nên một workflow bật > **Retry On Fail** sẽ retry mãi những bài không bao giờ đăng được. Nếu workflow của bạn đang có > vòng retry dựa trên hành vi cũ đó, hãy thay bằng bảng ở trên. ## Ví dụ workflow: Google Sheets → mọi nền tảng Một mẫu phổ biến là lịch nội dung trong Google Sheets, tự đăng khi một dòng được đánh dấu sẵn sàng: 1. **Schedule Trigger** (mỗi 15 phút) → **Google Sheets** (lấy các dòng có `status = ready`). 2. **HTTP Request** → `POST /v1/posts/photos` với `profile_id`, `platforms[]`, `caption` lấy từ dòng đó, `Idempotency-Key` = một UUID lưu sẵn trên dòng đó (không dùng số dòng, xem cảnh báo ở trên). 3. **IF** theo phản hồi → nếu thành công, **Google Sheets** cập nhật dòng thành `published`; nếu lỗi, gửi cảnh báo. ## Bước tiếp theo > [card] [Tuỳ chọn theo nền tảng](/vi/concepts/posts) > > Ghi đè caption, tiêu đề và tuỳ chọn cho từng nền tảng. > [card] [Lên lịch](/vi/concepts/scheduling) > > Để Hub tự đăng vào thời điểm tương lai. > [card] [Idempotency](/vi/concepts/idempotency) > > Chạy lại an toàn từ mọi automation. > [card] [Webhooks](/vi/concepts/webhooks) > > Nhận callback có chữ ký khi bài đăng hoàn tất. # Bắt đầu nhanh Source: https://docs.madiad.com/vi/quickstart Đăng bài đầu tiên lên nhiều nền tảng trong vòng chưa đầy năm phút. Hướng dẫn này đưa bạn từ tài khoản mới tạo đến bài đăng trực tiếp trên Instagram và Facebook. ## 1. Lấy API key Đăng nhập vào [hub.madiad.com](https://hub.madiad.com), mở **API Keys** và tạo một key mới. Key chỉ hiển thị một lần, hãy sao chép ngay lập tức. ```bash theme={null} export MADIAD_API_KEY="mdc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` > [!WARNING] > > Bảo mật API key như mật khẩu. Không commit vào git hoặc để lộ trong code phía client. ## 2. Kết nối một Profile Một **Profile** đại diện cho một thương hiệu, khách hàng hoặc tài khoản con mà bạn đăng bài thay mặt. Tạo Profile và kết nối các nền tảng mạng xã hội bạn muốn đăng lên. ```bash theme={null} curl -X POST https://api.madiad.com/v1/connections/start \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "friendly_name": "Acme Coffee" }' ``` Response: ```json theme={null} { "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "friendly_name": "Acme Coffee", "access_url": "https://connect.madiad.com/o/abc123def456", "expires_in": "48h" } ``` Mở `access_url` trong trình duyệt và đăng nhập vào từng nền tảng bạn muốn đăng lên (Instagram, Facebook, TikTok, v.v.). Các kết nối sẽ được duy trì cho đến khi bị thu hồi. ## 3. Đăng lên nhiều nền tảng Gửi một request duy nhất, MADIAD Hub sẽ phân phối đến từng nền tảng. ```bash theme={null} curl -X POST https://api.madiad.com/v1/posts/photos \ -H "Authorization: Bearer $MADIAD_API_KEY" \ -H "Idempotency-Key: launch-2026-05-31-001" \ -F "profile_id=9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83" \ -F "platforms[]=instagram" \ -F "platforms[]=facebook" \ -F "caption=New blend dropping today" \ -F "photos[]=@./launch.jpg" ``` Response: ```json theme={null} { "status": "completed", "profile_id": "9f2b4c17-8e3a-4d5f-b061-2c7e9a4d1b83", "platforms": ["instagram", "facebook"], "request_id": null, "job_id": null, "results": { "instagram": { "success": true, "publish_id": "17912345678901234", "url": "https://instagram.com/p/abc123" }, "facebook": { "success": true, "publish_id": "12345_67890", "url": "https://facebook.com/12345/posts/67890" } } } ``` Đó là dạng của một bài đã xử lý xong. Phần lớn bài, kể cả bài chữ và bài ảnh, lúc đầu trả về `"status": "processing"` kèm `request_id` và chưa có `results` cuối cùng; hãy poll `GET /v1/posts/status` bằng id đó để có `status` và `results` cuối cùng. Xem [Bài đăng](/vi/concepts/posts) để biết đầy đủ các giá trị status. ## 4. (Tùy chọn) Lên lịch đăng bài Thay thế thời điểm đăng ngay bằng một mốc thời gian trong tương lai. ```bash theme={null} -F "scheduled_at=2026-06-01T15:00:00Z" \ -F "timezone=Asia/Ho_Chi_Minh" ``` ## 5. (Tùy chọn) Đăng ký webhook Nhận thông báo khi một lần tải lên hoàn thành hoặc khi kết nối thay đổi, thay vì phải kiểm tra định kỳ. Tạo endpoint trong dashboard tại [hub.madiad.com/dashboard/webhooks](https://hub.madiad.com/dashboard/webhooks): đặt URL nhận sự kiện và chọn loại sự kiện (`post.completed`, `connection.connected`, `connection.disconnected`, `connection.reauth_required`). Hub ký mọi callback bằng HMAC-SHA256. Xem [Webhooks](/vi/concepts/webhooks) để biết cách xác minh. ## Bước tiếp theo > [card] [Mô hình Profiles](/vi/concepts/profiles) > > Cách các Profiles hoạt động qua nhiều thương hiệu và khách hàng. > [card] [Trường theo nền tảng](/vi/concepts/posts) > > Ghi đè caption, thumbnail và chế độ hiển thị cho từng nền tảng. > [card] [Lên lịch](/vi/concepts/scheduling) > > Thời điểm cụ thể so với các khung giờ trong hàng đợi lặp lại. > [card] [Idempotency](/vi/concepts/idempotency) > > Thực hiện retry an toàn từ bất kỳ client nào.