--- name: face2social-api description: Use when an agent needs to call the Face2Social REST API: sign in, upload a photo for a face search, wait for it to finish, pick matches and read the unlocked social profiles, or check credits, subscription and search history. Covers auth, the async search lifecycle, the paywall (paymentStage, credits, search limit) and soft errors that come back as HTTP 200. --- # Face2Social API Face2Social takes a photo, finds faces in it and returns matching social media profiles. Everything below is the public API at: ``` https://dashboard.face2social.com/api/v1 ``` Human docs: https://dashboard.face2social.com/docs/ Endpoint reference: https://dashboard.face2social.com/docs/api/landing 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 `F2S_EMAIL`, `F2S_PASSWORD`) and keep the token in memory. - Some endpoints report failure as **HTTP 200 with `"status": "error"`** in the body (settings, account deletion, `/ref/{code}`, some auth calls). Always check `status`, not only the HTTP code. - Each search costs one credit when its results are unlocked. Do not upload, select or open results unless the user asked for that search. - `POST /search/{searchId}/select` can be called **once** per search. Ask the user which matches to pick before calling it. - Only search for people the user has a lawful reason to identify. If `showAgeRestrictionPopup` is `true`, the user must confirm the age restriction terms before you continue (see step 4). ## 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.face2social.com/api/v1/auth/signin \ -H "Content-Type: application/json" \ -d "{\"email\": \"$F2S_EMAIL\", \"password\": \"$F2S_PASSWORD\", \"token\": true}" # → {"status": "success", "token": ""} ``` Send `Authorization: Bearer ` on every other call. A `401` means the token is missing or expired: sign in again once, then stop and tell the user if it still fails. ## 2. Check the account before searching ```bash curl -s https://dashboard.face2social.com/api/v1/users/me -H "Authorization: Bearer $JWT" ``` Read these fields of the `User` object: | Field | Meaning | |---|---| | `canSearch` | Only an explicit `false` blocks a new search (search limit reached; buy a plan). Missing means allowed. | | `paymentStage` | `selection` or `results`: which step charges the credit. Missing or `null` means `selection`. | | `credits.available` | Credits left. `GET /credits` returns the same `CreditsInfo`. | | `subscription` | Current plan, if any. | ## 3. Upload the photo ```bash curl -s -X POST https://dashboard.face2social.com/api/v1/search/upload \ -H "Authorization: Bearer $JWT" \ -F "file=@/path/to/photo.jpg" # → {"searchId": "...", "status": "processing", "detectedFaces": 1} ``` - `402` here always means **search limit reached** (a user without a subscription has used the free searches). Upload never spends credits. Tell the user they need a plan: `GET /subscription/plans` lists them. - `detectedFaces: 0` means no face was found. Tell the user and ask for another photo. ## 4. Wait for the search to finish Poll every 2–5 seconds, up to about 3 minutes: ```bash curl -s https://dashboard.face2social.com/api/v1/search/$SEARCH_ID/status -H "Authorization: Bearer $JWT" ``` | `status` | What to do | |---|---| | `processing` | Keep polling. | | `preview` | Partial result with a `thumbnail`. Keep polling for `completed`. | | `completed` | Go to step 5. | | `error` | Stop and report `message` to the user. | A client that can read Server-Sent Events can use `GET /search/{searchId}/status/stream` instead; it sends the same `SearchStatusResponse` objects. If the status response has `showAgeRestrictionPopup: true`, show the user the age restriction notice. Only after the user agrees, call `POST /search/{searchId}/age-restriction-consent` (returns `204`). ## 5. Get matches ```bash curl -s https://dashboard.face2social.com/api/v1/search/$SEARCH_ID/matches -H "Authorization: Bearer $JWT" ``` The response has `groups` (one per social network, first page of matches each), `totalCount` and `maxSelectable`. Pictures are blurred at this step. Load more matches for one network with `GET /search/{searchId}/matches/{source}?cursor=&limit=20`. `totalCount: 0` means nothing was found. Stop and tell the user. ## 6. Select matches Show the candidates to the user and let them choose. Then send the chosen ids, at most `maxSelectable` of them. An empty array means "none of these": ```bash curl -s -X POST https://dashboard.face2social.com/api/v1/search/$SEARCH_ID/select \ -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \ -d '{"matchIds": [""]}' ``` - `paymentStage = selection`: this call spends the credit. `402` = no credits left. - `paymentStage = results`: nothing is charged here; the charge happens in step 7. - `400`: already selected, or more than `maxSelectable` ids. Do not retry with other ids; go to step 7. ## 7. Read results ```bash curl -s https://dashboard.face2social.com/api/v1/search/$SEARCH_ID/results -H "Authorization: Bearer $JWT" ``` Returns `consensusName` and `profiles[]` (`source`, `username`, `fullname`, `profilePicture`, `bio`, `profileUrl`, `isPrivate`). - With `paymentStage = results`, the first call spends one credit. With no credits, the call still returns `200` but with `isBlurred: true` and masked data. Opening the same search again never charges twice. - `isBlurred: true` → tell the user the results are locked until they buy credits or a plan. Do not present masked fields as real data. - `400` = matches not selected yet (go back to step 6). - Add `?getSocialprofiler=true` to include the Social Profiler report when it is ready, or call `GET /search/{searchId}/socialprofiler`. ## Other useful calls | Call | Purpose | |---|---| | `GET /search/history?limit=20&cursor=...` | Past searches, cursor-paginated. | | `GET /search/{searchId}` | One search's details. | | `DELETE /search/{searchId}` | Remove a search from history. Confirm with the user first. | | `GET /credits` | Credit balance (`available`, `used`, `total`) and free-search usage (`searchesUsed`, `searchesLimit`). | | `GET /subscription/plans` | Plans, no token needed. | | `GET /subscription/info` | Current subscription and billing-portal URL. | | `POST /subscription/start/{planId}` | Starts a Stripe checkout; returns `checkout_url`, which the **user** must open. Never pay on the user's behalf. | | `POST /auth/signout` | End the session when done. | ## Errors | Status | Meaning | Action | |---|---|---| | `400` | Bad input, or a step called in the wrong order | Read `message`; fix the call or the order. | | `401` | No or expired token | Sign in again once. | | `402` | Upload: search limit reached. Select: no credits. | Tell the user; do not retry. | | `404` | Search not found, or not owned by this user | Check the `searchId`. | | `5xx` | Server error | Retry once after a few seconds, then report. |