NEXUS
APIs

CloudPay APIs

CloudPay's API (CloudPayClover) serves the CloudPay web app; it is not a public API. Its routes live under /api/v1 on the API's own host, except /health. The web app has its own same-origin route handlers under /api/*, listed in their own group. The last group is what the API calls at Clover.

Environments and configuration

Two environments, development and production, each with its own web container app, API App Service, database and Clerk instance. Development uses Clover's sandbox; production uses Clover's production hosts.

API settings (appsettings sections)
SectionKeys and effect
ConnectionStringsDefaultConnection (SQL Server). Redis is optional; when it is empty the cache is in memory.
CloverClientId, ClientSecret, Environment (sandbox, production or eu, which picks Clover's hosts), WebhookSecret, RemoteApplicationId
ClerkSecretKey (Clerk's backend API), Authority (the issuer whose tokens the API accepts), AuthorizedParties (allowed azp values), WebhookSigningSecret
JwtSecret, Issuer and Audience (both default to CloudPayClover), ExpirationInMinutes (60), for the API's own JWT
KeyVault:VaultUriWhen set, secrets such as the Twilio and SMTP2GO credentials are read from Key Vault with DefaultAzureCredential. When empty, they come from the Secrets section.
Smtp2GoHost, port (2525), TLS, and the sender address and name. The username and password are secret names.
TwilioThe sender number, the names of the account SID and auth-token secrets, and an optional StatusCallbackUrl
ContactSalesInbox for demo requests, Enabled, and the per-IP rate limit (RateLimitPermit 5 per RateLimitWindowMinutes 10)
Cors:AllowedOriginsThe web app's origins. Empty means any origin, without credentials.
Web app environment variables
VariableUsed for
NEXT_PUBLIC_BACKEND_URLThe API's base URL
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYClerk in the browser, with the NEXT_PUBLIC_CLERK_SIGN_IN_URL, …SIGN_UP_URL and fallback-redirect variables
NEXT_PUBLIC_TARGET_CLOVER_DOMAINThe Clover host for the device SDK and the transaction route handlers
NEXT_PUBLIC_REMOTE_APP_IDThe Clover remote app id the SDK connects as
NEXT_PUBLIC_CLARITY_PROJECT_IDTurns Clarity on in production builds
CLERK_SECRET_KEY, CLERK_WEBHOOK_SECRETClerk on the server, and the Clerk webhook route
DMS_API_URL, DMS_CLIENT_ID, DMS_CLIENT_SECRETThe DMS service, from the server only

The five NEXT_PUBLIC_* values are baked into the image when it is built, from the pipeline's variable group for that environment. Server-only values are set on the container app. src/web/.env.example is out of date: it lists the Clover domain and app id without the NEXT_PUBLIC_ prefix and leaves out several server variables.

Authentication and roles

  • Bearer tokens only. Every protected route takes Authorization: Bearer …. The API reads the token's issuer: a Clerk session token goes to the Clerk scheme, anything else to the API's own JWT scheme.
  • Clerk session tokens are checked against Clerk's signing keys, with up to 5 minutes' clock skew, and their azp claim against Clerk:AuthorizedParties. The API then looks the user up (cached for 60 seconds), creating the local user from Clerk the first time, and adds role, merchant_id and clover_merchant_id. Any role inside the token is discarded. Users of a deactivated merchant get no merchant claims, so merchant routes refuse them.
  • The API's own JWT (HS256, 60 minutes, no clock skew) comes from POST /api/v1/auth/session. Its sub is the merchant's id, alongside clover_merchant_id, role and user_provider_id. It is the older path, kept while the web app moves to Clerk tokens; today the web app uses it for merchant routes and Clerk tokens for the session and admin routes.
  • Policies: PlatformAdmin and AdminOnly need role = platform_admin; MerchantAdmin accepts platform_admin or merchant_admin. Routes marked Signed-in user need only a valid token.
  • Webhooks carry no bearer token. Clover's are checked with HMAC-SHA256 over {timestamp}.{body} and Clover:WebhookSecret; Clerk's with Svix and Clerk:WebhookSigningSecret.

Conventions

  • Errors are JSON { error, statusCode }. A broken Clover connection adds tokenStatus: "Disconnected" and requiresReconnect: true (401); a deactivated merchant adds tokenStatus: "Deactivated" and isDeactivated: true (403). Domain and validation errors are 400, a missing merchant 404, anything else 500 with a generic message.
  • Times are always UTC with a Z, whatever the database returns.
  • Money: payment amounts are integer cents; invoice amounts are decimal dollars.
  • Paging: page (from 1) and pageSize (default 20) return { items, totalCount, page, pageSize, totalPages, hasNextPage, hasPreviousPage }. The invoice list uses OData instead: $filter, $orderby, $select, $count, $skip and $top (up to 100), 50 rows by default, with the total in @odata.count.
  • Enums: a payment's status is a name (Captured, Refunded…); transactionType and result are Clover's uppercase strings (AUTH, REFUND, APPROVED…); an invoice's status is a number (see Product Overview).
  • Swagger UI is served in Development only.

What CloudPay calls

ServiceCallPurpose
Clover OAuthoauth/v2/authorize, oauth/v2/token, oauth/v2/refresh, and the v1 oauth/token exchange as a fallback for first installsConnect a merchant and keep its tokens fresh (API)
Clover REST v3Merchant, payment, order and refund reads (last group)Webhook processing, receipts, the logo and time zone (API)
Clover REST v3Payments, refunds, orders, devices, items, and the merchant's name and time zoneLive transaction lists, devices, inventory and settings sync (web route handlers)
Clover Hosted Checkoutinvoicingcheckoutservice/v1/checkoutsInvoice payment pages (API)
Clover Cloud Pay Displayremote-pay-cloud over a WebSocketSales, refunds and receipt prompts on the terminal (browser)
Clerk Backend APIUsers, user metadata, invitationsSign-in context, roles and invitations (API)
DMS servicePOST api/v1/auth/token with client credentials, then searches, lookups and settingsRepair orders, parts invoices, Reynolds settings (web route handlers)
TwilioMessagesInvoice and receipt texts (API)
SMTP2GOSMTPInvoice, receipt, paid-notification and demo-request emails (API)

The API retries transient failures from Clover and Clerk 3 times with exponential back-off. The DMS client caches its token until a minute before expiry and retries once after a 401.

Endpoint reference

Grouped by audience. Paths are relative to the API base URL; {id}-style segments are GUIDs unless noted.

Session and Clover OAuth

Base /api/v1.

EndpointAuthNotes
POST/api/v1/auth/sessionSigned-in userClerk session token only. Body { cloverMerchantId }, used when the Clerk account has none. Checks that the merchant exists and its Clover connection is live, creates or updates the local user, and returns { token, role, merchant, user, connectionStatus }. 400 with a tokenStatus when Clover needs reconnecting; 404 for an unknown merchant.
GET/api/v1/oauth/authorizeAnonymous?code_challenge=&state=&redirect_uri=. Returns { authorizationUrl } for Clover OAuth v2 with PKCE (S256).
POST/api/v1/oauth/tokenAnonymous{ code, codeVerifier, redirectUri, merchantId }. Exchanges the code with Clover, finds or creates the merchant (by Clover id, then by merchantId for the admin flow), stores the encrypted tokens, and syncs the name and time zone. Returns { token, merchantId, expiresIn }.
POST/api/v1/oauth/refreshSigned-in userRefreshes the caller's merchant's Clover tokens. The web app does not use it.
POST/api/v1/oauth/revokeSigned-in userMarks the caller's merchant's Clover tokens revoked. 204.

Merchant, users and preferences

Base /api/v1.

EndpointAuthNotes
GET/api/v1/merchantSigned-in userThe merchant: id, Clover id, business name, email, active flag, created date and settings.
PUT/api/v1/merchantSigned-in user{ businessName?, email?, isActive?, settings? }. The logo, and any display groups left out, are kept.
POST/api/v1/merchant/logo/syncSigned-in userCopies the merchant's logo from Clover when it has changed. Returns { logoUrl }.
GET/api/v1/merchant/connection-statusSigned-in user{ isConnected, tokenStatus, accessTokenExpiresAt, lastRefreshedAt }
GET/api/v1/merchant/tokenSigned-in user{ accessToken, merchantId, expiresAt } (epoch ms): the merchant's Clover token, which the browser needs for the device SDK and the transaction route handlers.
GET/api/v1/merchant/usersMerchant adminThe merchant's users. page, pageSize, search, role, department, sortBy (name, email, role, department, createdAt), sortDescending.
PUT/api/v1/merchant/users/{id}Merchant admin{ role: merchant_admin | merchant_user, department?: Parts | Service }. Updates Clerk too. Refuses your own role and platform admins.
GET/api/v1/user/preferencesSigned-in userThe caller's { theme, notificationsEnabled, timeZone, favoriteDevice, notifications }. Defaults for a user not stored yet.
PUT/api/v1/user/preferencesSigned-in userReplaces all of them. 400 for an unknown time zone or a favourite device without an id.

Payments and refund notes

Base /api/v1/payments. These read the API's copy of payments, written by Clover's webhooks.

EndpointAuthNotes
GET/api/v1/paymentsSigned-in userpage, pageSize, status (Pending, Authorized, Captured, Voided, Refunded, Failed), startDate, endDate. Newest first.
GET/api/v1/payments/{cloverPaymentId}Signed-in userOne payment. 404 when the API has no copy of it.
GET/api/v1/payments/lookupSigned-in user?externalReferenceId=: every payment and refund carrying that reference (an RO or invoice number), newest first.
POST/api/v1/payments/lookup/batchSigned-in user{ externalReferenceIds: [...] }, up to 100. Returns { reference: [payments] }, leaving out references with none.
GET/api/v1/payments/summarySigned-in userstartDate, endDate. Totals in cents (amount, tips, tax), counts (all, captured, failed), and the refund count and total.
POST/api/v1/payments/{sourceCloverPaymentId}/refund-noteSigned-in user{ note }, required, up to 1,000 characters. Creates or replaces the reason for that payment's refund or void.
GET/api/v1/payments/{sourceCloverPaymentId}/refund-noteSigned-in user{ sourceCloverPaymentId, note, createdByUserId, createdAt }, or 404.
GET/api/v1/payments/refund-notes/keysSigned-in userThe ids of every payment with a saved reason, so lists can flag them in one request.

Invoices and receipts

Base /api/v1.

EndpointAuthNotes
GET/api/v1/invoicesSigned-in userOData list (see Conventions), e.g. $filter=Status eq 0&$orderby=Created desc&$count=true&$top=25.
GET/api/v1/invoices/statsSigned-in userstartDate, endDate, status. Outstanding and paid totals, counts per status, average days to payment, and the overdue count (outstanding for more than 30 days). One SQL statement.
GET/api/v1/invoices/{id}Signed-in userOne invoice.
POST/api/v1/invoicesSigned-in user{ amount, taxAmount, tipAmount, totalAmount, sendToEmail, sendToSMS, orderId?, dmsSnapshot?, … }. Needs a valid email or phone. Saves the invoice as Outstanding and sends it. 201.
POST/api/v1/invoices/{id}/cancelSigned-in userRefuses a paid invoice; cancelling twice is harmless.
POST/api/v1/invoices/resendSigned-in user{ invoiceId, sendEmail?, sendSMS? }. Outstanding invoices only. New contacts replace the old ones.
POST/api/v1/receipts/sendSigned-in user{ cloverPaymentId | cloverRefundId, sendEmail?, sendSMS? }. Fetches the payment or refund from Clover and sends the receipt. Returns { cloverPaymentId, emailSent, smsSent, sentAt }, and fails only when every requested channel failed.

Admin

Base /api/v1/admin. Platform admins only.

EndpointAuthNotes
GET/api/v1/admin/merchantsPlatform adminpage, pageSize, search (name or email), isActive, isConnected, sortBy (businessName, email, createdAt, isActive), sortDescending.
GET/api/v1/admin/merchants/{id}Platform adminDetail, with the settings and the connection's last refresh.
POST/api/v1/admin/merchantsPlatform admin{ businessName, email?, cloverMerchantId?, settings? }. Invites the email as merchant admin.
PUT/api/v1/admin/merchants/{id}Platform admin{ businessName?, email?, isActive?, settings? }
PATCH/api/v1/admin/merchants/{id}/statusPlatform admin{ isActive }. 204.
POST/api/v1/admin/merchants/{id}/oauth/initiatePlatform adminAn authorization URL with the merchant's id as state. The web app builds its own PKCE request instead.
POST/api/v1/admin/merchants/{id}/oauth/revokePlatform adminMarks the merchant's Clover tokens revoked. 204.
GET/api/v1/admin/merchants/{id}/usersPlatform adminSame filters as the merchant's own users list.
PUT/api/v1/admin/merchants/{id}/users/{userId}Platform adminSame rules as the merchant's own version.
GET/api/v1/admin/merchants/users/searchPlatform admin?query= (2 or more characters): up to 10 Clerk users.
POST/api/v1/admin/merchants/{id}/link-userPlatform admin{ userId, cloverMerchantId }: writes the Clover merchant id to the Clerk user.
POST/api/v1/admin/merchants/{id}/unlink-userPlatform admin{ userId }: removes it.
POST/api/v1/admin/merchants/{id}/assign-rolePlatform admin{ userId, role }, any of the three roles. The web app does not use it.
POST/api/v1/admin/merchants/{id}/invitationsPlatform admin{ emailAddress, userRole: merchant_admin | merchant_user }. Needs a connected merchant. Clerk emails the invitation.
GET/api/v1/admin/merchants/{id}/invitationsPlatform adminPending invitations.
DELETE/api/v1/admin/merchants/{id}/invitations/{invitationId}Platform adminRevokes it in Clerk. 204.
GET/api/v1/admin/dataprotection-healthPlatform adminEncrypts and decrypts a probe with the token key ring: { healthy, keysPersisted, ciphertextLength }.

Public routes and webhooks

No sign-in.

EndpointAuthNotes
GET/api/v1/hosted-checkout/{invoiceId}AnonymousThe invoice pay link. Redirects to Clover Hosted Checkout, or returns an HTML page for a paid (200), unavailable (404) or failing (500) invoice.
POST/api/v1/contact/demo-requestAnonymous{ name, email, company, phone?, message, website }, where website is the honeypot. 202; 400 on validation; 429 over the rate limit; 503 when the email could not be sent.
POST/api/v1/webhooks/cloverSigned webhookClover's events. A body with verificationCode is Clover's setup check and is only logged. Otherwise each P: (payment) or R: (refund) object is fetched from Clover and upserted, and failures are retried in the background. Answers 200 once the body is accepted.
POST/api/v1/webhooks/clerkSigned webhookOn user.created, applies the newest pending invitation for the user's email.
POST/api/v1/webhooks/twilio/sms-statusAnonymousTwilio's delivery-status callback. Logged only.
GET/healthAnonymous200 while the process is up. No dependency checks.

Web app route handlers

Same-origin /api/* on the web app's host. Clover OAuth below means the caller passes the merchant's Clover token, from GET /api/v1/merchant/token, in a header or the body.

EndpointAuthNotes
GET/api/get-paymentsClover OAuthClover's latest payments, with voids and refunds. Feeds Today's Transactions.
GET/api/get-all-paymentsClover OAuthPayments, voids and refunds merged, sorted and paged. status, type, dates, and all=true for the CSV export. Defaults to the last 90 days.
GET/api/get-refundsClover OAuthRefunds for a date range, with a limit.
GET/api/get-invoice-paymentsClover OAuth?paymentId=: every payment and refund on that payment's Clover order.
GET/api/get-inventoryClover OAuthItems with stock and categories; search, sort and paging.
POST/api/scan-devicesClover OAuthThe merchant's devices, each one's last payment, and the merchant's address.
POST/api/merchant-timezoneClover OAuthReads the business name and time zone from Clover. PUT on the same path writes them.
POST/api/dms/repair-ordersSigned-in userRO search through the DMS service. The merchant comes from the Clerk session. /lookup fetches one RO.
POST/api/dms/parts-invoicesSigned-in userParts-invoice search. /lookup fetches one.
GET/api/dms/settingsPlatform admin?merchantId=: the merchant's Reynolds settings, or null when it is not provisioned. POST saves them.
POST/api/webhooks/clerkSigned webhookSvix-verified. Logs user.created; the API's webhook does the work.
POST/api/csp-reportAnonymousContent-Security-Policy violation reports, written to the log. 204.

Clover (outbound)

Called by the API on Clover's API host for the configured environment. REST reads use the merchant's OAuth token.

EndpointAuthNotes
GETv3/merchants/{mId}/payments/{id}Clover OAuthexpand=cardTransaction,refunds,order. Webhook upserts (refunds arrive nested in the payment) and receipts.
GETv3/merchants/{mId}/refunds/{id}Clover OAuthexpand=payment.cardTransaction. Refund webhooks and refund receipts.
GETv3/merchants/{mId}/orders/{id}Clover OAuthexpand=lineItems: the notes that carry an invoice id.
GETv3/merchants/{mId}Clover OAuthexpand=properties,logos: the name, time zone and logo.
POSTinvoicingcheckoutservice/v1/checkoutsClover OAuthHeader X-Clover-Merchant-Id. One line item for the invoice total, the invoice id in the cart and line-item notes, and the customer's email and phone. Returns href, checkoutSessionId and an expiry.
POSToauth/v2/tokenClient credentials{ client_id, client_secret, code }. The merchant id comes from the response or the token's merchant_uuid. For first installs from the App Market it falls back to the v1 exchange, then migrates with grant_type: authorization_code_legacy.
POSToauth/v2/refreshClient credentialsRefreshes a merchant's tokens with its refresh token.