GraphQL Authentication
How to obtain a dashboard session cookie and call the /dashboard/graphql endpoint for payee CRUD, wallet queries, and token rotation.
The Fynex GraphQL API lives at /dashboard/graphql. Unlike the REST API — which uses a bearer token — this endpoint is authenticated exclusively with an HttpOnly session cookie named dashboard_session. There is no bearer-token path to /dashboard/graphql.
This endpoint is the right choice for operations that have no REST equivalent: reconciliation data, the full wallet ledger (WalletEntry / WalletTransfer rows), token rotation, and dashboard-only queries. Payee CRUD, payout methods, wallets, split rules and payment reads all have bearer-token REST routes under /payments-api/v1 — prefer those from a server.
When to use the GraphQL endpoint
Use /dashboard/graphql (cookie-session auth) when you need to:
- Query reconciliation statements or the wallet ledger at entry level.
- Rotate the bearer token that your server-to-server REST integration uses (
regenerateSellerAccountToken). - Access any query or mutation not exposed on the REST surface.
Payees, payout methods, wallets, split rules and split executions are on the REST surface (/payments-api/v1/payees, /payout-methods, /wallets, /split-rules, /payments/{payment_id}/split); use GraphQL for them only from a dashboard session.
Note
Sellers who build custom dashboard integrations — or who need programmatic access to payee/wallet data — are the primary audience for this guide.
Authentication flow
- Obtain a session cookie by posting credentials to the login endpoint:
POST /api/v1/login/dashboard
Content-Type: application/json
{ "email": "you@example.com", "password": "your_password" }
On success (200 OK) the server sets an HttpOnly cookie named dashboard_session. The response body also returns the user ID, permissions, and onboarding status — you can discard those fields for a pure API integration.
Send GraphQL requests to
/dashboard/graphqlwith the cookie attached. SetContent-Type: application/jsonand include the cookie on every request.Log out when done (optional but recommended for server-side scripts):
POST /api/v1/logout
This clears the cookie server-side.
Caution
A new account is a draft until
complete.POST /api/v1/onboarding/startcreates the user and does set thedashboard_sessioncookie, but the account has no organization yet — GraphQL calls that need one fail untilPOST /api/v1/onboarding/completehas run (see Account setup & onboarding). For an existing account,POST /api/v1/login/dashboardis the way to a cookie.
Code samples — login and first query
curl
# Step 1 — login and save the cookie
curl -sc cookies.txt \
-X POST https://api.fynex.ai/api/v1/login/dashboard \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "your_password"}'
# Step 2 — send a GraphQL query using the saved cookie
curl -b cookies.txt \
-X POST https://api.fynex.ai/dashboard/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query ListPayees($limit: Int, $offset: Int) { payees(limit: $limit, offset: $offset) { id displayName role status } }",
"variables": { "limit": 20, "offset": 0 }
}'
JavaScript
const BASE = 'https://api.fynex.ai';
// Step 1 — login; browser (or same-origin server) sends/receives the cookie automatically
async function login(email, password) {
const res = await fetch(`${BASE}/api/v1/login/dashboard`, {
method: 'POST',
credentials: 'include', // required — sends and stores the HttpOnly cookie
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!res.ok) throw new Error(`Login failed: ${res.status}`);
return res.json(); // { userId, permissions, onboardingStatus }
}
// Step 2 — send any GraphQL operation
async function gql(query, variables = {}) {
const res = await fetch(`${BASE}/dashboard/graphql`, {
method: 'POST',
credentials: 'include', // cookie is attached automatically
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
});
const { data, errors } = await res.json();
if (errors?.length) throw new Error(errors[0].message);
return data;
}
// Usage
await login('you@example.com', 'your_password');
const { payees } = await gql(
`query ($limit: Int, $offset: Int) {
payees(limit: $limit, offset: $offset) { id displayName role status }
}`,
{ limit: 20, offset: 0 }
);
console.log(payees);
Python
import requests
BASE = "https://api.fynex.ai"
# requests.Session persists cookies across calls automatically
session = requests.Session()
# Step 1 — login
resp = session.post(
f"{BASE}/api/v1/login/dashboard",
json={"email": "you@example.com", "password": "your_password"},
)
resp.raise_for_status() # raises on 4xx/5xx
# Step 2 — send a GraphQL query
query = """
query ListPayees($limit: Int, $offset: Int) {
payees(limit: $limit, offset: $offset) {
id
displayName
role
status
}
}
"""
resp = session.post(
f"{BASE}/dashboard/graphql",
json={"query": query, "variables": {"limit": 20, "offset": 0}},
)
resp.raise_for_status()
data = resp.json()
if "errors" in data:
raise RuntimeError(data["errors"][0]["message"])
print(data["data"]["payees"])
Session properties
| Property | Value |
|---|---|
| Cookie name | dashboard_session |
| Cookie flags | HttpOnly, SameSite (not accessible from JavaScript) |
| Session TTL | 24 hours. Whether a session survives an API restart depends on the deployment's session store: shared-store deployments keep it, a single-process run loses it. Re-login on 401 either way |
| Auth required | Every request to /dashboard/graphql must carry the cookie |
| Multiple sessions | Each login creates a new session; logout clears both dashboard_session and backoffice_session |
Receipts are bound to the session
Some mutations refuse to act on a bare id and require a receipt issued by
an earlier call — activateSplitRule takes the activationReceipt that
previewSplit(ruleId:) returned. That receipt is signed with a key derived
from the dashboard_session that ran the preview, and it also names the
actor, the rule and a fingerprint of the exact rule snapshot previewed. It is
valid for 10 minutes.
The consequence for a scripted integration: preview and activate must happen inside the same session. A receipt presented from a different cookie — after a re-login, from a second worker, or after a server restart invalidated the session — is refused with "invalid split activation preview receipt", and an expired one with "split activation preview receipt expired; run preview again". Editing the rule in between invalidates it too. Hold one session for the whole preview → activate sequence, and treat any receipt error as "preview again", never as "retry the activation".
See Split payments for the full lifecycle.
GraphQL endpoint details
| Property | Value |
|---|---|
| URL | https://api.fynex.ai/dashboard/graphql |
| Method | POST |
| Content-Type | application/json |
| Body shape | { "query": "...", "variables": { ... } } |
| Error shape | { "errors": [{ "message": "..." }] } |
A GraphQL Playground is available at GET /dashboard/playground (no auth gate on the playground page itself — useful for manual exploration).
Rotating your bearer token from GraphQL
regenerateSellerAccountToken is the one mutation that bridges the GraphQL session world back to the REST bearer-token world. Call it to rotate the authorizationToken used by your server-to-server integration.
mutation RotateToken($merchantId: ID!) {
regenerateSellerAccountToken(merchantId: $merchantId) {
id
authorizationToken
}
}
Supply your seller account ID as $merchantId — the argument is a GraphQL ID, so "42" and 42 are both accepted. The response contains the new bearer token. The previous token is invalidated immediately — update your secret manager before calling this.
Caution
There is no overlap window between the old and new token. Plan a brief service restart or atomic secret rotation before calling this mutation in production.
Common pitfalls
| Pitfall | Resolution |
|---|---|
Calling /dashboard/graphql with a Bearer token |
This endpoint does not support bearer auth. Use the dashboard_session cookie instead. |
| GraphQL fails right after sign-up | A draft account (after POST /api/v1/onboarding/start) has no organization yet. Finish the wizard and call POST /api/v1/onboarding/complete first. |
| Session lost on server restart | Sessions are held in-memory on a single node. A restart invalidates all active sessions — clients must re-authenticate. |
| Cookie not sent by the browser | Ensure you use credentials: 'include' on every fetch call (or the equivalent in your HTTP client). |
401 on the GraphQL endpoint |
Either the cookie is absent, expired (>24 h), or was invalidated by a server restart. Re-login to get a fresh cookie. |
activateSplitRule rejects a receipt that previewSplit just issued |
The two calls ran under different sessions (a re-login or a second worker in between), or more than 10 minutes passed. Receipts are session-bound — see Receipts are bound to the session above. Run the preview again in the session that will activate. |
See also
- Authentication & Tokens (Bearer) — Bearer token auth for the server-to-server REST API.
- Payees — Create and manage payees via REST and GraphQL.
- Payout Methods — Register bank accounts as payout destinations for your payees.