Skip to main content

Authentication

The public API uses Bearer Token (JWT). Sign in to get a token and send it with each request.

Bearer Token​

Step 1: Sign In​

curl -X POST https://dashboard.socialprofiler.com/api/v1/auth/signin \
-H "Content-Type: application/json" \
-d '{
"email": "your@email.com",
"password": "your_password",
"token": true
}'

Response:

{
"status": "success",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

The token field in the request body only controls whether the JWT is returned in the response body. Sign-in also sets a session cookie regardless of token, so a browser client that relies on the cookie can omit it.

remember is accepted on the request but currently has no effect on token lifetime.

Step 2: Use Token in Requests​

curl https://dashboard.socialprofiler.com/api/v1/auth/info \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Token Lifetime and Refresh​

JWTs are valid for a server-configured duration (24 hours in the current configuration). To get a fresh token without re-sending the password, call POST /auth/token/refresh — it issues a new JWT from the session cookie set at sign-in (works even after the previous JWT has expired, as long as the session cookie is still valid):

curl -X POST https://dashboard.socialprofiler.com/api/v1/auth/token/refresh \
-H "Cookie: <session cookie from sign-in>"

Response: same shape as sign-in, {"status": "success", "token": "..."}. Returns 401 if the session has no signed-in user (e.g. the cookie is missing, cleared, or expired).

Calling POST /auth/signin again with the password also works and is not deprecated.

Sign Up​

curl -X POST https://dashboard.socialprofiler.com/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "your@email.com",
"password": "your_password",
"token": "<recaptcha-token>"
}'

Here token is a reCAPTCHA token string — required — not the boolean used by sign-in. Sign-up is meant to be driven by a browser that can solve the challenge. Optional fields: promocode, acquisition_code (see below).

On success (200 OK, {"status": "success"}) the response does not contain a JWT. Sign-up does not sign the user in automatically at the HTTP level — call POST /auth/signin with "token": true right after to get one.

Public Endpoints​

Some endpoints are public and do not require a token:

EndpointPurpose
POST /auth/signinSign in
POST /auth/token/refreshRefresh the JWT from the session cookie
POST /auth/signupRegister a new account
POST /auth/recoveryRequest password recovery
GET /auth/recovery/{hash}Validate a recovery link
POST /auth/recovery/{hash}Complete password recovery
POST /auth/confirm/{hash}Confirm an account via an emailed link
POST /auth/googleGoogle OAuth sign in
GET /sse/{reportId}Stream report updates (Bearer optional; ownership still checked)

POST /auth/signout, GET /auth/info, and DELETE /auth/google require a Bearer token.

Google OAuth​

For applications that want to offer "Sign in with Google":

curl -X POST https://dashboard.socialprofiler.com/api/v1/auth/google \
-H "Content-Type: application/json" \
-d '{
"clientId": "your_google_client_id",
"credential": "google_jwt_token",
"select_by": "user"
}'

Response:

{
"status": "success",
"isNewUser": false
}

isNewUser tells you whether this call created a new account or matched an existing one. Like sign-in, the response body contains a JWT token only when the request also sets "token": true.

DELETE /auth/google (authenticated, Bearer required) unlinks the Google account from the current user.

Attribution: acquisition_code (optional)​

Both POST /auth/signup and POST /auth/google accept an optional acquisition_code string (max 256 chars) — a campaign / attribution marker captured from the landing page (e.g. utm_campaign, ref, src, acquisition cookie). It is independent of promocode and is persisted on the user record and forwarded to Stripe metadata on subscription events.

curl -X POST https://dashboard.socialprofiler.com/api/v1/auth/google \
-H "Content-Type: application/json" \
-d '{
"clientId": "your_google_client_id",
"credential": "google_jwt_token",
"select_by": "user",
"acquisition_code": "utm_campaign=spring_launch"
}'

Same field is accepted on /auth/signup alongside promocode.

Password Recovery​

  1. POST /auth/recovery with {"email": "..."} — sends a recovery email to the address on file. Always responds {"status": "success"} — the response does not reveal whether the email is registered.
  2. GET /auth/recovery/{hash} — validates that the recovery link (from the email) is still valid.
  3. POST /auth/recovery/{hash} with {"password": "<new password>"} — sets the new password. This does not sign the user in or return a token; call POST /auth/signin afterwards.

Account Confirmation​

POST /auth/confirm/{hash} confirms the account using the hash from the confirmation email. No request body.

Error Responses​

A validation/authentication failure on POST /auth/signin, POST /auth/signup, POST /auth/recovery, POST /auth/recovery/{hash}, and POST /auth/google is reported as HTTP 200 with "status": "error" and a fieldError, not as a 4xx status:

{
"status": "error",
"fieldError": {
"field": "password",
"message": "wrong password"
}
}

Always check the status field on these endpoints, not only the HTTP status code. field is one of email, password, or promocode. For a bad/expired/exhausted promocode the message is always the generic "Promo code is invalid, expired or was used up to the limit." regardless of the specific reason.

401 Unauthorized with the general error shape is used for a missing, invalid, or expired Bearer token / session on an endpoint that requires one — this is a different failure mode from the field errors above:

{
"code": 401,
"message": "access denied"
}

403 Forbidden​

{
"code": 403,
"message": "access denied"
}

Causes:

  • Token valid but the report/resource belongs to another user
  • Report already generated (see Error Handling)

Security Best Practices​

  1. Never expose tokens in client-side code - Use server-side proxy for API calls
  2. Use HTTPS only - All API endpoints require HTTPS
  3. Store tokens securely - Use secure storage (Keychain, encrypted preferences)
  4. Handle token expiry - Call POST /auth/token/refresh (needs the session cookie) or sign in again

Code Examples​

JavaScript/Node.js​

const axios = require('axios');

const API_BASE = 'https://dashboard.socialprofiler.com/api/v1';

// Sign in and get token
async function authenticate(email, password) {
const response = await axios.post(`${API_BASE}/auth/signin`, {
email,
password,
token: true
});
return response.data.token;
}

// Create authenticated client
function createClient(token) {
return axios.create({
baseURL: API_BASE,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
}

// Usage
const token = await authenticate('user@example.com', 'password');
const client = createClient(token);
const userInfo = await client.get('/auth/info');

Python​

import requests

API_BASE = 'https://dashboard.socialprofiler.com/api/v1'

def authenticate(email: str, password: str) -> str:
response = requests.post(f'{API_BASE}/auth/signin', json={
'email': email,
'password': password,
'token': True
})
response.raise_for_status()
return response.json()['token']

def create_session(token: str) -> requests.Session:
session = requests.Session()
session.headers.update({
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
})
return session

# Usage
token = authenticate('user@example.com', 'password')
session = create_session(token)
user_info = session.get(f'{API_BASE}/auth/info').json()

Go​

package main

import (
"bytes"
"encoding/json"
"net/http"
)

const apiBase = "https://dashboard.socialprofiler.com/api/v1"

type SignInRequest struct {
Email string `json:"email"`
Password string `json:"password"`
Token bool `json:"token"`
}

type SignInResponse struct {
Status string `json:"status"`
Token string `json:"token"`
}

func authenticate(email, password string) (string, error) {
body, _ := json.Marshal(SignInRequest{
Email: email,
Password: password,
Token: true,
})

resp, err := http.Post(apiBase+"/auth/signin", "application/json", bytes.NewBuffer(body))
if err != nil {
return "", err
}
defer resp.Body.Close()

var result SignInResponse
json.NewDecoder(resp.Body).Decode(&result)
return result.Token, nil
}