Skip to main content

Rewarded App Development Guide

Build a web or mobile rewards app with the Swaarm Rewarded GraphQL API.

What the API provides

The Rewarded API lets an app:

  • register and authenticate rewarded users;

  • show offers that are available to the signed-in user;

  • track genuine offer impressions and clicks;

  • show event-level progress and earned rewards;

  • display monetary and points balances;

  • request and track withdrawals when payouts are enabled;

  • manage the user's profile, devices, password, and account deletion;

  • provide in-app support tickets; and

  • optionally support Google, Facebook, and Apple sign-in.

ℹ️ Offer completion is not reported by the app. Swaarm records progress after it receives and accepts an advertiser postback. The app reads that server-side state from the API.


Before you start

Ask Swaarm contact:

  • the GraphQL endpoint for your tenant;

  • confirmation that Rewarded is enabled;

  • a test offer and access to it for your rewarded users;

  • the tenant's monetary currency and points convention;

  • the minimum withdrawal and payment-provider configuration, if payouts are required;

  • email-verification and password-reset configuration;

  • social-login client IDs, if social login is required; and

  • app branding and deep-link settings.

All GraphQL operations use one tenant-specific endpoint:

POST <GRAPHQL_ENDPOINT> Content-Type: application/json

The request body contains a query and, normally, variables:

{   "query": "query CurrentUser { rewardedUser { id email nickname } }",   "variables": {} }

Use a GraphQL client such as Apollo, urql, Apollo iOS, Apollo Kotlin, or graphql-flutter when practical. Introspection and code generation help catch schema changes before release.

Check feature availability

ℹ️ Schemas can differ between tenants and releases. During development, enable schema introspection and generate client types from the target tenant. For a quick check:

query RewardedApiCapabilities {   queryType: __type(name: "Query") {     fields { name }   }   mutationType: __type(name: "Mutation") {     fields { name }   }   rewardedUserType: __type(name: "RewardedUser") {     fields { name }   } }

⚠️ Do not run a client against operations that are absent from its generated schema. Introspection may be disabled in hardened production environments, so perform this check in development or CI.


A small HTTP client

If you are not using a GraphQL library, this TypeScript helper handles both HTTP failures and GraphQL errors:

type GraphQLError = {   message: string;   path?: Array<string | number>;   extensions?: Record<string, unknown>; };  type GraphQLResponse<T> = {   data?: T;   errors?: GraphQLError[]; };  async function graphql<T>(   query: string,   variables: Record<string, unknown> = {},   accessToken?: string, ): Promise<T> {   const response = await fetch(GRAPHQL_ENDPOINT, {     method: "POST",     headers: {       "Content-Type": "application/json",       ...(accessToken         ? { Authorization: `Bearer ${accessToken}` }         : {}),     },     body: JSON.stringify({ query, variables }),   });    const payload = (await response.json()) as GraphQLResponse<T>;    if (!response.ok || payload.errors?.length || !payload.data) {     throw new Error(       payload.errors?.map((error) => error.message).join("; ") ||         `GraphQL request failed with HTTP ${response.status}`,     );   }    return payload.data; }

⚠️ GraphQL can return HTTP 200 with an errors array and partial data. Always inspect both.


Registration, verification, and authentication

Register with email and password

createRewardedUser is public. Use variables so credentials are not embedded in query text or logs.

mutation Register($input: RewardedUserInput!) {   createRewardedUser(input: $input) {     id     email     nickname     status   } }
{   "input": {     "email": "user@example.com",     "password": "a-long-user-chosen-password",     "nickname": "GamerPro",     "firstName": "Sam",     "lastName": "Lee",     "country": "US",     "status": "PENDING",     "internalId": "your-app-user-123",     "customFields": [       { "name": "preferred_category", "value": "gaming" }     ]   } }

Important registration rules:

  • email and password are required.

  • Email must be unique; a duplicate registration returns a GraphQL error.

  • Use an ISO 3166-1 alpha-2 country code such as US, DE, or RO.

  • Use PENDING for an email-verification flow. In newer schemas status can be omitted and defaults to PENDING; passing it keeps the request compatible with older schemas where it is required.

  • internalId is an optional identifier from your own system. Do not use the Swaarm user ID and your internal ID interchangeably.

  • referralId, when used, is the Swaarm id of the referring rewarded user.

  • Do not pass publisherId unless Swaarm has explicitly assigned one for this integration.

  • Never place secrets, authentication tokens, or regulated personal data in customFields.

Verify the email address

If your tenant uses code-based verification, first trigger the email using the user ID returned by registration:

mutation SendVerificationEmail($userId: String!) {   rewardedTriggerVerificationEmail(id: $userId) }

Then submit the email and the code entered by the user:

mutation VerifyEmail($email: String!, $code: String!) {   rewardedVerifyEmail(email: $email, code: $code) }

A successful verification returns true and activates the user. Some tenants instead use an emailed verification link that returns to an app deep link. Confirm the desired flow during onboarding.

Log in

mutation Login($input: AuthLoginInput!) {   login(input: $input) {     authChallenge     token {       accessToken       refreshToken       tokenType       expiresIn     }   } }
{   "input": {     "username": "user@example.com",     "password": "a-long-user-chosen-password"   } }

For authenticated operations, send:

Authorization: Bearer <accessToken>

expiresIn is the access-token lifetime in seconds. Do not hard-code a lifetime in the app. Schedule renewal from the returned value and also recover from an HTTP 401.

Store tokens in the platform's secure storage, such as iOS Keychain or Android Keystore-backed storage. Never log tokens, include them in URLs, or put them in analytics events.

Refresh the access token

mutation RefreshAccessToken($input: AuthRefreshTokenInput!) {   refreshToken(input: $input) {     authChallenge     token {       accessToken       refreshToken       tokenType       expiresIn     }   } }
{   "input": {     "refreshToken": "<refresh-token>"   } }

Replace the stored access token with the returned one. If the response contains a refresh token, replace that atomically as well. If refresh fails, clear the session and ask the user to log in again.

authChallenge is normally NONE. A tenant can also return challenges such as NEW_PASSWORD_REQUIRED or ACCOUNT_MIGRATION_REQUIRED; do not assume token is present until the challenge has been completed.

Optional social login

When configured for the tenant, rewardedSocialLogin verifies a provider token and either signs in the existing active user or creates a new active rewarded user.

mutation SocialLogin($input: RewardedSocialLoginInput!) {   rewardedSocialLogin(input: $input) {     authChallenge     isNewUser     token {       accessToken       refreshToken       tokenType       expiresIn     }   } }

Recommended inputs:

{ "input": { "provider": "GOOGLE", "idToken": "<google-id-token>" } }
{ "input": { "provider": "FACEBOOK", "accessToken": "<facebook-access-token>" } }
{   "input": {     "provider": "APPLE",     "idToken": "<apple-id-token>",     "firstName": "Sam",     "lastName": "Lee"   } }

ℹ️ For Apple, pass the name on the first authorization because Apple may not include it on later sign-ins. Social login requires tenant-side client ID configuration and an email from the provider.

Password reset

Request a reset code:

mutation RequestPasswordReset($email: String!) {   rewardedRequestPasswordReset(email: $email) }

Submit the code and a new password:

mutation ResetPassword(   $email: String!   $newPassword: String!   $code: String! ) {   rewardedResetPassword(     email: $email     newPassword: $newPassword     code: $code   ) }

ℹ️ Reset codes expire. Show a generic confirmation after requesting a code and rate-limit the UI to avoid account enumeration and abuse.


Load the current user and app shell

The app-facing user query is rewardedUser; it resolves from the bearer token. Admin-only operations such as singleRewardedUser are not needed in an end-user app.

query AppBootstrap {   rewardedUser {     id     internalId     email     nickname     firstName     lastName     country     status     profilePictureUrl     customFields { name value }     stats {       startedOffers       completedOffers       totalTheyGet       totalTheyGetPoints     }     balance {       availableBalance       pendingBalance       withdrawnBalance     }   } }

ℹ️ profilePictureUrl and withdrawnBalance are newer fields. Omit them if they are absent from the target tenant's generated schema.

The monetary fields (theyGet, totalTheyGet, and balances) are amounts in the tenant's configured platform currency. theyGetPoints fields are the corresponding point values. Ask Swaarm how your tenant displays and converts them; do not assume that one point equals one currency unit.

Handle user status explicitly:

Status

Recommended app behaviour

PENDING

Continue the tenant's email-verification or approval flow; do not show the offer wall yet.

ACTIVE

Allow normal Rewarded API use.

INACTIVE

End the session and show that the account is unavailable.

BANNED_FOR_FRAUD

End the session and show the tenant-approved support or appeal message. This status exists only in newer schemas.

Optional public app branding

An app can load tenant branding before login when rewardedAppDetails is available:

mutation AppDetails {   rewardedAppDetails {     title     logo     playStoreUrl     iosStoreUrl     webUrl   } }

ℹ️ This is currently a mutation even though it reads configuration. Treat nullable store and web URLs as optional.

Update the user's profile

mutation UpdateProfile($input: RewardedUserUpdateInput!) {   updateRewardedUser(input: $input) {     id     nickname     firstName     lastName     country     profilePictureUrl     customFields { name value }   } }

The update input is a full desired profile, not a reliable patch: omitted nullable fields can be stored as null, and the custom-field list is replaced. Load the current profile, merge edits locally, and send all values that must be preserved.

The self-service mutation updates profile fields. User status and internal notes are controlled by Swaarm even though shared schema inputs may expose those names.

Record an app open and device details

When supported, call rewardedAppOpen after authentication and whenever important device or push-token data changes:

mutation RecordAppOpen($input: RewardUserOpenEvent!) {   rewardedAppOpen(input: $input) }
{   "input": {     "time": "2026-08-05T12:30:00",     "deviceId": "<stable-app-device-id>",     "deviceType": "PHONE",     "os": "IOS",     "osv": "18.5",     "locale": "en-US",     "timezone": "Europe/Bucharest",     "appVersion": "1.4.0",     "buildVersion": "104",     "expoPushToken": "<optional-expo-push-token>",     "trackingPermission": "DENIED"   } }

Only send data for which your app has a legitimate purpose and the required user consent. Use a stable app-scoped device ID, not a fingerprint assembled from unrelated device attributes. expoPushToken is only relevant to tenants using Expo push notifications.


Build the offer wall

Query available offers

query OfferWall(   $filter: RewardedOffersFilter   $pagination: Pagination! ) {   rewardedUser {     offers(       filter: $filter       pagination: $pagination       ordering: DEFAULT     ) {       totalCount       edges {         node {           id           name           description           appStoreId           previewUrl           trackingUrl           impressionUrl           leadflow           kpi           additionalInformation           tags           verticals           restrictions           percentageCompleted           completedStatus           totalPayout { theyGet theyGetPoints }           targeting {             countries             os {               platform               version { minVersion maxVersion }             }           }           eventTypes {             id             name             description             category             completed             payout { theyGet theyGetPoints }             reward { theyGet theyGetPoints time }             creativeUrl             creativeThumbnail           }           creatives {             id             title             description             creatives {               id               title               type               url               thumbnailUrl               active               hidden             }           }         }       }     }   } }
{   "filter": {     "countries": ["US"],     "oses": ["IOS"],     "completionStatuses": ["UNATTEMPTED", "STARTED"]   },   "pagination": { "offset": 0, "limit": 20 } }

ℹ️ Fields added by newer Rewarded releases - such as verticals, restrictions, category, and the plural filters - should be omitted for an older tenant schema.

Filtering behavior

RewardedOffersFilter can include:

Field

Behaviour

autoFilter

Uses request IP and user agent to infer country and OS.

excludeCompleted

Removes fully completed offers.

country / countries

Includes offers targeting the given country or any country in the list.

excludeCountries

Excludes offers matching the supplied countries.

os / oses

Includes offers for the given platform, normally IOS, ANDROID, or WEB.

excludeOses

Excludes the supplied platforms.

keyword

Searches offer ID, name, and description.

completionStatuses

Includes UNATTEMPTED, STARTED, and/or COMPLETED.

tags / excludeTags

Includes or excludes offers by tag.

verticals

Includes offers having any supplied vertical.

restrictions

Includes offers having any supplied restriction.

ℹ️ For native apps, explicit country and OS filters are more predictable than autoFilter, because an HTTP library's user agent may not identify the device OS. Use values supplied or verified by your application and apply your own consent and location policies.

Filters are combined, so an offer must pass every populated category of filter. Values within a list generally use “any match” semantics.

Ordering behavior

Newer schemas accept ordering:

  • DEFAULT sorts by recent EPC and can include unattempted, started, and completed offers according to the filter.

  • TAG_PRIORITIZED places specially tagged offers first and hides offers on which the user already has progress.

ℹ️ The schema default can be TAG_PRIORITIZED. Pass DEFAULT explicitly for a general offer wall or when you need started offers to remain visible.

Highlighted and single-offer screens

highlightedOffers returns a small tenant-curated set:

query HighlightedOffers {   rewardedUser {     highlightedOffers {       edges {         node {           id           name           totalPayout { theyGet theyGetPoints }           creatives {             creatives { type url thumbnailUrl active hidden }           }         }       }     }   } }

In newer schemas, load a signed-in user's offer detail with the nested field:

query OfferDetails($id: String!) {   rewardedUser {     offer(id: $id) {       id       name       description       trackingUrl       impressionUrl       completedStatus       percentageCompleted       eventTypes {         id         name         description         completed         payout { theyGet theyGetPoints }         reward { theyGet theyGetPoints time }       }     }   } }

Use the root rewardedOffer(id: ...) only where the nested field is not yet available.

Pagination

Offer, reward, and progress connections use offset pagination:

input Pagination {   offset: Int   limit: Int }

Start with { offset: 0, limit: 20 }, then increase offset by the number of returned edges. Stop when no edges are returned or when the accumulated result reaches totalCount. Data can change between requests, so deduplicate by stable IDs when merging pages.


Tracking impressions and clicks correctly

Every returned offer has user-specific tracking URLs. Do not construct these URLs yourself or copy a URL from one user to another.

Impression

When an offer is genuinely displayed, make a GET request to its impressionUrl if the value is not null or empty. Preserve the URL exactly. Avoid firing impressions for prefetched items that never become visible, and avoid repeated calls caused only by UI re-rendering.

Click

When the user chooses the offer, open its trackingUrl exactly as returned. Do not strip, reorder, or replace query parameters. The URL carries the Swaarm attribution context for that user and offer.

Treat tracking URLs as sensitive operational data: they can contain user and attribution identifiers. Do not write the full URL to analytics or crash logs.

Completion

⚠️ Do not mark an event complete or credit a balance because the user clicked, installed an app, or returned to your app. Swaarm records progress only after an advertiser postback passes evaluation. Refresh the offer detail or progress query when the app resumes and poll conservatively if the UI needs near-real-time updates.

ℹ️ Postbacks can arrive later than the user's return to the app. Show a neutral “tracking” or “pending confirmation” state rather than promising an immediate reward.


Show progress, reward history, and analytics

There are three complementary views of progress.

Progress on an offer

Use these fields on RewardedOffer and RewardedEventType:

  • completedStatus: UNATTEMPTED, STARTED, or COMPLETED;

  • percentageCompleted: a fraction from 0 to 1;

  • eventTypes[].completed: whether the event has completed;

  • eventTypes[].payout: the expected reward for the event; and

  • eventTypes[].reward: the actual credited amount and time, or null before completion.

⚠️ Do not calculate the user's credited reward from payout; use reward, progress, or rewards. Payout configuration can change, while credited progress stores the actual award.

Chronological event history

Newer schemas expose passed progress entries sorted newest first:

query RewardProgress(   $filter: RewardedUserProgressFilter   $pagination: Pagination! ) {   rewardedUser {     progress(filter: $filter, pagination: $pagination) {       totalCount       edges {         node {           offer { id name }           eventType { id name }           reachedAt           theyGet           theyGetPoints         }       }     }   } }
{   "filter": {     "offerId": "12345",     "from": "2026-07-01T00:00:00",     "to": "2026-08-05T23:59:59"   },   "pagination": { "offset": 0, "limit": 50 } }

Rewards grouped by offer

query Rewards($pagination: Pagination!) {   rewardedUser {     rewards(pagination: $pagination) {       totalCount       edges {         node {           offer { id name }           totalPayout { theyGet theyGetPoints }           completed {             eventType { id name description }             theyGet             theyGetPoints             time           }         }       }     }   } }

ℹ️ Older schemas do not accept a filter argument on rewards and may not expose totalPayout. The completed event field is named eventType, not event.

Time-series report

query RewardReport($input: RewardedReportInput!) {   rewardedUser {     report(input: $input) {       start       end       granularity       rows {         dimensions { dimension value }         theyGet         theyGetPoints       }     }   } }
{   "input": {     "start": "2026-07-01T00:00:00",     "end": "2026-08-01T00:00:00",     "granularity": "DAY",     "dimensions": ["DATETIME", "OFFER_ID"]   } }

Supported granularities are DAY, WEEK, MONTH, YEAR, and ALL. Dimensions are DATETIME and OFFER_ID. Use ISO-8601 local date-time strings without a UTC suffix unless your tenant confirms another convention, and agree on the reporting timezone with Swaarm.


Balances and withdrawals

Payout operations are available only when the Rewarded payment flow is deployed and a provider is configured for the tenant.

Read the balance and payment history

query Wallet($first: Int, $offset: Int) {   rewardedUser {     id     balance {       availableBalance       pendingBalance       withdrawnBalance     }     payments(first: $first, offset: $offset) {       totalCount       edges {         node {           id           createdAt           amount           start           end           status           publisherNote           paymentProcessorResponse { key value }         }       }     }   } }
  • availableBalance can be requested for withdrawal.

  • pendingBalance is reserved by requests still awaiting a decision.

  • withdrawnBalance is the amount recorded as paid.

ℹ️ The server is authoritative. Refresh the wallet immediately before enabling the withdrawal action.

Request a withdrawal

The correct mutation is requestRewardedUserPayment:

mutation RequestWithdrawal($input: PartnerPublisherPaymentInput!) {   requestRewardedUserPayment(input: $input) {     id     createdAt     amount     start     end     status     publisherNote     paymentProcessorResponse { key value }   } }
{   "input": {     "rewardedUserId": "<current-rewarded-user-id>",     "amount": 25.0,     "start": "2026-08-05T12:30:00",     "end": "2026-08-05T12:30:00",     "publisherNote": "Requested from iOS app"   } }

The current schema requires start and end, although the Rewarded implementation assigns the request period on the server. Send the current local date-time for both unless your Swaarm integration engineer specifies another convention. Do not send publisherId unless instructed.

The server enforces:

  • an authenticated user can request only their own balance;

  • the amount must be greater than zero;

  • the amount must meet the tenant's configured minimum;

  • the amount cannot exceed availableBalance; and

  • concurrent requests for the same user are serialised.

ℹ️ Creating a request does not guarantee payment. It creates a record in REQUESTED state for review.

⚠️ Do not automatically retry this mutation after a timeout. There is no client idempotency key. First reload payment history and reconcile the result; retry only if no request was created.

Payment states and claim links

Possible states are:

Status

Meaning for the app

REQUESTED

Created by the user and awaiting review.

OPEN

Accepted for internal processing.

APPROVED

Approved; a provider-specific claim link may now be available.

SENT_TO_PAYMENT_PROCESSOR

Sent to a configured payment processor.

PAID

Recorded as paid.

DENIED

Rejected; the reserved amount becomes available again.

Treat paymentProcessorResponse as provider-specific key/value data. If it contains a non-empty url, present a “Claim reward” action and open the URL in the system browser:

const claimUrl = payment.paymentProcessorResponse?.find(   (entry) => entry?.key === "url", )?.value;  if (claimUrl) {   await openInSystemBrowser(claimUrl); }

⚠️ Do not require only PAID before showing a link: some configured providers create the link when the request becomes APPROVED. Validate that the URL uses HTTPS and do not log it.

For tenants without the nested payments field, the equivalent root query is:

query PaymentHistory($userId: String!, $first: Int, $offset: Int) {   rewardedUserPayments(userId: $userId, first: $first, offset: $offset) {     totalCount     edges {       node {         id         createdAt         amount         status         paymentProcessorResponse { key value }       }     }   } }

In-app support

When enabled, support tickets can be loaded through the current user:

query MySupportTickets($first: Int!, $offset: Int!) {   rewardedUser {     supportTickets(first: $first, offset: $offset) {       totalCount       edges {         node {           id           title           body           status           lastMessagedAt           messages {             id             body             time             user { id nickname }             accountManager { id }           }         }       }     }   } }

Create a ticket with the signed-in user's Swaarm ID:

mutation CreateSupportTicket($input: RewardedUserSupportTicketInput!) {   rewardedCreateSupportTicket(ticket: $input) {     id     title     status     lastMessagedAt   } }
{   "input": {     "title": "Missing reward",     "rewardedUserId": "<current-rewarded-user-id>",     "message": {       "body": "I completed the offer but do not see the final event yet."     }   } }

Reply to an existing ticket:

mutation ReplyToSupportTicket(   $ticketId: String!   $message: RewardedUserSupportTicketMessageInput! ) {   rewardedCreateSupportTicketMessage(     ticketId: $ticketId     message: $message   ) {     id     status     lastMessagedAt     messages { id body time }   } }

The server verifies that the authenticated user owns the ticket. Do not use the root rewardedSupportTickets list in an end-user app; it is an admin operation.


Account deletion

When the user confirms deletion, call the authenticated operation:

mutation DeleteMyRewardedAccount {   rewardedRequestDeletion }

On success, clear local tokens and user data. The operation disables the account, removes authentication access, and anonymizes profile fields. Treat it as irreversible from the app and require a clear confirmation step.


Error handling and safe retries

Handle these categories separately:

  • HTTP 401: refresh once, replay a safe query, or return to login if refresh fails.

  • HTTP 4xx/5xx: record a sanitized request ID and show a retryable or permanent error as appropriate.

  • GraphQL errors: inspect the message and path even when HTTP status is 200.

  • Network timeout: retry idempotent queries with exponential backoff and jitter.

  • Mutation timeout: reconcile server state before retrying registration, withdrawal, support-ticket creation, or account deletion.

  • Partial data: use it only if your UI explicitly supports partial results; otherwise fail the screen as one operation.

⚠️ Never expose raw stack traces or internal error details to users. Never log passwords, tokens, complete tracking URLs, claim URLs, social-provider tokens, or full GraphQL variables containing personal data.


Recommended app architecture

Keep API concerns behind a small application service layer:

UI screens   -> auth/session store   -> rewarded API client   -> generated GraphQL operations and types   -> secure token storage

Useful client-side modules are:

  • SessionService: login, refresh, logout, and challenge handling;

  • ProfileService: bootstrap, update, device registration, and deletion;

  • OfferService: offer wall, detail, impression, and click handling;

  • RewardService: progress, rewards, and report refresh;

  • WalletService: balance, payment history, withdrawal reconciliation, and claim links; and

  • SupportService: tickets and replies.

⚠️ Cache display data only for a short time. Never treat cached offer payout, progress, balance, or payment status as authoritative.


Production checklist

Before release, verify all of the following against the production tenant:

  • GraphQL operations are generated from the production-compatible schema.

  • Registration, duplicate email, verification, login, refresh, logout, and password reset work.

  • Tokens are stored securely and removed on logout or deletion.

  • PENDING, ACTIVE, INACTIVE, and fraud-blocked users receive the intended UX.

  • Country and OS filtering return the expected test offers.

  • One visible card produces the expected impression request.

  • A click opens the exact returned tracking URL.

  • A test advertiser postback produces event progress and the correct credited amount.

  • Delayed postbacks do not cause the app to award rewards locally.

  • Pagination, empty states, and removed offers are handled.

  • Monetary amounts use the correct tenant currency and formatting.

  • Withdrawal minimum, insufficient balance, denial, approval, claim link, and reconciliation are tested.

  • Support-ticket access cannot cross user boundaries.

  • Logs and analytics contain no credentials or sensitive tracking and claim URLs.

  • Deep links and social-login provider settings match the released app identifiers.

  • Consent, privacy notice, account deletion, and device-data collection meet the app's legal requirements.


Troubleshooting

The API returns “Access denied”

Confirm Rewarded is enabled for the tenant, the access token is current, and the operation is app-facing rather than admin-only. A user can read or mutate only their own Rewarded data.

No offers are returned

Check offer access and status in Swaarm, then remove filters one at a time. Pass ordering: DEFAULT if the user has already started offers. For native apps, test explicit countries and oses rather than relying on autoFilter.

A click is recorded but progress does not update

A click is not completion. Confirm that the advertiser sent the correct postback, that it passed Swaarm evaluation, and that the offer/event/user attribution is correct. Allow for postback delay before escalating.

The displayed amount differs from the offer payout

Expected payout and credited reward are different concepts. Display credited progress for the wallet and history. Confirm tenant currency, event visibility, and points configuration with Swaarm.

A withdrawal fails

Reload availableBalance, confirm the configured minimum, and look for an existing request created during a previous timeout. If the server says another request is in progress, wait briefly and reconcile payment history rather than submitting repeatedly.

For integration support, contact your Swaarm account manager or support@swaarm.com and provide the tenant, operation name, sanitized timestamp, and a request/correlation ID. Do not send passwords, bearer tokens, provider tokens, or full tracking URLs.

Did this answer your question?