Globagift Docs
DevelopersAPI reference

Public merchant lookup and onboarding

Public, unauthenticated endpoints for storefront merchant lookup and merchant onboarding.

This page covers two unrelated groups of endpoints that are both public (no merchant session) rather than part of the authenticated BFF surface: the storefront's merchant/catalog lookup, and merchant onboarding.

Storefront merchant lookup

Used by the public storefront (webshop) to render a merchant's branded page and catalog. Both are CORS-public, unauthenticated, and only return data for merchants with status: ACTIVE.

Get a merchant by slug

GET /api/merchants/{slug}

(Re-exported from src/app/api/public/merchants/[slug]/route.ts; the path shown is the one CLAUDE.md scopes as merchant-facing.)

Response

{
  "ok": true,
  "data": {
    "id": "...",
    "slug": "...",
    "name": "...",
    "logoUrl": null,
    "description": null,
    "primaryColor": null,
    "secondaryColor": null,
    "currency": "EUR",
    "homeUrl": null,
    "termsUrl": null,
    "siftSiteId": null,
    "analyticsId": null,
    "popularGiftCardAmount": null
  }
}

termsUrl is fetched from the card network's program details at request time; if that lookup fails, it's null rather than failing the whole request. Cache-Control: no-store is set.

Errors

StatusCause
404No merchant matches slug, or the matched merchant's status is not ACTIVE.
500Unexpected internal error.

Get a merchant's catalog by slug

GET /api/merchants/{slug}/catalog

(Re-exported from src/app/api/public/merchants/[slug]/catalog/route.ts.)

Returns the merchant's sellable gift card offers. Refund cards are excluded (they are internal-use-only and can't be auto-issued through this storefront flow).

Response

{
  "ok": true,
  "data": [
    {
      "id": "...",
      "merchantId": "...",
      "name": "...",
      "description": null,
      "imageUrl": null,
      "type": "GIFTCARD_DIGITAL",
      "currency": "EUR",
      "priceOptions": [2500, 5000, 10000],
      "minValue": 2500,
      "maxValue": 10000
    }
  ],
  "meta": { "popularAmount": null }
}

priceOptions is in minor units. For fixed-price offers it's a single value; for variable-pricing offers with no explicit denominations set, it's auto-generated from the min/max range using "nice" step sizes (roughly 4-6 options), so it may not match what the merchant actually configured if they intended a continuous range rather than fixed steps.

Errors

Same as Get a merchant by slug.

Onboarding

apply/ is not the real onboarding entry point

CLAUDE.md's documented scope names src/app/api/apply/... as the onboarding entry point. Reading it shows a single, minimal POST that creates a draft merchant and a user with no password or auth provider set (the code's own comment says "in a real app, we'd handle password/auth provider here"), and it does not match the real 4-step onboarding flow (Company information, Directors, Documents, Review & submit) already documented in Getting started: creating an account. That flow is actually backed by src/app/api/onboarding/* and src/app/api/public/merchant/onboarding-check, documented below instead. apply/ appears to be an earlier or unused stub; it is not documented here and should not be treated as a supported entry point until confirmed otherwise.

All onboarding submission and document endpoints below require an authenticated session (created during account signup, before KYB submission); only the status-check lookup is fully public.

Save or submit onboarding details

POST /api/onboarding/submit

Saves a draft of the company/KYB onboarding form, or performs a full submission. Only allowed while the merchant's status is DRAFT or WAITING_FOR_INFO.

Request body

FieldTypeRequired for draftRequired for submit
isDraftbooleanno (defaults to a full submit if omitted/false)n/a
registrationNumberstringnoyes
vatNumberstringnoyes
tradingNamestringnono
currencystringnono (defaults to EUR)
numberOfLocationsstringnono (defaults to "0")
legalAddress.line1 / .city / .postalCode / .countrystringnoyes
legalAddress.statestringnono
directorsarray of { name, role }noyes, at least one
documentsarray of { type, url, name? }nono
termsAcceptedbooleannoyes
privacyAcceptedbooleannoyes

On a full submission (isDraft not true), this also creates or refreshes an IDenfy verification session and emails the merchant a status update with the verification link; both are best-effort and a failure here does not fail the submission itself.

Response

{ "ok": true }

Errors

StatusCause
401No session.
400Session user has no associated merchant; body fails to parse; or (full submit only) required fields are missing, or terms/privacy were not accepted.
403Merchant status is not DRAFT or WAITING_FOR_INFO (application is under review and cannot be edited).
500Unexpected internal error.

Upload an onboarding document

POST /api/onboarding/upload

Uploads a KYB document (for example, Certificate of Incorporation or director ID) to object storage under a tenant-scoped key.

Request

multipart/form-data with a single field:

FieldTypeRequired
filefileyes

Response

{
  "ok": true,
  "url": "https://<bucket>.s3.<region>.amazonaws.com/<merchantId>/onboarding/documents/<uuid>-<name>.<ext>",
  "previewUrl": "https://...(signed, expires in 1 hour)",
  "key": "<merchantId>/onboarding/documents/<uuid>-<name>.<ext>",
  "originalName": "...",
  "filename": "<uuid>-<name>.<ext>"
}

url is the permanent (unsigned) S3 URL, intended for storing in the documents array passed to onboarding/submit. The bucket is private, so this URL alone is not browsable; use previewUrl for immediate display, or Get a signed document URL later.

Errors

StatusCause
401No session.
400Session user has no associated merchant, or no file provided.
500Upload failed.

Get a signed document URL

GET /api/onboarding/document?url=<S3 object URL>

Exchanges a previously stored document URL for a short-lived signed URL (60 seconds), since the underlying bucket is private.

Query parameters

ParamRequiredNotes
urlyesMust be a virtual-hosted-style S3 URL for the configured bucket.

Response

{ "ok": true, "url": "https://...(signed, expires in 60 seconds)" }

Errors

StatusCause
401No session.
400Missing url, or url doesn't parse as a valid S3 URL for the configured bucket.
403Non-admin session user has no associated merchant, or the document's storage key does not belong to their merchant (tenant check on the key prefix). Admins bypass this check.
500Failed to generate the signed URL.

Check onboarding status by email

POST /api/public/merchant/onboarding-check

Fully public, unauthenticated. Rate-limited per client IP and per email address (separately). Used for a "what's the status of my application" self-service lookup, without requiring a login.

Request body

FieldTypeRequired
emailstring (valid email)yes

Response

{
  "ok": true,
  "data": {
    "recognized": true,
    "activated": false,
    "merchantStatus": "KYB_IN_PROGRESS",
    "summary": {
      "text": "...",
      "nextStep": "..."
    }
  }
}

recognized is false if no user matches the email at all (the data shape is narrower in that case: just recognized and summary, no activated/merchantStatus). merchantStatus is one of DRAFT, SUBMITTED, WAITING_FOR_INFO, KYB_IN_PROGRESS, DECISION_PENDING, ACTIVE, INACTIVE, SUSPENDED, DECLINED, or null if the email matches an admin account with no merchant, or a user with no merchant profile linked yet. summary.text is a plain-language status message intended to be shown directly to the merchant; it never echoes raw status codes. The response intentionally never reveals whether an email exists in a way that would help enumerate accounts beyond the generic "not found" message.

Errors

StatusCause
429Rate limit exceeded for this IP or this email address; Retry-After header indicates the wait.
400email missing or not a valid email address.
500Unexpected internal error.

On this page