Connect API (REST)
Introduction
The Octopus Connect API lets you programmatically read your community's content — posts, comments, replies, user profiles, and engagement metrics. It is read-only and intended for server-side use.
API Versioning
The Connect API is versioned. Include the version in the request path:
https://connect.octopuscommunity.com/v<VERSION_NUMBER>
The following table lists the available versions:
| Version number | Status |
|---|---|
| 1 | available |
For more details on changes between versions, see the Changelog.
Authentication
The Octopus Connect API uses a Connect API secret key to authenticate requests.
Pass the Connect API secret key (provided by the Octopus team) in the standard Authorization header using the Bearer scheme:
curl -H "Authorization: Bearer <YOUR_CONNECT_API_KEY>" <URL>
Connect API secret keys are prefixed with connect-secret-.
The Connect API secret key is distinct from the SDK API key — calls to the REST API with an SDK API key will fail.
Treat it like a password: anyone with it can read your community's content. For this reason, call the Connect API from your server-side code only — never from a browser, mobile app, or other client where the key would be exposed.
API requests
To make a REST API request, send an HTTP GET to the API service URL with the resource URI (including the version number) in the path, and the Connect API secret key in the request headers.
The URL to the API service is:
https://connect.octopuscommunity.com
All API endpoints are GET requests that accept query parameters, for example:
curl --location 'https://connect.octopuscommunity.com/v1/users/profile?octopusId=<TARGETED_ID>' \
--header 'Authorization: Bearer <YOUR_CONNECT_API_KEY>'
All API requests must be made over HTTPS. Calls made over plain HTTP will fail.
API responses
HTTP status codes
Successful requests
| Status code | Description |
|---|---|
200 OK | The request succeeded |
Failed requests
| Status code | Description | Possible causes and solutions |
|---|---|---|
400 Bad Request | The request is malformed or violates the schema. | Verify your parameters meet the documented requirements. |
401 Unauthorized | The Connect API secret key is not provided. | Ensure the Authorization header is set with a Bearer token. See Authentication section. |
403 Forbidden | The Connect API secret key is not valid. | Check that you are using the right Connect API secret key. |
404 Not Found | The specified resource does not exist. | The URI is incorrect, or the resource is no longer available. |
429 Too Many Requests | Too many requests — rate limit reached. | You have reached your rate limit. Wait until the rate limit resets. See Rate limiting section. |
500 Internal Server Error | Something unexpected happened on our servers. | Retry later; if the issue persists, contact the Octopus team. |
Endpoints
These endpoints let you retrieve the content of your community.
Get a list of feed items
Your community content is organized into feeds, each grouping messages in a different way for display. This endpoint returns pages of feed items for:
- Posts in a specified topic, sorted by creation date
- Comments on a specified post, sorted by creation date
- Replies to a specified comment, sorted by creation date
- Posts by a specified user, sorted by creation date
- All posts, sorted by creation date
For the first four feeds, the following parameters are available:
- The ID of the parent message (topic, post, or comment)
- The
pageSizedefines the number of items per page. It must be between 1 and 20 and defaults to 5. - The sort
order, which can beAscorDesc.Ascreturns items from oldest to newest, whileDescreturns items from newest to oldest. The default isDesc. - The
pageCursoris an opaque cursor returned in the previous response. Pass it to get the next page of feed items; leave it blank for your first request. Because the cursor encodes the sort order, anyorderparameter passed alongside it is ignored.
To retrieve all posts, you can use these parameters:
originTime: if you want to start at a specific timestamp. Expected format: a Unix timestamp in milliseconds.order: can beAscorDesc.Ascreturns items from oldest to newest, whileDescreturns items from newest to oldest. The default isDesc.direction: can beBeforeorAfter. If you provide anoriginTime,Afterretrieves all posts more recent than this timestamp, andBeforereturns all posts created before this timestamp. The default isBefore.- The
pageSizedefines the number of posts per page. It must be between 1 and 20 and defaults to 5. - The
pageCursoris an opaque cursor returned in the previous response. Pass it to get the next page of posts; leave it blank for your first request. Because the cursor encodes the sort order and direction, anyorderordirectionparameters passed alongside it are ignored.
Feed items are returned as if they were requested by an anonymous user, so banned messages are not included in the response.
A banned or shadow-banned message can still be fetched by its Octopus ID through the messages endpoints.
Get details of a message item
With a message ID, you can retrieve the following details about the targeted content:
- Body of the message: text, image URL or video URL, poll options, call-to-action button.
- Metrics: view count, reactions, poll results
- The five latest answers — only available when the message is a post or comment — if
retrieveLatestCommentsorretrieveLatestRepliesis set totrue - A cursor to retrieve the next page of replies, when more replies exist and were requested.
By using the query parameter clientObjectId instead of postId, you can retrieve a bridge post by the client object ID you assigned to it (the same ID passed to the SDK when the bridge post was created).
All message text is returned in its original language. Use the get translations endpoint to fetch all existing translations.
Get details about a profile
With either an octopusId or a loginId as a query parameter, you can get:
- Octopus ID of the user
- Nickname
- Avatar URL
- Biography
- Total number of messages the user has written (all types)
- Account age in days
- Gamification level (if gamification is enabled for your community)
- Five most recent posts (when
retrieveLatestPosts=true)
Other useful endpoints
Get all topics
This endpoint is useful if you want to list all topics in your community and get their IDs, for example to set up bridge posts.
Get all translations of a message
This endpoint returns all translations generated for the text parts of a message. Translations are grouped by language code and provided for the following fields:
- text
- poll options
- call-to-action button
- catchPhrase (for bridge posts)
Tooling
Postman Collection
To help you get started quickly, we provide a Postman collection that includes all available API endpoints with example requests and responses.
How to use the Postman Collection:
- Click the "Download Collection" button above (if it opens in the browser, right-click and select "Save As...").
- Open Postman and click "Import" in the top left
- Select the downloaded JSON file or drag & drop it
- Once imported, set your Connect API secret key:
- Go to the collection variables tab
- Set the
API_KEYvariable to your actual Connect API secret key (provided by the Octopus team)
- You're ready to make requests!
The collection includes:
- Users: Get public user profiles
- Feeds: Retrieve feeds of posts, comments, and replies
- Messages: Get detailed information about topics, posts, comments, and replies
The Postman collection is automatically configured with the correct base URL and authentication headers. Just add your Connect API secret key and start testing!
API reference (ReDoc)
You can also browse the v1 ReDoc API reference for full details on every available endpoint.
Example use cases
Display engagement counters on an article linked to a bridge post
- Java
- Python
- Node.js
Code sample
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class OctopusApiClient {
private static final String API_KEY = "YOUR_CONNECT_API_KEY";
private static final String BASE_URL = "https://connect.octopuscommunity.com/v1/messages/post";
// Reaction unicode → Map your own reactions here
private static final Map<String, String> REACTION_LABELS = new HashMap<>();
static {
REACTION_LABELS.put("❤\uFE0F", "❤️");
REACTION_LABELS.put("\uD83D\uDE02", "😂");
REACTION_LABELS.put("\uD83D\uDE2E", "😮");
REACTION_LABELS.put("\uD83D\uDC4F", "👏");
REACTION_LABELS.put("\uD83D\uDE22", "😢");
REACTION_LABELS.put("\uD83D\uDE21", "😡");
}
public static void main(String[] args) throws Exception {
String clientObjectId = "YOUR_CLIENT_OBJECT_ID";
String url = String.format(
"%s?clientObjectId=%s&retrieveLatestComments=false",
BASE_URL, URLEncoder.encode(clientObjectId, StandardCharsets.UTF_8)
);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.err.println("Request failed: HTTP " + response.statusCode());
System.err.println(response.body());
return;
}
parseAndPrintMetrics(response.body());
}
private static void parseAndPrintMetrics(String json) {
JSONObject root = new JSONObject(json);
JSONObject post = root.getJSONObject("post");
// metrics can be null per the schema
if (post.isNull("metrics")) {
System.out.println("No metrics available for this post.");
return;
}
JSONObject metrics = post.getJSONObject("metrics");
int childrenCount = metrics.optInt("childrenCount", 0);
int viewCount = metrics.optInt("viewCount", 0);
System.out.println("===== Post Metrics =====");
System.out.println("💬 Comments : " + childrenCount);
System.out.println("👁 Views : " + viewCount);
// --- Reactions ---
JSONArray reactions = metrics.optJSONArray("reactions");
System.out.println("\n----- Reactions -----");
if (reactions == null || reactions.isEmpty()) {
System.out.println("No reactions yet.");
} else {
for (int i = 0; i < reactions.length(); i++) {
JSONObject reaction = reactions.getJSONObject(i);
String unicode = reaction.getString("unicode");
int count = reaction.getInt("count");
String label = REACTION_LABELS.getOrDefault(unicode, unicode);
System.out.printf(" %s x%d%n", label, count);
}
}
System.out.println("========================");
}
}
Code sample
import requests
API_KEY = "YOUR_CONNECT_API_KEY"
BASE_URL = "https://connect.octopuscommunity.com/v1/messages/post"
# Reaction unicode → Map your own reactions here
REACTION_LABELS = {
"❤\uFE0F": "❤️",
"\uD83D\uDE02": "😂",
"\uD83D\uDE2E": "😮",
"\uD83D\uDC4F": "👏",
"\uD83D\uDE22": "😢",
"\uD83D\uDE21": "😡"
}
def fetch_post_metrics(client_object_id):
response = requests.get(
BASE_URL,
params={"clientObjectId": client_object_id, "retrieveLatestComments": "false"},
headers={
"Accept": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
)
response.raise_for_status()
return response.json()
def print_metrics(data):
post = data.get("post", {})
metrics = post.get("metrics")
if not metrics:
print("No metrics available for this post.")
return
print("===== Post Metrics =====")
print(f"💬 Comments : {metrics.get('childrenCount', 0)}")
print(f"👁 Views : {metrics.get('viewCount', 0)}")
reactions = metrics.get("reactions", [])
print("\n----- Reactions -----")
if not reactions:
print("No reactions yet.")
else:
for r in reactions:
label = REACTION_LABELS.get(r["unicode"], r["unicode"])
print(f" {label} x{r['count']}")
print("========================")
if __name__ == "__main__":
client_object_id = "YOUR_CLIENT_OBJECT_ID"
data = fetch_post_metrics(client_object_id)
print_metrics(data)
Code sample
const API_KEY = "YOUR_CONNECT_API_KEY";
const BASE_URL = "https://connect.octopuscommunity.com/v1/messages/post";
// Reaction unicode → Map your own reactions here
const REACTION_LABELS = {
"❤\uFE0F": "❤️",
"\uD83D\uDE02": "😂",
"\uD83D\uDE2E": "😮",
"\uD83D\uDC4F": "👏",
"\uD83D\uDE22": "😢",
"\uD83D\uDE21": "😡"
};
async function fetchPostMetrics(clientObjectId) {
const params = new URLSearchParams({
clientObjectId,
retrieveLatestComments: "false"
});
const res = await fetch(`${BASE_URL}?${params}`, {
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${API_KEY}`
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
return res.json();
}
function printMetrics(data) {
const metrics = data?.post?.metrics;
if (!metrics) {
console.log("No metrics available for this post.");
return;
}
console.log("===== Post Metrics =====");
console.log(`💬 Comments : ${metrics.childrenCount ?? 0}`);
console.log(`👁 Views : ${metrics.viewCount ?? 0}`);
const reactions = metrics.reactions ?? [];
console.log("\n----- Reactions -----");
if (!reactions.length) {
console.log("No reactions yet.");
} else {
reactions.forEach(({ unicode, count }) => {
const label = REACTION_LABELS[unicode] ?? unicode;
console.log(` ${label} x${count}`);
});
}
console.log("========================");
}
(async () => {
const clientObjectId = "YOUR_CLIENT_OBJECT_ID";
const data = await fetchPostMetrics(clientObjectId);
printMetrics(data);
})();
Display posts of a specific topic
- Java
- Python
- Node.js
Code sample
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import org.json.JSONArray;
import org.json.JSONObject;
public class OctopusFeedClient {
private static final String API_KEY = "YOUR_CONNECT_API_KEY";
private static final String BASE_URL = "https://connect.octopuscommunity.com/v1/feeds/posts";
public static void main(String[] args) throws Exception {
String topicId = "YOUR_TOPIC_ID";
String pageSize = "20";
String order = "Desc";
String url = String.format("%s?topicId=%s&pageSize=%s&order=%s",
BASE_URL, URLEncoder.encode(topicId, StandardCharsets.UTF_8), pageSize, order);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.err.println("Request failed: HTTP " + response.statusCode());
return;
}
JSONArray posts = new JSONObject(response.body()).getJSONArray("posts");
for (int i = 0; i < posts.length(); i++) {
JSONObject post = posts.getJSONObject(i);
String text = post.getJSONObject("content").optString("text", "(no text)");
String author = post.isNull("createdBy") ? "Unknown"
: post.getJSONObject("createdBy").optString("name", "Unknown");
System.out.println("--- Post " + (i + 1) + " ---");
System.out.println("Author : " + author);
System.out.println("Text : " + text);
}
}
}
Code sample
import requests
API_KEY = "YOUR_CONNECT_API_KEY"
BASE_URL = "https://connect.octopuscommunity.com/v1/feeds/posts"
def fetch_feed(topic_id, page_size="20", order="Desc"):
response = requests.get(
BASE_URL,
params={"topicId": topic_id, "pageSize": page_size, "order": order},
headers={
"Accept": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
)
response.raise_for_status()
return response.json()
def print_posts(data):
posts = data.get("posts", [])
for i, post in enumerate(posts, 1):
text = post.get("content", {}).get("text", "(no text)")
created_by = post.get("createdBy")
author = created_by.get("name", "Unknown") if created_by else "Unknown"
print(f"--- Post {i} ---")
print(f"Author : {author}")
print(f"Text : {text}")
if __name__ == "__main__":
data = fetch_feed("YOUR_TOPIC_ID")
print_posts(data)
Code sample
const API_KEY = "YOUR_CONNECT_API_KEY";
const BASE_URL = "https://connect.octopuscommunity.com/v1/feeds/posts";
async function fetchFeed(topicId, pageSize = "20", order = "Desc") {
const params = new URLSearchParams({ topicId, pageSize, order });
const res = await fetch(`${BASE_URL}?${params}`, {
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${API_KEY}`
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
return res.json();
}
function printPosts(data) {
const posts = data.posts ?? [];
posts.forEach(({ content, createdBy }, i) => {
const text = content?.text ?? "(no text)";
const author = createdBy?.name ?? "Unknown";
console.log(`--- Post ${i + 1} ---`);
console.log(`Author : ${author}`);
console.log(`Text : ${text}`);
});
}
(async () => {
const data = await fetchFeed("YOUR_TOPIC_ID");
printPosts(data);
})();
Display a user's latest post
- Java
- Python
- Node.js
Code sample
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import org.json.JSONArray;
import org.json.JSONObject;
public class OctopusUserProfileClient {
private static final String API_KEY = "YOUR_CONNECT_API_KEY";
private static final String BASE_URL = "https://connect.octopuscommunity.com/v1/users/profile";
public static void main(String[] args) throws Exception {
String loginId = "YOUR_LOGIN_ID";
String url = String.format("%s?loginId=%s&retrieveLatestPosts=true", BASE_URL, URLEncoder.encode(loginId, StandardCharsets.UTF_8));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
System.err.println("Request failed: HTTP " + response.statusCode());
return;
}
JSONObject root = new JSONObject(response.body());
String nickname = root.getJSONObject("profile").optString("nickname", "Unknown");
JSONArray posts = root.getJSONArray("firstPostsPage");
String text = posts.isEmpty() ? "(no posts)"
: posts.getJSONObject(0).getJSONObject("content").optString("text", "(no text)");
System.out.println("Nickname : " + nickname);
System.out.println("Latest post: " + text);
}
}
Code sample
import requests
API_KEY = "YOUR_CONNECT_API_KEY"
BASE_URL = "https://connect.octopuscommunity.com/v1/users/profile"
def fetch_user_profile(login_id):
response = requests.get(
BASE_URL,
params={"loginId": login_id, "retrieveLatestPosts": "true"},
headers={
"Accept": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
)
response.raise_for_status()
return response.json()
def print_latest_post(data):
nickname = data.get("profile", {}).get("nickname", "Unknown")
posts = data.get("firstPostsPage", [])
text = posts[0].get("content", {}).get("text", "(no text)") if posts else "(no posts)"
print(f"Nickname : {nickname}")
print(f"Latest post: {text}")
if __name__ == "__main__":
data = fetch_user_profile("YOUR_LOGIN_ID")
print_latest_post(data)
Code sample
const API_KEY = "YOUR_CONNECT_API_KEY";
const BASE_URL = "https://connect.octopuscommunity.com/v1/users/profile";
async function fetchUserProfile(loginId) {
const params = new URLSearchParams({
loginId,
retrieveLatestPosts: "true"
});
const res = await fetch(`${BASE_URL}?${params}`, {
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${API_KEY}`
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
return res.json();
}
function printLatestPost(data) {
const nickname = data.profile?.nickname ?? "Unknown";
const posts = data.firstPostsPage ?? [];
const text = posts[0]?.content?.text ?? "(no posts)";
console.log(`Nickname : ${nickname}`);
console.log(`Latest post: ${text}`);
}
(async () => {
const data = await fetchUserProfile("YOUR_LOGIN_ID");
printLatestPost(data);
})();
Rate limiting
Each Connect API secret key has a maximum number of calls that can be made in one minute. Each call consumes one credit unit, regardless of the response status.
When the rate limit has not been reached, the response includes these headers:
RateLimit-Limit: the maximum number of credit units allowed per minute for your keyRateLimit-Remaining: the number of credit units leftRateLimit-Reset: the number of seconds until your credit units are refilled
If your key is configured as unlimited, these headers are not returned.
If you exceed the limit and receive a 429 Too Many Requests response, the headers include:
Retry-After: the number of seconds until your credit units are refilled