Skip to content

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/, attach token and accountId headers from cookies. Note: the header key is the literal string token (not Authorization).
  • For every request, attach apptype header (base64 of process.env.APP_TYPE).
  • Save the current path + hash to the currentUrl cookie (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/refresh endpoint 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.js middleware on the next navigation will eventually call /auth/getaccountdetails, which will return empty data, which triggers the catch-all logout + cookies.removeAll() + localStorage.clear() + redirect /#login path.

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:

  1. Try/catch swallowing: store/api.js actions wrap calls in try/catch and return cached data on error.
  2. Try/catch with toast: Some components show this.$toast.error(...) or this.$snotify.error(...) on failure.
  3. Component-level alert/console.log: A few components log to console.
  4. Bare promise: Some calls have neither .catch nor 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.js cache 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/getuaccounts vs /auth/getUAccounts — both exist (case difference). [VERIFY-API-4]
  • /auth/getBillingInfo vs /auth/getBillinginfo — both exist. [VERIFY-API-5]
  • /getGenerateContentStatus vs /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 of params: { 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.