The service exposes a GraphQL endpoint alongside the REST API for flexible reads and writes.
- Endpoint:
POST /graphql(andGET /graphqlfor the IDE in development) - IDE: GraphQL Playground is served at
/graphqlwhenNODE_ENVis notproduction.
- Production: Set
GRAPHQL_API_KEYin the environment. Every GraphQL request must send that value using either:- Header
x-api-key: <your-key>, or - Header
Authorization: Bearer <your-key>
- Header
- Development: If
GRAPHQL_API_KEYis unset, requests are allowed without a key (for local use and Playground). If you setGRAPHQL_API_KEYlocally, the same headers apply. - Optional:
GRAPHQL_CLIENT_SUBJECT— label returned by themequery when authenticated (defaultapi-client).
The me query returns null when no API key is configured (anonymous development mode). When a valid key is sent, me returns { id, subject }.
Core types include Transaction, Dispute, DisputeNote, DisputeReport, BulkImportJob, and User. Mutations mirror REST flows: deposits/withdrawals, opening and managing disputes, and notes.
Introspection is available in development via Playground (Docs / Schema panels).
Open http://localhost:3000/graphql in a browser and run:
query {
__schema {
queryType {
fields {
name
}
}
}
}With GRAPHQL_API_KEY=my-secret-key:
query {
me {
id
subject
}
}HTTP headers:
x-api-key: my-secret-keyquery GetTxn($id: ID!) {
transaction(id: $id) {
id
referenceNumber
amount
status
createdAt
tags
jobProgress
}
}Variables:
{ "id": "550e8400-e29b-41d4-a716-446655440000" }query List {
transactions(limit: 10, offset: 0) {
id
referenceNumber
type
status
createdAt
}
}mutation Deposit {
deposit(
input: {
amount: "100.00"
phoneNumber: "+237670000000"
provider: "mtn"
stellarAddress: "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"
}
) {
transactionId
referenceNumber
status
jobId
}
}mutation Open {
openDispute(
input: {
transactionId: "550e8400-e29b-41d4-a716-446655440000"
reason: "Amount not credited"
reportedBy: "user-42"
}
) {
id
status
transactionId
}
}
query OneDispute($id: ID!) {
dispute(id: $id) {
id
status
notes {
author
note
createdAt
}
}
}query Report {
disputeReport(
filter: { from: "2026-01-01T00:00:00.000Z", assignedTo: "agent-1" }
) {
generatedAt
totals {
total
open
resolved
}
summary {
status
count
avgResolutionHours
}
}
}After creating a job via POST /api/transactions/bulk, poll with:
query Bulk($id: ID!) {
bulkImportJob(id: $id) {
status
progress {
total
processed
succeeded
failed
}
errors {
row
error
}
createdAt
completedAt
}
}curl -s -X POST http://localhost:3000/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_KEY" \
-d '{"query":"query { me { subject } }"}'GraphQL responses use errors[].extensions.code where applicable, for example:
| Code | Typical case |
|---|---|
UNAUTHENTICATED |
Missing or invalid API key |
NOT_FOUND |
Resource missing |
CONFLICT |
Duplicate dispute or deposit lock |
BAD_USER_INPUT |
Validation / invalid transition |
INTERNAL |
Unexpected failure |
| REST | GraphQL |
|---|---|
GET /api/transactions/:id |
transaction, transactions, transactionByReferenceNumber, transactionsByTags |
POST .../deposit |
deposit |
POST .../withdraw |
withdraw |
Dispute routes under /api/disputes and /api/transactions/:id/dispute |
dispute, disputeReport, openDispute, updateDisputeStatus, assignDispute, addDisputeNote |
GET /api/transactions/bulk/:jobId |
bulkImportJob |
CSV bulk upload remains POST /api/transactions/bulk (multipart); GraphQL is used to query job status by id.