curl --request PATCH \
--url https://api.zenzap.co/v2/topics/{topicId}/cover-image \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--header 'X-Signature: <api-key>' \
--form filePart='@example-file'import requests
url = "https://api.zenzap.co/v2/topics/{topicId}/cover-image"
files = { "filePart": ("example-file", open("example-file", "rb")) }
headers = {
"Authorization": "Bearer <token>",
"X-Signature": "<api-key>"
}
response = requests.patch(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('filePart', '<string>');
const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'X-Signature': '<api-key>'}
};
options.body = form;
fetch('https://api.zenzap.co/v2/topics/{topicId}/cover-image', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zenzap.co/v2/topics/{topicId}/cover-image",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data",
"X-Signature: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zenzap.co/v2/topics/{topicId}/cover-image"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("X-Signature", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.zenzap.co/v2/topics/{topicId}/cover-image")
.header("Authorization", "Bearer <token>")
.header("X-Signature", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zenzap.co/v2/topics/{topicId}/cover-image")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["X-Signature"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "550e8400-e29b-41d4-a716-446655440000",
"updatedAt": 1699564800000
}"text is required""unauthorized""Topic not found""internal server error"Set or update a topic cover image
Upload a cover image for an existing topic. Send the image as multipart/form-data with a
filePart (the image).
Authorization: Your API key bot must be a member of the topic (returns 404 if not).
Cover image requirements:
- must be a valid image (JPEG or PNG); undecodable images return 400
- non-square images are automatically center-cropped to a square
- max dimension 4096×4096; larger images return 400
- max size 8 MB; converted to JPEG automatically
Behavior: The image is uploaded first; the topic’s cover is only updated once the upload succeeds, so a topic never references a missing image.
curl --request PATCH \
--url https://api.zenzap.co/v2/topics/{topicId}/cover-image \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--header 'X-Signature: <api-key>' \
--form filePart='@example-file'import requests
url = "https://api.zenzap.co/v2/topics/{topicId}/cover-image"
files = { "filePart": ("example-file", open("example-file", "rb")) }
headers = {
"Authorization": "Bearer <token>",
"X-Signature": "<api-key>"
}
response = requests.patch(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('filePart', '<string>');
const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'X-Signature': '<api-key>'}
};
options.body = form;
fetch('https://api.zenzap.co/v2/topics/{topicId}/cover-image', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zenzap.co/v2/topics/{topicId}/cover-image",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data",
"X-Signature: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zenzap.co/v2/topics/{topicId}/cover-image"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("X-Signature", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.zenzap.co/v2/topics/{topicId}/cover-image")
.header("Authorization", "Bearer <token>")
.header("X-Signature", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zenzap.co/v2/topics/{topicId}/cover-image")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["X-Signature"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"filePart\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "550e8400-e29b-41d4-a716-446655440000",
"updatedAt": 1699564800000
}"text is required""unauthorized""Topic not found""internal server error"Authorizations
Bearer token for the request. Two flavors:
- Static API key — pass your API key (the value returned as
apiKeywhen the bot was created). Must be paired withX-Signature+X-Timestamp(thehmacSignaturescheme). - OAuth access token — pass the JWT returned by
POST /oauth/token. No signature headers are required.
HMAC-SHA256 signature for request verification. Required only when authenticating with a static API key. Omit when using an OAuth access token.
Headers
HMAC signature of the request for authentication and replay protection.
Required only when authenticating with a static API key. If you are using an OAuth access token (issued by POST /oauth/token), omit this header — the JWT carries all the authentication and integrity guarantees.
Replay Protection: The signature includes a timestamp to prevent replay attacks. Requests with timestamps older than 5 minutes are rejected.
The signature payload differs by HTTP method:
- POST/PUT/PATCH/DELETE: HMAC-SHA256 of
{timestamp}.{body} - GET: HMAC-SHA256 of
{timestamp}.{uri}
The signature is calculated as:
- Get the current Unix timestamp in milliseconds
- Determine the payload:
- For POST/PUT/PATCH/DELETE: Use
{timestamp}.{body}where body is the request body - For GET: Use
{timestamp}.{uri}where uri is the full request URI (e.g.,/v2/members?limit=10)
- For POST/PUT/PATCH/DELETE: Use
- Calculate HMAC-SHA256 of the combined payload using your API secret
- Hex-encode the output
- Include the timestamp in the
X-Timestampheader
Example for GET request to /v2/members?limit=10:
timestamp = 1699564800000 payload = "1699564800000./v2/members?limit=10" signature = HMAC-SHA256(secret, payload) X-Signature: hex(signature) X-Timestamp: 1699564800000
Example for POST request with body {"topicId":"123","text":"Hello"}:
timestamp = 1699564800000 payload = '1699564800000.{"topicId":"123","text":"Hello"}' signature = HMAC-SHA256(secret, payload) X-Signature: hex(signature) X-Timestamp: 1699564800000
For multipart/form-data requests, sign the exact raw request body bytes
(including boundaries and file bytes) as transmitted.
^[a-f0-9]{64}$"a3d5f8e7c2b1d4f6a8e9c7b5d3f1a2e4b6c8d0f2e4a6b8c0d2e4f6a8b0c2d4e6"
Unix timestamp in milliseconds when the request was created. Used for replay protection — requests older than 5 minutes are rejected.
Required only when authenticating with a static API key. Omit when using an OAuth access token.
1699564800000
Path Parameters
The ID of the topic to set the cover image for
Body
Multipart body for setting/updating a topic cover image.
Cover image file. Must be a valid image (JPEG or PNG), max 4096×4096 and 8 MB. Automatically converted to JPEG and center-cropped to a square. Oversized or invalid images return 400.