Globagift Docs
DevelopersAPI reference

Merchant + team

Merchant profile, stats, team members, retail stores, and gift card lookup by gift cards management.

This sub-area covers the merchant's own profile, dashboard stats, team member management, retail store management, and the gift-cards-management card lookup. These routes use a mix of permissions and auth patterns; each is noted per endpoint.

Get merchant profile

GET /api/bff/merchant

Requires the portal.information permission (admins bypass). Returns the merchant's company and brand profile, enriched with KYB/onboarding fields.

Response

If the session user has no associated merchant yet (for example, mid onboarding or invited but not yet linked):

{
  "ok": true,
  "data": { "company": null, "brand": {}, "slug": null, "webshopUrl": "...", "kyb": null },
  "meta": { "workspace": "missing", "pendingTeamInvite": false }
}

Otherwise:

{
  "ok": true,
  "data": {
    "company": {
      "status": "ACTIVE",
      "legalName": "...",
      "orgNumber": "...",
      "taxNumber": "...",
      "country": "...",
      "tradeName": "...",
      "externalMerchantId": "...",
      "timeZone": "UTC",
      "registrationNumber": "...",
      "vatNumber": "...",
      "tradingName": "...",
      "currency": "EUR",
      "numberOfLocations": 0,
      "legalAddress": { "line1": "", "city": "", "state": "", "postalCode": "", "country": "IE" },
      "directors": [],
      "documents": [],
      "idenfyKybStatus": null,
      "idenfyCompanyId": null,
      "idenfySessionUrl": null
    },
    "brand": {
      "logoUrl": "...",
      "category": "",
      "descriptor": "",
      "website": "...",
      "facebook": "",
      "twitter": "",
      "instagram": ""
    },
    "slug": "...",
    "webshopUrl": "...",
    "kyb": {
      "registrationNumber": "...",
      "vatNumber": "...",
      "legalAddress": { },
      "directors": [],
      "documents": []
    }
  }
}

company.status is the merchant's MerchantStatus enum value (DRAFT, plus others used by onboarding/admin review). orgNumber and taxNumber both currently mirror vatNumber/registrationNumber; this is a quirk of the current mapping, not two independently meaningful fields.

If the merchant has no logoUrl set and card-network credentials are configured, a logo is fetched from the card network and cached asynchronously after the response is sent; the first response after a merchant is created may not yet include it.

Errors

StatusCause
401No session.
403Non-admin session user's role lacks the portal.information permission.
500Unexpected internal error.

Update merchant profile

PUT /api/bff/merchant

Requires the portal.information permission (admins bypass). Updates the local profile immediately; mirrors changes to the card network in the background (best-effort, does not block or fail the request).

Request body

{
  "company": {
    "status": "string",
    "legalName": "string",
    "orgNumber": "string",
    "taxNumber": "string",
    "country": "string",
    "currency": "string",
    "tradeName": "string",
    "externalMerchantId": "string",
    "timeZone": "string"
  },
  "brand": {
    "logoUrl": "string (valid URL)",
    "category": "string",
    "descriptor": "string",
    "website": "string",
    "facebook": "string",
    "twitter": "string",
    "instagram": "string"
  }
}

Both company and brand are optional, as are all of their fields. Of these, only legalName/tradeName (mapped to the merchant's name), orgNumber (mapped to registrationNumber), taxNumber (mapped to vatNumber), website (mapped to websiteUrl), logoUrl, and timeZone (stored in metadata.portalTimeZone) are actually persisted locally. country and currency are onboarding-owned fields and are not updated through this endpoint despite being accepted in the request shape.

An account-change notification email is sent to the user best-effort on a successful save.

Response

{ "ok": true, "data": { } }

data echoes back the parsed request body, not the updated merchant record.

Errors

StatusCause
400Body fails schema validation (for example, brand.logoUrl not a valid URL).
401No session.
403Non-admin session user's role lacks the portal.information permission.
500Save failed; message is a generic "could not save changes" rather than the underlying error detail.

Get merchant stats

GET /api/bff/merchant/stats

Currently disabled, returns hardcoded zeros

The real analytics query for this endpoint is disabled in source ("disabling real analytics query due to memory risks") and always returns a fixed zero-valued payload regardless of merchant or the days query parameter, which is accepted but otherwise unused. Do not rely on this endpoint for real numbers; use Analytics overview instead, which is fully implemented.

Requires only a valid session and a merchant link; no specific permission is checked.

Response

{
  "ok": true,
  "data": {
    "series": [],
    "totals": {
      "totalRevenue": 0,
      "totalOrders": 0,
      "totalVisits": 0,
      "webshopRevenue": 0,
      "widgetRevenue": 0
    }
  }
}

Errors

StatusCause
401No session.
403Session user has no associated merchant.
500Unexpected internal error.

List team members and pending invites

GET /api/bff/merchant/users

No specific permission check beyond having a session and an associated merchant; returns an empty list if the session user has no merchant.

Response

{
  "ok": true,
  "data": [
    {
      "id": "user-id-or-invite:challenge-id",
      "firstName": "...",
      "lastName": "...",
      "email": "...",
      "phonePrefix": null,
      "phone": null,
      "role": "Admin",
      "status": "Active",
      "merchantTeamRole": "ADMIN"
    }
  ]
}

The list merges actual team members with pending invite challenges (an invite already accepted by a matching member email is excluded to avoid duplicates). id is prefixed invite: for pending invites, otherwise it's the user's ID. role is a display label: "Admin", "Full access", "Viewer (read-only)", or "—" for members; for invites it's the invited role label. status for members is "Active" or "Pending email verification"; for invites it's one of "Pending your verification", "Verification expired", "Invitation expired", or "Invited, pending acceptance". Pending invites are sorted first, then members, alphabetically by email within each group.

Errors

StatusCause
401No session.
500Unexpected internal error.

Add a team member

POST /api/bff/merchant/users

Always returns 400

This endpoint cannot add a user directly. It always returns a fixed error directing the caller to the dashboard's Organization > Team > "Invite teammate" flow, which uses a separate OTP-verification process not exposed as a BFF endpoint.

Response

{
  "ok": false,
  "error": "New users cannot be added through this API. In the portal, open Organization, then Team, and use Invite teammate. You must confirm the verification code sent to your email before we contact the invitee.",
  "code": "TEAM_INVITE_OTP_REQUIRED"
}

Status 400 always.

Change a team member's role

PATCH /api/bff/merchant/users

Requires the acting user to have the merchant.team.invite permission (distinct from the portal.* permissions used elsewhere; effectively restricted to organization admins).

Request body

FieldTypeRequired
targetUserIdstringyes
merchantTeamRolestring, one of ADMIN, FULL, VIEWERyes

Response

{ "ok": true }

Errors

StatusCause
401No session.
403Acting user lacks merchant.team.invite permission.
400Missing/invalid targetUserId or merchantTeamRole; caller tried to change their own role; or the change would leave the organization with no admin-capable member (a user with merchantTeamRole of ADMIN or unset is "admin-capable", and the last one cannot be downgraded).
404targetUserId does not belong to the acting user's merchant.
500Unexpected internal error.

List retail stores

GET /api/bff/merchant-stores

Requires the portal.organization permission. Lists the merchant's retail stores directly from the card network (not a local table).

Failures are swallowed, not surfaced

If the upstream card network call fails or the merchant has no card-network account configured, this still returns 200 with an empty retailStores array rather than an error status. A client cannot distinguish "no stores" from "lookup failed" from this response alone.

Response

{
  "ok": true,
  "data": {
    "merchantId": "...",
    "retailStores": [
      {
        "address1": "...",
        "address2": "...",
        "city": "...",
        "postcode": "...",
        "country": "...",
        "externalId": "...",
        "cashRegisters": 0
      }
    ]
  }
}

Each store object also includes whatever other fields the card network returned (the normaliser only adds/overwrites the address and identifier fields shown above; it does not strip the rest of the raw response).

Errors

StatusCause
401No session.
404Session user has no associated merchant.
403Session user's role lacks the portal.organization permission.

Create a retail store

POST /api/bff/retail-store

Requires the portal.organization permission.

Request body

FieldTypeNotes
namestring
address1 / address2string
citystring
postcodestring
countrystringDefaults to US upstream if omitted.
externalIdstringIf omitted, a generated ID (<merchantId>-<timestamp>) is used.

No schema validation is enforced; fields are read directly from the request body.

Response

{ "ok": true, "data": { } }

data is the raw card network response for the created store.

Errors

StatusCause
401No session.
404Session user has no associated merchant.
403Session user's role lacks the portal.organization permission.
400Merchant has no card-network account configured. Response shape here is a bare { "error": "..." }, not { "ok": false, ... }.
variesUpstream creation failure passes through the upstream status code, response shape { "error": "...", "details": { } }.
500Unexpected internal error; response shape { "error": "..." }.

Update a retail store

PUT /api/bff/retail-store

Requires the portal.organization permission. Same request body as create, plus:

FieldTypeNotes
externalIdstringRequired in practice; identifies which store to update.
statusstring"Active" maps to upstream ACTIVE; anything else maps to NOT_ACTIVE.

Response and errors

Same shape as Create a retail store.

Look up a gift card by QR code (gift cards management)

GET /api/bff/cards/{qr}

Requires the portal.gift_cards permission. This is the gift-cards management screen's card lookup, distinct from the POS terminal's lookup at pos/card/{qrcode} (gated by portal.pos instead); both proxy the same underlying card network endpoint. See POS: look up a card by QR code for the equivalent POS-side contract; the request/response shapes are the same here.

Errors

StatusCause
401No session.
404Session user has no associated merchant.
403Session user's role lacks the portal.gift_cards permission.
400Missing qr path parameter.
variesCard not found or other upstream failure; passes through the upstream status code.

On this page