WLI: Integration Architecture — Customer Success approach
Updated today
🔗 Frontend, Backend Proxy, and the Metricool API
As we mentioned before (here), this is a sample integration which unique goal is to show the minimum requirements for making the Metricool White Label integration.
After this, please consider:
Your Metricool integration is built from three layers: the frontend your end users see, your own backend acting as a proxy, and the Metricool API.
Browser's HTML/JS files never talks to Metricool directly — every request goes through your backend first, the only place where your access token lives (Menu > Account settings > API).
This article covers why this architecture is ideal and how its three layers communicate; detailed proxy configuration and endpoint references are covered in separate articles.
Server-to-server connection
Calling the Metricool API directly from browser-side JavaScript can fail for two independent reasons:
CORS: any authenticated call includes the
X-Mc-Authheader andContent-Type: application/json, which forces the browser to send a preflightOPTIONSrequest first. Our API responds403 Forbiddento that preflight, because it isn't meant to be called from arbitrary browser origins.Token exposure: even if the preflight succeeded, a token in client-side code is readable by anyone who opens the page's source. Thus, the
userTokengrants access to your whole Metricool account and shall not appear in browser's side. Be careful ; )
localhost
Even when the call succeeds from localhost without the proxy configuration, it will fail once deployed.
- CORS and Token exposure:
A direct call from the browser never gets past the preflight. E.g.:
// Called from client-side (domain) JS — fails before it reaches Metricool's domain
fetch('https://app.metricool.com/api/v2/scheduler/posts', {
method: 'POST',
headers: {
'X-Mc-Auth': '[YOUR_TOKEN]',
'Content-Type': 'application/json'
},
body: JSON.stringify({ /* ... */ })
});
// → browser sends a preflight OPTIONS request → Metricool responds 403 ForbiddenThe fix is for your own backend to receive the request from the browser — same origin, no CORS issue —, attach the credentials, and call Metricool on your app's behalf:
Browser (your frontend) → Your backend (proxy) → Metricool APIThe backend proxy: the only place your token lives
The access token (userToken) grants full control over the connected Metricool account, so it must never reach the browser or your frontend's source code.
Your backend reads it from a server-side environment variable and attaches it as the X-Mc-Auth header on every request it forwards to Metricool — the frontend never sees it:
# On the backend only — never in a file served to the browser
METRICOOL_USER_TOKEN = os.environ.get("METRICOOL_USER_TOKEN")
headers = {"X-Mc-Auth": METRICOOL_USER_TOKEN}
request = urllib.request.Request(target_url, data=body, method=method, headers=headers)userId and blogId are different: they aren't secret credentials, they're identifiers visible in Metricool's own URL as you browse your account.
Your frontend can send them as request parameters, and your proxy simply forwards them as-is.
If the token is ever exposed in the frontend — for example, pasted directly into an HTML file during testing — treat it as compromised and regenerate it immediately from Metricool → Account Settings → API.
How the frontend and the proxy communicate
For this specific proyect, the frontend never calls app.metricool.com directly: it always calls a local route on your own domain, and your proxy translates it into the real call against Metricool.
This project implements two calls with that pattern (we talk in detail about them in the references below):
Frontend page | Call to the proxy | On Metricool |
|---|---|---|
|
| Schedules a new post |
|
| Creates a new brand/profile |
Because it's a route on your own domain, the browser treats it as same-origin and no CORS preflight is triggered.
The frontend only ever calls that local route — no token, no X-Mc-Auth header, nothing Metricool-specific:
Full proxy configuration — including environment variables, the HTTPS certificate, and how each local route is mapped to its corresponding Metricool route — is documented in the article dedicated to proxy configuration.
// schedules.html — no credentials, no Metricool URL, just your own domain
const response = await fetch('/api/scheduler/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});The proxy receives that request, attaches the token, and maps the local path to its real Metricool route before forwarding it:
# server-side — local route → real Metricool route
if path == "/api/scheduler/posts":
remote_path = "/v2/scheduler/posts"
elif path.startswith("/api/"):
remote_path = path[len("/api"):]The exact parameters for each call are documented in the reference articles for scheduler/posts and admin/add-profile.
Additional Resources:
Proxy
GET - new profile
POST - scheduler
Buttons for redirection