6529.io API
Introduction
The 6529.io website communicates with its backend via a JSON-encoded REST API.
This API is open, documented, and available for external use. You can find the full reference here: https://api.6529.io/docs
Some endpoints are public, some require authentication, and some can be used in both modes—returning more contextual information if the user is authenticated.
v2 API authentication
New external clients should use session-v2 wallet authentication: request a signable message, sign it exactly, exchange the signature for an access token, then send that token as bearer auth.
Read the full external-client auth guideKey terminology
This is not a comprehensive glossary, but an overview of the most common terms you'll encounter when working with the API:
- Identity - A representation of a user in the system. An identity consists of one (or more, in case of consolidation) Ethereum wallet addresses. Authenticating as an identity involves signing a message with one of its Ethereum wallets.
- Profile - A set of properties associated with an identity. A profile may include a handle, bio, NIC statements, etc. Note: a profile is not the same as an identity.
- Brain - The social network inside 6529.io, consisting of waves where identities can communicate, vote, and share media.
- Wave - A channel within Brain. Think of it as a topic, chatroom, or sub-community Waves can be public or private. Access may differ: e.g. you might be able to view a wave but not post in it, or post but not participate in competitions.
- Drop - A message inside a wave. Interaction rules differ by type. For example, you can react to a CHAT drop but vote on a PARTICIPATORY drop. Drops types are:
- CHAT → a standard chat message
- PARTICIPATORY → a competition entry
- WINNER → a previously participatory drop elected as a winner
- Groups - Sets of identities meeting predefined criteria (e.g. TDH, REP, NIC, etc).
- Groups are used for filtering community members and regulating access to waves.
- REP - Tags with metrics that identities can attach to each other. These can be used when forming groups.
- NIC - Trust ratings that identities give each other to validate authenticity. Also usable in groups.
Authentication quickstart
Authentication is based on Ethereum signatures. For scripts and other external clients, request a native session-v2 challenge.
This example shows the short native/script flow for external API clients. Use the full guide for refresh, logout, and security notes.
The flow works as follows:
- Request a session-v2 signable message for the wallet you want to authenticate.
- Sign the signable message exactly using your wallet.
- Send the signature back to the server.
- Receive a JWT bearer token, which you can include in headers of subsequent requests.
Creating drops with embedded media
Current API supports multipart upload
The flow works as follows:
- Read the file
- Send the file name and mime type (not the file itself) to our API
- Get back upload ID and temporary S3 key
- Optional: Split the file to chunks/parts.
- Get S3 upload URL for each part from our API
- Upload each part to S3 using the signed urls gotten from previous steps and keep the ETags from responses
- When all parts have finished uploading, complete the upload by supplying the ETags to our API
- Use the media URL from completion API response to create a drop
Here's a full example in Node.js:
import fetch from "node-fetch";
import {readFile} from "fs/promises";
import {extname} from "path";
import mime from "mime-types";
async function run() {
// 0) Authenticate and get token
// ...
// 1) read file
const filePath = "/Users/exampleuser/Desktop/example_picture.jpg";
const fileBytes = await readFile(filePath);
const fileName = filePath.split(/[\\/]/).pop();
const contentType = mime.lookup(extname(fileName)) || "application/octet-stream";
console.log(`Read ${fileName} (${fileBytes.length} bytes, ${contentType})`);
// 2) start multipart
const {upload_id, key} = await startMultipartUpload({
token,
fileName,
contentType,
});
console.log("Started multipart:", {upload_id, key});
// 3) get part URL
const part_no = 1; // single-part example, but with larger files it might be worth to chunk them.
const uploadUrl = await getPartUploadUrl({token, upload_id, key, part_no});
console.log("Got pre-signed part URL");
// 4) PUT bytes to S3
const etag = await putPartToS3({uploadUrl, bytes: fileBytes, contentType});
console.log("Uploaded part, ETag:", etag);
// 5) complete
const mediaUrl = await completeMultipartUpload({
token,
upload_id,
key,
parts: [{part_no, etag}],
});
console.log("Multipart complete. media_url:", mediaUrl);
// 6) create drop
const drop = await createDrop({
token,
mediaUrl,
mimeType: contentType,
signerAddress: wallet.address,
});
console.log("Drop created:", drop);
}
async function startMultipartUpload({token, fileName, contentType}) {
const resp = await fetch("https://api.6529.io/api/drop-media/multipart-upload", {
method: "POST",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({
file_name: fileName,
content_type: contentType,
}),
});
if (!resp.ok) {
throw new Error(
`startMultipartUpload failed: ${resp.status} ${resp.statusText} - ${await resp.text()}`
);
}
return resp.json();
}
async function getPartUploadUrl({token, upload_id, key, part_no}) {
const resp = await fetch("https://api.6529.io/api/drop-media/multipart-upload/part", {
method: "POST",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({upload_id, key, part_no}),
});
if (!resp.ok) {
throw new Error(
`getPartUploadUrl failed: ${resp.status} ${resp.statusText} - ${await resp.text()}`
);
}
const {upload_url} = await resp.json();
return upload_url;
}
async function putPartToS3({uploadUrl, bytes, contentType}) {
const putResp = await fetch(uploadUrl, {
method: "PUT",
headers: {
"content-type": contentType,
},
body: bytes,
});
if (!putResp.ok) {
throw new Error(
`S3 upload part failed: ${putResp.status} ${putResp.statusText} - ${await putResp.text()}`
);
}
const rawETag = putResp.headers.get("etag") || putResp.headers.get("ETag");
const etag = rawETag?.replace(/^"+|"+$/g, "");
if (!etag) throw new Error("Missing ETag from S3 response");
return etag;
}
async function completeMultipartUpload({token, upload_id, key, parts}) {
const resp = await fetch("https://api.6529.io/api/drop-media/multipart-upload/completion", {
method: "POST",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({
upload_id,
key,
parts,
}),
});
if (!resp.ok) {
throw new Error(
`completeMultipartUpload failed: ${resp.status} ${resp.statusText} - ${await resp.text()}`
);
}
const json = await resp.json();
return json.media_url;
}
async function createDrop({token, mediaUrl, mimeType, signerAddress}) {
const body = {
title: null,
drop_type: "CHAT",
parts: [
{
content: null,
quoted_drop: null,
media: [{url: mediaUrl, mime_type: mimeType}],
},
],
mentioned_users: [],
referenced_nfts: [],
metadata: [],
signature: null,
is_safe_signature: false,
signer_address: signerAddress,
wave_id: 'TARGET_WAVE_ID_GOES_HERE'
};
const resp = await fetch("https://api.6529.io/api/drops", {
method: "POST",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify(body),
});
if (!resp.ok) {
throw new Error(`Create drop failed: ${resp.status} ${resp.statusText} - ${await resp.text()}`);
}
return await resp.json();
}
run().catch((err) => {
console.error("Error:", err);
process.exit(1);
});