--- name: socialprofiler-api description: Use when an agent needs to call the SocialProfiler REST API — sign in, create a profile-analysis report by username or by name+state, pick which matched profiles to add, confirm to start loading, wait for it via /check or SSE, then read the report (interests, friends, AI summary). Covers auth, the async report lifecycle, the report-credit paywall (402/403/401/409), and soft errors that come back as HTTP 200. --- # SocialProfiler API SocialProfiler analyzes public social media profiles (Instagram, Twitter, Facebook, TikTok, LinkedIn) and returns interests, red flags, friends and an AI summary. Everything below is the public API at: ``` https://dashboard.socialprofiler.com/api/v1 ``` Human docs: https://dashboard.socialprofiler.com/docs/ Endpoint reference: https://dashboard.socialprofiler.com/docs/api/socialprofiler-api All request and response bodies are JSON unless noted. Errors use `{"code": , "message": ""}`. ## Rules - Never print, log or save the user's password or JWT. Read them from the environment (for example `SP_EMAIL`, `SP_PASSWORD`) and keep the token in memory. - Some endpoints report failure as **HTTP 200 with `"status": "error"`** in the body (`/auth/signin`, `/auth/signup`, `/auth/recovery`, `/auth/recovery/{hash}`, `/auth/google`). Always check `status`, not only the HTTP code. - Creating a report (step 2) is what spends the credit check — `POST /create` returns `402` when the account has none left. Confirming (step 5) does not itself check or spend a credit; the credit is deducted transparently the first time the report is viewed. Do not create or confirm a report unless the user asked for that specific search. - `POST /create/{reportId}/confirm` locks the report **permanently** — you cannot change the selection afterwards, and calling it again on the same report returns `409` (already confirmed) rather than re-locking it. Show the user the matched profiles and let them choose before confirming. - Send `Authorization: Bearer ` on **every** request after sign-in, including GET requests — a missing header on a GET returns `403`, not `401`. ## 1. Authenticate Sign-up needs a reCAPTCHA token, so an agent cannot create accounts. Use an existing account and sign in with `"token": true` to get a JWT in the body: ```bash curl -s -X POST https://dashboard.socialprofiler.com/api/v1/auth/signin \ -H "Content-Type: application/json" \ -d "{\"email\": \"$SP_EMAIL\", \"password\": \"$SP_PASSWORD\", \"token\": true}" # → {"status": "success", "token": ""} ``` A wrong email/password is **not** an HTTP error — it comes back as `200` with `{"status": "error", "fieldError": {"field": "password", "message": "wrong password"}}`. Check `status` before reading `token`. Send `Authorization: Bearer ` on every other call. A `401` means the token is missing or expired: call `POST /auth/token/refresh` (needs the session cookie set at sign-in) or sign in again once; stop and tell the user if it still fails. ## 2. Create a report ```bash curl -s -X POST https://dashboard.socialprofiler.com/api/v1/create \ -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \ -d '{"source": "ig", "username": "jenniferaniston"}' # → {"id": ""} ``` - By username (recommended): `{"source": "", "username": ""}`. - By name (US only): `{"name": "John Smith", "state": "CA"}` — searches across all networks. - Source codes: `ig` (Instagram), `tw` (Twitter), `fb` (Facebook), `tt` (TikTok), `li` (LinkedIn). Save `id` (the `reportId`) — every following call uses it. ## 3. Get matched profiles ```bash curl -s https://dashboard.socialprofiler.com/api/v1/create/$REPORT_ID \ -H "Authorization: Bearer $JWT" ``` Or one source: `GET /create/{reportId}/{source}?limit=20&offset=0`. **Send the Bearer token on this GET too** — omitting it returns `403 access denied`, which looks like a permission problem but is really a missing header. Each item has `id`, `source`, and may carry a `badReason` (see the table below) when it cannot be analyzed. If nothing matched, search again within the report: `POST /create/{reportId}/{source}/search` with `{"username": ""}`. | `badReason` | Meaning | |---|---| | `private` | Profile is private | | `notFound` | Profile doesn't exist | | `zeroFollows` | Profile follows no one — nothing to analyze | | `optOut` | User requested data removal | | `unloaded` | Data not loaded yet | | `unmapped` | Profile could not be mapped | | `unexpected` | Unexpected failure | Show the candidates to the user and let them pick — do not silently add every match. ## 4. Add the chosen profiles Call **once per profile** — there is no bulk "add all": ```bash curl -s -X POST https://dashboard.socialprofiler.com/api/v1/create/$REPORT_ID/ig/add \ -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \ -d '{"id": ""}' ``` Repeat with the right `source` for each additional profile (e.g. one call for the Instagram match, another for the Twitter match). The response's `status.status` is one of `inited` / `inprogress` / `done` / `error`. ## 5. Confirm — locks the report ```bash curl -s -X POST https://dashboard.socialprofiler.com/api/v1/create/$REPORT_ID/confirm \ -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \ -d '[{"source": "ig", "id": ""}]' ``` - The body must list **exactly** the profiles already added in step 4 (same `source`/`id`) — you cannot add new accounts through confirm. - **Requires a real signed-in account**, not just a guest session: returns `401` if the caller has no user id, even though steps 2–4 work for guests. - Confirm itself does not check or spend a report credit — that check already happened at step 2 (`POST /create` returns `402` when the account has none, `"to continue please upgrade your subscription"` for a signed-in user, a different message for a guest). The credit is actually deducted transparently the first time the report is viewed (step 7); if none are left by then, the report comes back with blurred/placeholder data instead of an error. - On an already-confirmed report, returns `409` with `"report profiles were already confirmed"` — treat this as "already started", not as a failure; move on to step 6. - Response: `{"estimatedTime": , "loadTimeout": }`. `estimatedTime: 0` means data is already loaded. ## 6. Wait for loading to finish ```bash curl -s https://dashboard.socialprofiler.com/api/v1/check/$REPORT_ID -H "Authorization: Bearer $JWT" # → {"ready": true, "generated": true, "inProgress": false} ``` - `ready: true` — main report data is available. - `generated: true` — the full report including the AI summary is ready. - `loadError` / `errorSource` — set when at least one added profile failed to load (loading failure is per-profile, not an HTTP error for the whole report — the report may still be usable for the profiles that succeeded). - `GET /check/{reportId}` returns `404` (not `403`) when the caller does not own the report. Prefer SSE for a live agent loop: `GET /sse/{reportId}` (`Accept: text/event-stream`), Bearer optional but recommended. Events: `ping` (`inited` then `inprogress` every ~15s), `follows` (`inprogress`/`done`/`error` per profile, `error` field uses the same codes as `badReason`), `summary` (`inprogress`/`done`), `confirm`. Stop waiting once `summary` reports `done` (or after a few minutes — poll `/check` as a fallback if the stream drops). ## 7. Read the report ```bash curl -s https://dashboard.socialprofiler.com/api/v1/report/$REPORT_ID -H "Authorization: Bearer $JWT" ``` - `GET /report/{reportId}` — slim **ReportPage** (per-source rows, previews, merged highlights). Default choice; supports `?sortByRedFlags=true&limit=…` and `If-None-Match` for a 304. - `GET /report/{reportId}/full` — monolithic per-profile payload (`ReportItem[]`). Prefer the slim page plus the calls below for new integrations. - `GET /report/{reportId}/interests` — full interest category trees; `?source=ig` for one network. - `GET /report/{reportId}/summary` — AI summary; may still be generating (poll or use SSE). - `GET /report/{reportId}/{source}` — one source's report data. - `GET /report/{reportId}/friends` (and `/friends/{source}`) — friends list, `limit`/`offset`. - `GET /report/{reportId}/{source}/common/{friendId}` — shared interests with a friend. - `GET /report/{reportId}/{source}/{interestId}` and `.../profiles` — interest detail / members. - `GET /report/{reportId}/{source}/diffs?date=2024-01&type=new` — interest changes over time. Report IDs are persistent — reports can be fetched again later without re-running the flow. ## Errors | Status | Meaning | Action | |---|---|---| | `200` with `"status":"error"` | Auth field error (signin/signup/recovery/google) | Read `fieldError.field`/`message`; do not treat as success. | | `400` | Bad input, or confirm sent an id that was not added | Read `message`; fix the call. | | `401` | No/expired token, or confirm called by a guest session | Refresh or sign in again once; for confirm, ensure the account is signed in, not a guest. | | `402` | No report credits left (`POST /create`, not confirm) | Tell the user; do not retry automatically. | | `403` | Missing Bearer on a GET, report already generated, or report belongs to another user | Add the header, or stop — the report is locked or not yours. | | `404` | Report/profile/interest/friend not found, or `/check` on a report you don't own | Verify the id. | | `408` | Search timed out | Retry once after a short delay. | | `409` | Confirm called again on an already-confirmed report | Treat as already-started; move on to waiting/reading the report. | | `503` | Backend temporarily unavailable | Retry with backoff. | ## Other useful calls | Call | Purpose | |---|---| | `POST /auth/token/refresh` | New JWT from the session cookie, without re-sending the password. | | `GET /auth/info` | Current authenticated user. | | `POST /auth/signout` | End the session when done. | | `GET /interests/{source}/redzone` | Categories flagged as "red flag" interests, per source. |