j7tracker

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/json

x-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.

JobMethodPathBody
Check everythingGET/api/watched-accountsnone
Add a custom accountPOST/api/accounts{ handle }
Add available accountsPOST/api/accounts/available{ handles }
Remove an accountPOST/api/remove-account{ handle }

Account kinds

KindWhat it isCosts
Main feedAccounts J7 tracks for everyoneNothing. You already get these
CustomAn X account you pay a slot for. J7 starts tracking it if nobody else was1 slot
AvailableAn account another user already pays for. It stays in the pool only while at least one user pays for itFree

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.

BodyTypeDescription
handle*stringX 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.

BodyTypeDescription
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_added 409 if you have it, not_available 404 if nobody tracks it.
  • Several: handles not in the pool are skipped silently. Compare addedHandles with 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.

BodyTypeDescription
handle*stringAny X handle
The handle is…Resultaction
Your custom accountRemoved, slot freedremoved_custom
One of your available accountsRemovedremoved_available
Anything elseHidden from your feedhidden
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"})

On this page