Manage Accounts
Check, add and remove the X accounts on your feed.
Plain HTTP on the core host. Changes reach your open feed socket within milliseconds, with no reconnect needed.
https://core.j7tracker.io
Authorization: Bearer <jwt>
Content-Type: application/jsonx-session-id: <jwt> works instead of Authorization. Call it from a server or script: browser requests from origins other than j7tracker get 403. Handles are case-insensitive, and @, quotes and whitespace are stripped.
| Job | Method | Path | Body |
|---|---|---|---|
| Check everything | GET | /api/watched-accounts | none |
| Add a custom account | POST | /api/accounts | { handle } |
| Add available accounts | POST | /api/accounts/available | { handles } |
| Remove an account | POST | /api/remove-account | { handle } |
Account kinds
| Kind | What it is | Costs |
|---|---|---|
| Main feed | Accounts J7 tracks for everyone | Nothing. You already get these |
| Custom | An X account you pay a slot for. J7 starts tracking it if nobody else was | 1 slot |
| Available | An account another user already pays for. It stays in the pool only while at least one user pays for it | Free |
Free slots come from your deploys, plus any purchased slots: 15+ deploys = 1 slot, 50+ = 2, 100+ = 3.
Check everything
GET /api/watched-accounts
Every platform's watched list, your custom and available lists, the pool you can still add, and your hidden list. Add ?fresh=1 to skip the 10 s server cache.
interface WatchedAccountsResponse {
success: true;
x: { accounts: WatchedX[]; plan: object | null }; // Main-feed X accounts, newest first
truth: PlatformList<WatchedSocial>;
ig: PlatformList<WatchedSocial>;
bsq: PlatformList<WatchedSocial>;
tiktok: PlatformList<WatchedSocial & { state: string }>;
youtube: PlatformList<WatchedSocial & { channelId: string; profileUrl: string }>; // Identify channels by channelId
custom: {
accounts: string[]; // Your custom (slot) accounts, oldest first
availableAccounts: string[]; // Your free copies from the pool, oldest first
maxAccounts: number; // Your slot count
deployCount: number;
};
available: {
accounts: string[]; // Pool handles you can still add for free
total: number; // Pool size, including what you already have
};
hidden: string[]; // Main-feed X handles you removed (hidden)
feedPrefs: {
noAutoAddMainFeed: boolean; // true = accounts newly added to the main feed start hidden for you
};
publishedAt: Record<string, string | null>; // When each list was last refreshed. null = not loaded yet
}
interface PlatformList<T> {
accounts: T[];
limits: { current: number; max: number | '?' }; // '?' when unknown
}
interface WatchedX {
id: string;
handle: string;
name: string;
avatar: string;
}
interface WatchedSocial {
handle: string;
name: string;
avatar?: string | null;
}Slots left = custom.maxAccounts - custom.accounts.length. Errors on this route use success: false instead of ok: false.
Add a custom account
POST /api/accounts
Uses one slot and starts tracking the handle if nobody else was.
| Body | Type | Description |
|---|---|---|
handle* | string | X handle |
interface AddAccountResponse {
ok: true;
action: 'add';
account: string;
addedAsAvailable: boolean; // true = added from the pool instead, no slot used
deployCount: number;
maxAccounts: number;
message: string; // e.g. "Added @frankdegods to your custom accounts"
}
// Main-feed handle: no slot used
interface MainFeedResponse {
ok: true;
action: 'unhide';
account: string;
unhidden: boolean;
message: string;
}- Out of slots but someone else already tracks the handle? It's added as available instead (
addedAsAvailable: true). - Can take a few seconds when J7 has to start tracking a brand-new handle.
Errors: invalid_handle 400 · already_added 409 · no_deploys 403 · limit_reached 403 · tracking_failed_invalid 422 · tracking_failed_quota 429 · tracking_failed_other 502 · internal_error 500.
Add available accounts
POST /api/accounts/available
Adds one or many handles from the pool (available.accounts above). Free, never uses a slot.
| Body | Type | Description |
|---|---|---|
handles* | string[] | One or more handles, up to 25,000 |
// One handle
interface AddAvailableResponse {
ok: true;
action: 'add' | 'unhide';
account: string;
message: string; // e.g. "Added @ansem from available accounts"
}
// Two or more handles
interface AddAvailableBatchResponse {
ok: true;
action: 'add_batch';
added: number;
addedHandles: string[];
unhidden: number;
message: string; // e.g. "Added 2 available account(s)"
}- One handle: fails with
already_added409 if you have it,not_available404 if nobody tracks it. - Several: handles not in the pool are skipped silently. Compare
addedHandleswith what you sent.
Remove an account
POST /api/remove-account
One call for every removal. The server works out what the handle is to you.
| Body | Type | Description |
|---|---|---|
handle* | string | Any X handle |
| The handle is… | Result | action |
|---|---|---|
| Your custom account | Removed, slot freed | removed_custom |
| One of your available accounts | Removed | removed_available |
| Anything else | Hidden from your feed | hidden |
interface RemoveAccountResponse {
ok: true;
action: 'removed_custom' | 'removed_available' | 'hidden';
account: string;
}To bring an account back, add it again. If you were the last user paying for a custom account, J7 stops tracking it and it leaves the pool.
Errors: invalid_handle 400 · untrack_failed 502 (nothing changed, retry) · internal_error 500.
All error codes and rate limits are on Errors & Limits.
Example
BASE=https://core.j7tracker.io
AUTH="Authorization: Bearer <jwt>"
JSON="Content-Type: application/json"
curl -s $BASE/api/watched-accounts -H "$AUTH"
curl -s -X POST $BASE/api/accounts -H "$AUTH" -H "$JSON" -d '{"handle":"frankdegods"}'
curl -s -X POST $BASE/api/accounts/available -H "$AUTH" -H "$JSON" -d '{"handles":["ansem","cobie"]}'
curl -s -X POST $BASE/api/remove-account -H "$AUTH" -H "$JSON" -d '{"handle":"frankdegods"}'const BASE = "https://core.j7tracker.io";
const headers = { Authorization: "Bearer <jwt>", "Content-Type": "application/json" };
const call = async (method, path, body) => {
const res = await fetch(BASE + path, { method, headers, body: body && JSON.stringify(body) });
return res.json();
};
const all = await call("GET", "/api/watched-accounts");
console.log(`${all.custom.accounts.length}/${all.custom.maxAccounts} slots used`);
const r = await call("POST", "/api/accounts", { handle: "frankdegods" });
if (!r.ok) console.error(r.code, r.error);
await call("POST", "/api/accounts/available", { handles: ["ansem", "cobie"] });
await call("POST", "/api/remove-account", { handle: "frankdegods" });import requests
BASE = "https://core.j7tracker.io"
s = requests.Session()
s.headers["Authorization"] = "Bearer <jwt>"
all_ = s.get(f"{BASE}/api/watched-accounts").json()
print(f"{len(all_['custom']['accounts'])}/{all_['custom']['maxAccounts']} slots used")
r = s.post(f"{BASE}/api/accounts", json={"handle": "frankdegods"}).json()
if not r["ok"]:
print(r["code"], r["error"])
s.post(f"{BASE}/api/accounts/available", json={"handles": ["ansem", "cobie"]})
s.post(f"{BASE}/api/remove-account", json={"handle": "frankdegods"})