API Consumption¶
Status: Audit v0.1 (2026-05-09).
The web client consumes the Someli backend API at process.env.API_URL. ~150 distinct endpoints are referenced across pages/components/store. This doc covers how the client talks to that API.
1. HTTP client¶
| Property | Value | Source |
|---|---|---|
| Library | @nuxtjs/axios (a Nuxt module wrapping axios) |
nuxt.config.js:250 |
| Base URL | process.env.API_URL |
nuxt.config.js:265-267 |
| Single API client module | No — every call site uses this.$axios.get/post(...) directly |
grep -rEn "this\.\$axios" pages components \| wc -l = many |
| Axios call sites in pages/components | 963 | grep -rEn "this\.\$axios\.\|axios\." pages components \| wc -l |
| Custom interceptor | One request interceptor in middleware/axios.js |
middleware/axios.js:6-17 |
| Response interceptor | None | grep -nE "response\.use\|response_use" middleware/axios.js plugins/*.js store/*.js returns nothing |
| Request cancellation (AbortController) | None observed | grep -rEn "AbortController\|signal:" pages components store returns nothing |
| Retry / backoff | None | No axios-retry in deps; no manual retry wrappers observed |
Finding [WC-API-1] (Severity: High): There is no central API client wrapper. Every component opens its own this.$axios.get/post('/endpoint', ...) call, scattered across 963 call sites. Endpoint paths are typed as string literals; there is no single place to:
- Update a path when the backend renames it
- Log all requests
- Wrap a class of endpoints in a shared error handler
- Add OpenAPI-generated types
This is the single biggest "we'll regret it later" item in the API-consumption layer. Recommendation: Phase 1 — introduce a thin per-feature API service module (e.g., services/billing.js, services/posts.js) and migrate progressively.
Finding [WC-API-2] (Severity: High): There is no response interceptor. The client cannot: - Retry on 401 (token refresh) - Detect global errors (5xx, network) - Strip envelope before passing to caller
The auth strategy's logout-on-401 behaviour relies on @nuxtjs/auth doing it internally for the auth strategy's own endpoints, but all other endpoints are uncovered. A 401 from /auth/getpost does nothing automatic — the action returns the cached value silently (see routing-and-state.md Finding WC-ST-3).
Finding [WC-API-3] (Severity: Medium): No request cancellation. A user navigating between pages mid-fetch will trigger React-style "setState on unmounted component" warnings (in Vue: stale promises resolving against a destroyed component; race conditions if the user re-enters the page). Recommendation: wrap the API service modules with AbortController integration when they are introduced.
Finding [WC-API-4] (Severity: Medium): No retry/backoff anywhere. Network blips fail user actions immediately. For idempotent reads, automatic retry-with-backoff is appropriate.
2. Request interceptor¶
middleware/axios.js:1-29:
export default function ({ $axios, route, $cookies }) {
const { path, hash } = route
![PATH.REDIRECT, PATH.CALL_BACK].includes(path) && $cookies.set('currentUrl', path + hash)
$axios.interceptors.request.use((config) => {
const incomingUrl = config.url
if (incomingUrl.includes('/auth/')) {
const cookie = parseCookies(document.cookie)
config.headers.token = cookie.token || ''
if (cookie && cookie.usedetail) {
config.headers.accountId = JSON.parse(cookie.usedetail).accountId || ''
}
}
config.headers.apptype = Buffer.from(process.env.APP_TYPE, 'utf8').toString('base64')
return config
})
}
Behaviour:
- For requests whose URL contains
/auth/, attachtokenandaccountIdheaders from cookies. Note: the header key is the literal stringtoken(notAuthorization). - For every request, attach
apptypeheader (base64 ofprocess.env.APP_TYPE). - Save the current path + hash to the
currentUrlcookie (used by login redirect).
Finding [WC-API-5] (Severity: High): The auth header is token, not Authorization. This is non-standard and prevents the client from being inspected by any standard auth-aware tool (curl -H "Authorization: Bearer ..." won't work; nginx auth_request modules, OpenAPI security schemes, etc., all assume the standard). It also conflicts with the other code paths in this codebase that do set Authorization directly on the axios defaults (see Finding WC-AUTH-1 in authentication-client.md).
Finding [WC-API-6] (Severity: Medium): The interceptor parses document.cookie via a hand-rolled parseCookies instead of using the cookie-universal-nuxt plugin already in deps. Inconsistency.
Finding [WC-API-7] (Severity: Low–Medium): JSON.parse(cookie.usedetail) will throw on a malformed usedetail cookie. There is no try/catch — a corrupted cookie crashes the interceptor and breaks all subsequent requests until the cookie is cleared.
3. Authentication header¶
| Header | Where set | Format |
|---|---|---|
token |
middleware/axios.js:11 (per-request, for /auth/* URLs) |
Raw token from cookie.token |
accountId |
middleware/axios.js:13 (per-request, for /auth/* URLs) |
Raw account id from cookie.usedetail.accountId |
apptype |
middleware/axios.js:15 (every request) |
base64 of process.env.APP_TYPE |
Authorization (raw token) |
middleware/socialLogin.js:18, store/index.js:378, store/index.js:412 |
Set on axios.defaults.headers.common after specific social-login flows |
Authorization: Bearer <token> |
store/index.js:420 (in socialLinkMe) |
Standard format; only this one site uses Bearer prefix |
Finding [WC-API-8] (Severity: High): Three different Authorization formats coexist:
1. Authorization: <token> (raw, no prefix) — socialLogin.js:18, store/index.js:378,412
2. Authorization: Bearer <token> — store/index.js:420
3. token: <token> (custom header) — middleware/axios.js:11
Whether the backend accepts all three is unknown. [VERIFY-API-1]: does the backend's auth middleware accept token, Authorization, and Authorization: Bearer X interchangeably? If only one is canonical, the others are dead code at best and authentication-bypass risk at worst.
4. Endpoint inventory¶
A full categorised catalog of backend endpoints called from the frontend is in ../14-api-catalog.md (existing project doc). The audit-relevant summary:
| Group | Approximate count |
|---|---|
| Authentication & session | ~20 |
| Accounts (the workspace) | ~12 |
| Members / team management | ~11 |
| User profile & settings | ~10 |
| Subscriptions & billing | ~14 |
| Posts (lifecycle) | ~25 |
| Library, templates, media | ~20 |
| Brand kit / AI assets / org profile | ~24 |
| Topics / subjects / industries | ~13 |
| Approval / scheduling | ~6 |
| Reels & ideas | 1 |
| Logging, activity, web scraping | ~5 |
| Member chat | 1 |
Nuxt server middleware (/api/*) |
3 |
| Total | ~150 |
The actual unique endpoint count from grep:
grep -rohE "axios\.(post|get|put|delete)\([\"'][^\"']+[\"']" pages components 2>/dev/null \
| grep -oE "[\"'][^\"']+[\"']" | grep -E "^['\"]/" | sort -u | wc -l
= 134 unique endpoints referenced in pages/components, plus ~40 more in store/middleware, total ~150–170 unique endpoints.
5. Response shape handling¶
The backend returns a variety of shapes:
| Pattern | Example response |
|---|---|
{ status, data } |
Most endpoints |
{ status, data: { data: [...] } } |
Some — frontend reads data.data[0] |
{ status, data: [...], rows } |
Paginated endpoints |
{ status, data: { data: [...], pagination: { totalPages } } } |
getAllSharedImages, getMyLibraryPost |
{ status, data, code, message } |
Some auth endpoints (LinkedIn, Twitter) |
| Raw value | A few endpoints |
The frontend handles this with per-call shape unwrapping, e.g., data.data[0] or data?.data?.[0]?.field. There is no:
- Schema validation library (no Zod, Yup, io-ts in deps)
- TypeScript types (codebase is JS-only)
- Runtime envelope check
Finding [WC-API-9] (Severity: Medium): Inconsistent backend shapes are unwrapped per-call across 963 sites. This is fragile — when a backend changes a shape, the frontend silently breaks. Adding Zod or io-ts at the per-feature service layer would catch shape drift early and create executable docs of the contract.
6. Token refresh¶
There is no token refresh logic on the client. Specifically:
- No response interceptor to detect
401. - No proactive expiry detection (no JWT decoding to check
exp). - No
/auth/refreshendpoint visible in the call inventory.
The behaviour the user sees on token expiry depends on what the backend does:
- If backend returns
401, the request fails and the cached value is returned (silent). - The
redirect.jsmiddleware on the next navigation will eventually call/auth/getaccountdetails, which will return empty data, which triggers the catch-alllogout + cookies.removeAll() + localStorage.clear() + redirect /#loginpath.
So token expiry surfaces as a silent forced logout, not a graceful re-auth prompt. [VERIFY-API-2]: what is the backend token's TTL? If it's short, this UX is painful; if it's long (e.g., 30 days), it's mostly OK in practice.
Finding [WC-API-10] (Severity: Medium–High): No token-refresh path. Phase 1 effort to add (depends on backend offering a refresh endpoint).
7. Error handling¶
There is no unified API error-handler. Patterns observed:
- Try/catch swallowing:
store/api.jsactions wrap calls in try/catch and return cached data on error. - Try/catch with toast: Some components show
this.$toast.error(...)orthis.$snotify.error(...)on failure. - Component-level alert/console.log: A few components log to console.
- Bare promise: Some calls have neither
.catchnor await-wrapped try/catch — uncaught rejections may surface in the browser console.
Finding [WC-API-11] (Severity: High): Uncaught promise rejections are not piped to error tracking (no Sentry — see ./observability.md). User-visible errors and silent failures both fall through.
The dev-team-facing impact of this is documented in ../10-troubleshooting.md § "Network call returned an error but UI thinks it succeeded": developers cannot rely on a thrown rejection to indicate failure; they must inspect the Network tab. In production, the situation is worse — there's no devtools open, so the failure is invisible to everyone until a user complains. Cross-ref Risk Register WC-RISK-04 in enterprise-readiness.md.
8. Concurrency and race conditions¶
- Components don't
AbortController-cancel on unmount. - The
api.jscache is a per-page-bound key but reads/writes are not transactional — if two components hydrate the same page concurrently, both call the endpoint. The cache is keyed only by route, not by in-flight state, so no de-duplication.
[VERIFY-API-3]: does the codebase deduplicate in-flight requests anywhere? Promise.all + RSVP usage is observed in middleware/sociallink.js, but that's parallel-by-design rather than de-duplicated.
9. CORS¶
The frontend is served from a different origin than the backend (frontend on e.g. app.someli.ai, backend at process.env.API_URL). This implies CORS is configured on the backend side. The frontend doesn't influence it.
The auth cookie is sameSite: 'none'; secure: true (nuxt.config.js:286-291), confirming a cross-origin design.
10. Endpoint-level findings¶
Specific patterns flagged for follow-up:
/auth/getuaccountsvs/auth/getUAccounts— both exist (case difference). [VERIFY-API-4]/auth/getBillingInfovs/auth/getBillinginfo— both exist. [VERIFY-API-5]/getGenerateContentStatusvs/auth/getGenerateContentStatus— both exist; the unauthenticated variant may leak info or may be dead. [VERIFY-API-6]/auth/Notify?isFrom=billing— query parameter in the path instead ofparams: { isFrom: 'billing' }. Style inconsistency./auth/getMyLibraryPost/<page>— path-segment pagination instead of query/body. Not consistent with the rest of paginated endpoints which use{ page }in body.
11. Recommendations summary¶
| Priority | Action | Effort |
|---|---|---|
| Phase 0a | Decide on canonical auth header (token vs Authorization vs Bearer) and remove dead paths |
<1d |
| Phase 0 | Add a response interceptor that surfaces 5xx and uncaught errors to error tracking | 1–2d (depends on observability work) |
| Phase 0 | Add a try/catch to the request interceptor's JSON.parse(usedetail) |
<1h |
| Phase 1 | Introduce per-feature API service modules; deprecate direct this.$axios use |
2–4 weeks |
| Phase 1 | Add token-refresh on 401 (if backend supports it) | 1 week |
| Phase 1 | Add Zod/io-ts schema validation at the service layer | 2 weeks |
| Phase 2 | OpenAPI-generated types from backend spec | TBD (depends on backend OpenAPI availability) |
Open questions tracked in ./verify-markers.md.