Skip to content
In this section

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

  1. 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.

  1. Send GraphQL requests to /dashboard/graphql with the cookie attached. Set Content-Type: application/json and include the cookie on every request.

  2. 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/start creates the user and does set the dashboard_session cookie, but the account has no organization yet — GraphQL calls that need one fail until POST /api/v1/onboarding/complete has run (see Account setup & onboarding). For an existing account, POST /api/v1/login/dashboard is 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