API reference
Every procedure an API key may call, with its request fields and an example answer. The guide,
The Chargeless API: your backend, explains the basics (keys, errors, retries, limits)
and walks through the flows; this page is the list. The fields here come from
openapi/backend-api/v1.yaml, which is generated from the protos and holds every field with its
description. Where this page and that file differ, the file wins.
These 20 are all there is for an API key. Any other procedure answers permission_denied.
How to read this page
Every procedure is a POST of a JSON body to https://api.chargeless.io/{service}/{procedure},
with your key and Content-Type: application/json. The examples show only the body:
curl -sS https://api.chargeless.io/sr.cases.v1.CasesService/ListActions \
-H "Authorization: Bearer $CHARGELESS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pageSize": 50}'- Field names are lowerCamelCase, enums are their names as strings, 64-bit integers
(
amountMinor,number, counts) are strings in answers, times are RFC 3339 in UTC. A field at its default (empty, zero, false) is left out of an answer, so read a missing field as its default. - Money is
{"amountMinor": "1999", "currency": "USD"}: an integer in the currency's minor unit and an ISO 4217 code. Totals are per currency, never summed across currencies. - Ids are opaque strings. The ledger's ids (a customer, a transaction, a subscription, an action,
a case) are what the API returns and takes back; a provider's id (
ch_…,sub_…,cus_…) is returned beside them and accepted only where a field says so. - A few request fields exist only for Chargeless's own chat, help site and ticket automation
(
transcript,ticketId,answeredMessageId,sessionIdon a ticket,address,portalBindingId,includeRetention). A request from an API key that sets one of them is refused withinvalid_argument. They are left out below. - Some answer fields are sent to the panel's members only and are always empty for an API key:
a customer's email (
customerEmail,maskedCustomerEmail,SubscriptionCustomer.email), a provider dashboard link, a case's notes and customer statistics, and the plan list ofListSubscriptions.
Pages
The lists page with pageSize and pageToken: send nextPageToken back as pageToken
unchanged, with the same filters, until it comes back empty. A token is opaque and works only
with the filters it was made for.
| Procedure | pageSize |
Time range |
|---|---|---|
ListActions, ListCases, ListProviderCalls |
1–200, default 50 | from, to optional (not on ListCases) |
ListRefunds, ListDisputes, ListFraudWarnings, ListSubscriptions |
1–100, default 50 | from and to required, at most 400 days apart |
ListVersions |
at most 100 a page | pages with beforeVersion and hasMore instead |
Lookup |
limit: default 20, at most 100 charges |
none |
from is inclusive and to exclusive. Lists are newest first.
Identity
Prove who a customer is before you read their charges or act for them. Chargeless binds a proof to a set of customer rows, at most one account customer and one guest customer per payment connection (a guest is the payer of guest payments, known by the checkout email): pass the whole set on.
Verify
sr.identity.v1.IdentityService/Verify checks an identity token your server signed (HS256 with the
Widget Signing Secret; the claims are in the guide) and
returns the customer it binds.
| Field | Type | Meaning |
|---|---|---|
token |
string | The signed token. Required |
provider |
string | Optional: stripe or merchant. Empty means the kind of the connection the token resolves in |
{"token": "eyJhbGciOiJIUzI1NiIs…"}{
"customerId": "01a1aff4-5117-79a0-a966-68ed1a264ea1",
"providerCustomerId": "cus_Q8x…",
"method": "IDENTITY_METHOD_SIGNED_TOKEN",
"connectionId": "01a1aff4-50ab-74ce-9c05-b142b747606c",
"verifiedAt": "2026-11-06T09:30:00Z",
"expiresAt": "2026-11-06T09:45:00Z"
}customerId is the ledger customer: pass it as identity.customerId on Submit and in
customerIds on Lookup. A token that does not verify or has expired is unauthenticated; a
customer the ledger does not hold yet is not_found.
LookupCustomer
sr.identity.v1.IdentityService/LookupCustomer finds the customers an email address belongs to:
in each live payment connection, the one customer with that address on file. An address that
several customers share in one connection binds nobody there.
| Field | Type | Meaning |
|---|---|---|
email |
string | Matched case-insensitively |
provider |
string | Optional: stripe or merchant to look in connections of that kind only |
{"email": "[email protected]"}{
"found": true,
"customerId": "01a1aff4-5117-79a0-a966-68ed1a264ea1",
"providerCustomerId": "cus_Q8x…",
"customers": [
{
"customerId": "01a1aff4-5117-79a0-a966-68ed1a264ea1",
"providerCustomerId": "cus_Q8x…",
"connectionId": "01a1aff4-50ab-74ce-9c05-b142b747606c"
}
]
}A lookup is not a proof by itself: it says who an address belongs to in your records. Use it for
an address your own system has verified (a signed-in customer, a confirmed address), never for an
address someone typed into a form; for those, send the request through
SubmitCustomerRequest with email, and the address's owner proves it.
Transactions
Lookup
sr.transactions.v1.TransactionsService/Lookup lists a verified customer's recent charges, newest
first, and their subscriptions, across their payment connections, each charge with what remains
refundable. A merchant-hosted payment system is read first; when it cannot be read, the answer is
unavailable and nothing stale is returned.
| Field | Type | Meaning |
|---|---|---|
customerIds |
string[] | The verified set: ledger customer ids, at most one account and one guest customer per connection and at most 10 |
customerId |
string | One customer; with customerIds empty, the set of this one |
limit |
integer | Charges to return across the set: default 20, at most 100 |
{"customerIds": ["01a1aff4-5117-79a0-a966-68ed1a264ea1"], "limit": 20}{
"transactions": [
{
"id": "01a1aff4-53d6-7334-8e2d-9d3d35bfcff5",
"publicRef": "PAY-7K3F9QS",
"provider": "stripe",
"providerTransactionId": "ch_3Q8x…",
"amount": {"amountMinor": "1900", "currency": "EUR"},
"refundableRemainder": {"amountMinor": "1900", "currency": "EUR"},
"state": "succeeded",
"occurredAt": "2026-11-02T14:05:11Z",
"cardBrand": "visa",
"cardLast4": "4242",
"subscriptionId": "01a1aff4-5500-7a1c-8b2e-0c7d1e2f3a4b",
"connectionId": "01a1aff4-50ab-74ce-9c05-b142b747606c",
"connectionLabel": "Stripe"
}
],
"subscriptions": [
{
"id": "01a1aff4-5500-7a1c-8b2e-0c7d1e2f3a4b",
"publicRef": "SUB-M2XA4TY",
"provider": "stripe",
"providerSubscriptionId": "sub_1Q…",
"state": "active",
"price": {"amountMinor": "1900", "currency": "EUR"},
"billingInterval": {"unit": "BILLING_INTERVAL_UNIT_MONTH", "count": 1},
"currentPeriodEnd": "2026-12-02T14:05:11Z",
"connectionId": "01a1aff4-50ab-74ce-9c05-b142b747606c",
"connectionLabel": "Stripe"
}
]
}Every payment and subscription has a short public reference, publicRef ("PAY-" or "SUB-" and
seven symbols, the last a check symbol; ADR 0042). It is unique in your workspace, never changes,
and is what your customers and your team see in the chat, the help site, mails and the panel: print
it on your receipts and account pages so a customer can quote it. It is a pointer, never proof of
who someone is. The API still takes ledger ids as targetId.
- A transaction's
stateispending,succeeded,partially_refunded,refunded,disputedorfailed; a subscription's isactive,trialing,past_due,canceled,unpaid,incomplete,paused,graceorexpired. Treat an unknown value as unspecified. refundedcounts every refund that took effect, whoever made it.refundReversedAtis set when a provider later reversed one of Chargeless's refunds: the refund still counts, but the money did not stay with the customer.connectionLabelis text your team chose; show it as data.- A transaction carries its order as the payment provider states it:
lineItems(at most the first 10 lines;lineCountsays how many there are),totals(subtotal, discount, shipping, tax and whether tax is included) andlinesState. ReadlinesStatebefore taking an emptylineItemsto mean there are none:LINES_STATE_UNREAD(not looked up yet; treat an unspecified state the same way),LINES_STATE_COMPLETE,LINES_STATE_TRUNCATED(the payment has more than 100 lines),LINES_STATE_NONE(the provider has no lines for this payment) orLINES_STATE_UNSUPPORTED(the provider does not report lines). - A line has a
kind(LINE_ITEM_KIND_ITEMorLINE_ITEM_KIND_SHIPPING), aname,productRefandsku(both empty on a merchant-hosted provider's line), aquantity, the amountsunitAmount,subtotal(before the line's discount and tax),discount,taxandtotal, andtaxRatePpm(parts per million: 1% is 10,000),taxName,taxInclusive,periodStart,periodEndandproration. Nothing is computed: a value the provider did not state is absent. Amounts are in the payment's currency and can be negative (a proration credit), and the lines need not add up to the payment'samount. Line names are the merchant's text; show them as data. Lines are for display: a refund is always an amount of the payment.
Policy
Evaluate
sr.policy.v1.PolicyService/Evaluate previews what the published policy would decide for a
proposed refund or cancellation, without recording anything. A preview is not a promise: the
facts can change before you submit, and Submit decides again.
| Field | Type | Meaning |
|---|---|---|
actionType |
enum | ACTION_TYPE_REFUND or ACTION_TYPE_CANCEL_SUBSCRIPTION |
targetId |
string | The ledger transaction (refund) or subscription (cancellation) |
source |
enum | SOURCE_CHAT for a customer you verified, as Submit records it |
identityIds |
string[] | The verified set, as on Submit |
identityId |
string | One verified customer; required with SOURCE_CHAT when identityIds is empty |
identityMethod |
enum | How the customer was verified: IDENTITY_METHOD_SIGNED_TOKEN, _EMAIL_OTP or _EMAIL_LINK. Unspecified is denied, as Submit would deny it |
reasonCode |
string | duplicate, forgot_to_cancel, unrecognized, not_as_described, service_issue or other |
amount |
Money | Optional partial refund in the charge's currency; the whole remainder when left out |
{
"actionType": "ACTION_TYPE_REFUND",
"targetId": "01a1aff4-53d6-7334-8e2d-9d3d35bfcff5",
"reasonCode": "duplicate",
"source": "SOURCE_CHAT",
"identityIds": ["01a1aff4-5117-79a0-a966-68ed1a264ea1"],
"identityMethod": "IDENTITY_METHOD_SIGNED_TOKEN"
}{
"decision": {
"outcome": "OUTCOME_ALLOW",
"policyVersion": "v7",
"amount": {"amountMinor": "1900", "currency": "EUR"},
"reasons": [{"code": "eligible", "text": "…"}],
"trace": [
{"name": "kill_switch", "passed": true},
{"name": "identity", "passed": true, "detail": "signed_token"},
{"name": "target_state", "passed": true, "detail": "19.00 EUR refundable; requested 19.00 EUR"}
]
}
}The decision is the same object an action carries (below, Decision).
GetPublished
sr.policy.v1.PolicyService/GetPublished returns the published policy. The body is {}.
{
"policyVersion": "v7",
"publishedAt": "2026-11-01T08:00:00Z",
"document": "eyJ2ZXJzaW9uIjoxLCJpZGVudGl0eSI6…"
}document is the canonical policy JSON as bytes, so it arrives base64-encoded: decode it to
read the policy (policy/v1 in the contracts). A workspace with no published policy answers
failed_precondition.
ListVersions
sr.policy.v1.PolicyService/ListVersions lists every stored version, newest first, at most 100 a
page. Versions are never changed or removed.
| Field | Type | Meaning |
|---|---|---|
beforeVersion |
integer | Only versions older than this number; 0 or left out starts from the newest |
{
"versions": [
{
"policyVersion": "v7",
"number": 7,
"published": true,
"publishedAt": "2026-11-01T08:00:00Z",
"documentSha256": "e83c1abc4fac148b23fadf553c571b1ce87fa8d84de80fbd5de0c1b935d55464",
"note": "Lower the auto-refund cap for TRY"
}
],
"hasMore": true
}With hasMore, ask again with beforeVersion set to the last number. A superseded version
carries supersededAt; one that restored an earlier version carries restoredFromVersion.
GetVersion
sr.policy.v1.PolicyService/GetVersion returns one version and its document (base64, as above).
{"policyVersion": "v6"}The answer is {"version": {…as in ListVersions…}, "document": "…"}; a version the workspace does
not have is not_found.
Actions
Submit
sr.actions.v1.ActionsService/Submit decides an action with the published policy, records it and
starts its execution at the payment provider. The flow around it is in
the guide.
| Field | Type | Meaning |
|---|---|---|
idempotencyKey |
string | Your key for the intent, reused on every retry: backend:{your request id}:{action}:{target}. Required |
type |
enum | ACTION_TYPE_REFUND, ACTION_TYPE_CANCEL_SUBSCRIPTION or ACTION_TYPE_HANDOFF |
targetId |
string | The ledger transaction (refund) or subscription (cancellation). Required for both; left out for a handoff |
identity |
object | customerIds (the verified set), customerId and method, as Verify or LookupCustomer returned them |
reasonCode |
string | As for Evaluate |
amount |
Money | Optional partial refund; the whole refundable remainder when left out |
requestedBy |
string | Your label for where the request came from, shown on the action. Required |
handoffReason |
string | Required with ACTION_TYPE_HANDOFF: a code of [a-z0-9_], at most 64 characters. Anything else is recorded as unspecified, and the handoff still happens |
waitSeconds |
integer | How long to wait for the provider's outcome before answering: default 5, at most 10 |
locale |
string | Optional BCP 47 language (en, tr, pt-BR): the language of a ticket a handoff opens |
{
"idempotencyKey": "backend:ord_8812:refund:01a1aff4-53d6-7334-8e2d-9d3d35bfcff5",
"type": "ACTION_TYPE_REFUND",
"targetId": "01a1aff4-53d6-7334-8e2d-9d3d35bfcff5",
"reasonCode": "duplicate",
"identity": {
"customerIds": ["01a1aff4-5117-79a0-a966-68ed1a264ea1"],
"method": "IDENTITY_METHOD_SIGNED_TOKEN"
},
"requestedBy": "account-page",
"waitSeconds": 5
}{
"action": {
"id": "01a1aff5-427d-765f-a005-d2675fe290ab",
"caseId": "01a1aff5-427b-7a35-b2a4-1f8e6d0c9b11",
"type": "ACTION_TYPE_REFUND",
"status": "ACTION_STATUS_EXECUTED",
"targetId": "01a1aff4-53d6-7334-8e2d-9d3d35bfcff5",
"customerId": "01a1aff4-5117-79a0-a966-68ed1a264ea1",
"amount": {"amountMinor": "1900", "currency": "EUR"},
"reasonCode": "duplicate",
"decision": {"outcome": "OUTCOME_ALLOW", "policyVersion": "v7", "reasons": [{"code": "eligible", "text": "…"}]},
"policyVersion": "v7",
"providerRef": "re_3Q…",
"source": "SOURCE_CHAT",
"requestedBy": "account-page",
"createdAt": "2026-11-06T09:31:02Z",
"executedAt": "2026-11-06T09:31:03Z"
},
"replayed": false
}An action from an API key is recorded with source: SOURCE_CHAT, decided exactly as a chat in the
widget. A handoff goes to your handoff destination: the team queue in the panel, the
handoff.requested webhook, or both. A replayed key returns the first action with replayed: true; the same key with another type or
target is failed_precondition. Dispute actions are Chargeless's own and are refused with
invalid_argument.
When your policy refunds the last charge with a cancellation (subscriptions.refund_last_charge_if)
and an allowed cancellation qualifies, the response also carries followup: that refund's own
action, decided like any refund in the same transaction, with its own status (it can be denied or
handed off while the cancellation goes through). Its key is the cancellation's key followed by
:refund:{transaction_id} (44 characters more), so keep a cancellation's key under 160 characters:
a longer one can make the refund's own key too long for the provider, and it then fails and goes to
your team. Get on the cancellation returns it too, and Submit waits for it as it waits for the
cancellation.
Get
sr.actions.v1.ActionsService/Get returns one action, as Submit answered it, with its current
status.
{"actionId": "01a1aff5-427d-765f-a005-d2675fe290ab"}The answer is {"action": {…}}.
Action
| Field | Meaning |
|---|---|
status |
ACTION_STATUS_EXECUTED, _PENDING, _DENIED, _HANDOFF or _FAILED (each ACTION_STATUS_…). DENIED, HANDOFF, EXECUTED and FAILED are final. APPROVED is the short moment between the decision and the execution |
type |
ACTION_TYPE_REFUND, _CANCEL_SUBSCRIPTION, _HANDOFF, _DISPUTE_ACCEPT or _DISPUTE_SUBMIT_EVIDENCE |
source |
Who asked: SOURCE_CHAT (the widget and API keys), SOURCE_TICKET, SOURCE_PORTAL, SOURCE_FRAUD_ALERT, SOURCE_DISPUTE or SOURCE_MERCHANT_USER (your team, with actorUserId) |
decision |
The decision behind the status (below) |
amount |
The amount decided on; for a refund, what was or will be refunded |
providerRef |
The provider's id for the refund or cancellation, once it has one |
failure |
Set with FAILED: how the provider refused at execution |
billedBy |
Paddle or FastSpring when that merchant of record billed the charge and confirms the refund itself |
refundReversedAt |
Set when the provider reversed the refund after it succeeded; the action stays EXECUTED |
manualOverride |
True when a member of your team allowed the action beyond policy |
caseId, customerId, targetId, reasonCode, requestedBy, policyVersion, createdAt, executedAt |
As named |
Decision
| Field | Meaning |
|---|---|
outcome |
OUTCOME_ALLOW, OUTCOME_DENY or OUTCOME_HANDOFF |
reasons |
Ordered {code, text, ruleId}: a stable machine code (outside_window, over_max_amount, rule_matched, …), English text for people, and ruleId when one of your rules decided. The set of codes is open: show an unknown code with its text |
policyVersion |
The version that decided, "v<n>" |
amount |
The refund amount, in the charge's currency |
settlementAmount |
The same amount in your settlement currency, when the limits were compared in it |
limitsApplied |
{name, limit, value, headroom} for each limit the decision measured |
trace |
Each step of the fixed evaluation order: {name, passed, detail} |
followups |
Suggested next actions, such as refunding the last charge after a cancellation |
Requests from your form
SubmitCustomerRequest
sr.tickets.v1.RequestIntakeService/SubmitCustomerRequest takes one request from your own support
form, opens a ticket and has the policy decide it. The flow is in
the guide.
| Field | Type | Meaning |
|---|---|---|
requestId |
string | Your id for the request, 1–128 characters of [A-Za-z0-9_-]. The idempotency key |
action |
enum | ACTION_TYPE_REFUND or ACTION_TYPE_CANCEL_SUBSCRIPTION |
targetId |
string | A ledger id, or your provider's id for the charge or subscription |
connectionId |
string | The payment connection of a provider id; empty: your first connection |
identityToken or email |
string | Exactly one proof: the signed identity token, or the address the form collected (which proves nothing until its owner confirms it) |
reasonCode |
string | As for Evaluate; empty reads as other |
amount |
Money | Optional partial refund |
message |
string | What the customer wrote, at most 4,000 characters |
requesterName |
string | The name the customer typed, at most 100 characters; never identity |
locale |
string | The form's language: the ticket's, its page's and its mail's |
{
"ticketId": "01a1b00b-2c4e-7f10-9a8b-6d5e4c3b2a19",
"ticketNumber": "1042",
"pageUrl": "https://acme.help.chargeless.io/t/9fK2…",
"outcome": "CUSTOMER_REQUEST_OUTCOME_IDENTITY_REQUIRED",
"reasonCode": "identity_required",
"actionId": "01a1b00b-2c51-7d0e-8f3a-5b4c3d2e1f00",
"replayed": false
}outcome is CUSTOMER_REQUEST_OUTCOME_EXECUTED, _PENDING, _DENIED, _HANDOFF or
_IDENTITY_REQUIRED. Errors beyond invalid_argument are failed_precondition with a detail
naming the reason: TARGET_NOT_FOUND, INVALID_IDENTITY_TOKEN, NO_REPLY_ADDRESS (the token's
customer has no address on file), TENANT_NOT_LIVE, or KEY_REUSED (the same requestId with
other content).
Workspace
GetTenant
sr.tenant.v1.TenantService/GetTenant returns your workspace. The body is {}. It is the one call
that still answers while Chargeless has suspended the workspace.
{
"tenant": {
"id": "01a1b700-5c8b-78d0-9ac5-0e50fde96cc5",
"slug": "acme",
"name": "Acme Store",
"status": "TENANT_STATUS_ACTIVE",
"settlementCurrency": "USD",
"timeZone": "Europe/Istanbul",
"activatedAt": "2026-11-07T17:13:36.978820Z",
"handoffDestination": "HANDOFF_DESTINATION_TEAM_QUEUE_AND_WEBHOOK",
"widgetOrigins": ["https://shop.acme.example"]
},
"helpSite": {"slug": "acme", "url": "https://acme.help.chargeless.io", "chosen": true},
"customerPortal": {"enabled": true, "url": "https://acme.help.chargeless.io", "codeSignIn": true}
}statusisTENANT_STATUS_ONBOARDING(not live yet:onboardinglists what is left),TENANT_STATUS_ACTIVEorTENANT_STATUS_SUSPENDED.killSwitch(your emergency stop),operatorHold(Chargeless's) andcircuitBreakerOpenare left out while false. While any of them is true, no automatic refund or cancellation runs.testis true for a test workspace.- The answer also carries the assistant's tone (
assistantStyle), your help links and the help site's branding.tenant.paymentProviderandtenant.handoffare deprecated: a workspace has several payment connections and webhook endpoints.
Actions, cases and payments
sr.cases.v1.CasesService: what happened in your workspace, for your own reporting and
reconciliation. Each of these is read-only.
ListActions
Every action, newest first.
| Field | Type | Meaning |
|---|---|---|
from, to |
time | Optional bounds on createdAt |
types |
enum[] | Any of these action types |
statuses |
enum[] | Any of these statuses |
sources |
string[] | Any of chat, fraud_alert, dispute, system, ticket, merchant_user |
pageSize, pageToken |
1–200, default 50 |
{"from": "2026-11-01T00:00:00Z", "statuses": ["ACTION_STATUS_HANDOFF"], "pageSize": 50}The answer is {"actions": [Action…], "nextPageToken": "…"}.
ListCases
The cases (a chat, a ticket, a fraud warning, a dispute, a staff action, a portal request), newest first, each with its latest decision and amount.
| Field | Type | Meaning |
|---|---|---|
types |
enum[] | CASE_TYPE_CHAT_REFUND, _CHAT_CANCELLATION, _FRAUD_ALERT, _DISPUTE, _TICKET, _STAFF_ACTION, _PORTAL_REFUND, _PORTAL_CANCELLATION |
statuses |
enum[] | CASE_STATUS_OPEN, _RESOLVED, _HANDED_OFF, _FAILED |
connectionIds |
string[] | At most 10 payment connections; empty means all |
pageSize, pageToken |
1–200, default 50 |
{
"cases": [
{
"id": "01a1b7d7-cf73-7d19-aead-31ccf144948c",
"number": "348",
"type": "CASE_TYPE_TICKET",
"status": "CASE_STATUS_HANDED_OFF",
"customerId": "01a1b700-5e57-7515-bc66-6bf4bec0d27d",
"transactionId": "01a1b700-83f0-7f25-af58-82a8474e203d",
"ticketId": "01a1b7d6-c2e2-785c-af0d-e6c7a0209674",
"connectionId": "6e776da5-5c08-8dd5-aa9d-6c9736dfdbea",
"connectionLabel": "Stripe",
"openedAt": "2026-11-07T21:08:56.302765Z",
"closedAt": "2026-11-07T21:08:56.302765Z",
"summary": {
"latestDecision": {
"actionId": "01a1b7d7-cf75-7c0b-b3b9-a9013967a01a",
"actionType": "ACTION_TYPE_REFUND",
"actionStatus": "ACTION_STATUS_HANDOFF",
"outcome": "OUTCOME_HANDOFF",
"reasonCode": "reason_not_auto_refundable",
"decidedAt": "2026-11-07T21:08:56.309789Z"
},
"amount": {"amountMinor": "1999", "currency": "USD"},
"amountKind": "CASE_AMOUNT_KIND_REQUESTED"
}
}
],
"nextPageToken": "MTc5NDA4NTcz…"
}number is what people quote ("CS-348"). A filter by connection is bounded in time; past it the
answer is deadline_exceeded (narrow the filter, or retry).
GetCase
One case with its timeline, its actions and the facts each decision was evaluated on.
{"caseId": "01a1b7d7-cf73-7d19-aead-31ccf144948c"}| Answer field | Meaning |
|---|---|
case |
The case, as in ListCases without summary |
actions |
Its actions, as Action |
actionFacts |
"What we saw, when": per action, the facts its decision used (the target, the customer's counters, the stops, the identity, and for a merchant-hosted system the reads it made). Re-evaluating them at the recorded policy version gives the recorded decision |
timeline |
Append-only entries {id, at, kind, payload}: the decision, the execution, webhooks |
charge |
The case's charge: provider reference, amount, card brand and last four |
dispute, fraudWarning |
Set on a dispute or fraud-alert case |
ListRefunds
Every refund on your charges, newest first: Chargeless's own (REFUND_ORIGIN_SUPPORTROUTE, with
their action and case) and the ones made elsewhere, at the provider (REFUND_ORIGIN_PROVIDER) or
in your own system (REFUND_ORIGIN_MERCHANT_SYSTEM).
| Field | Type | Meaning |
|---|---|---|
from, to |
time | Required bounds on refundedAt, at most 400 days apart |
origins |
enum[] | Any of the three origins |
states |
enum[] | PAYMENT_REFUND_STATE_PENDING, _SUCCEEDED, _FAILED |
connectionIds |
string[] | At most 10 |
pageSize, pageToken |
1–100, default 50 |
{
"refunds": [
{
"id": "01a1b700-69cc-7080-b5ca-e541a95862d7",
"origin": "REFUND_ORIGIN_SUPPORTROUTE",
"provider": "stripe",
"providerRefundId": "re_c58FA64x0aOqOxLK0I9ZjlZb",
"amount": {"amountMinor": "4200", "currency": "USD"},
"state": "PAYMENT_REFUND_STATE_SUCCEEDED",
"refundedAt": "2026-11-07T17:13:40.044033Z",
"charge": {
"transactionId": "01a1b700-5ea6-736f-99fe-80e460b95079",
"providerRef": "ch_8w2pTukEA4IOftQJexOZbddA",
"amount": {"amountMinor": "4200", "currency": "USD"},
"cardBrand": "visa",
"cardLast4": "9772",
"customerId": "01a1b700-5ea6-707e-bb61-b0763860a280",
"customerProviderRef": "cus_aQYDQuFGnAjZ6rt25BbC8jvo"
},
"actionId": "01a1b700-65e6-7b4d-ae33-d46bf87e283f",
"caseId": "01a1b700-65e6-76d0-9a8b-1325c5b3c1f2",
"caseNumber": "5",
"actionStatus": "ACTION_STATUS_EXECUTED",
"source": "SOURCE_FRAUD_ALERT",
"decisionReason": "eligible",
"connectionId": "6e776da5-5c08-8dd5-aa9d-6c9736dfdbea",
"connectionLabel": "Stripe"
}
],
"totals": [
{"amount": {"amountMinor": "219885", "currency": "EUR"}, "refunds": "36"},
{"amount": {"amountMinor": "790509", "currency": "USD"}, "refunds": "160"}
],
"nextPageToken": "MTc5NDA3MTYy…"
}totals is what the range refunded per currency (succeeded and not reversed, with the origin and
connection filters), the same on every page. reversedAt marks a refund that was returned later.
ListDisputes
Disputes and inquiries, newest first, with their charge and what Chargeless did about each.
| Field | Type | Meaning |
|---|---|---|
from, to |
time | Required bounds on openedAt, at most 400 days apart |
statuses |
enum[] | DISPUTE_STATUS_NEEDS_RESPONSE, _UNDER_REVIEW, _WON, _LOST, _WARNING, _CLOSED |
reasons |
string[] | The provider's reason codes, compared exactly (fraudulent), at most 20 |
connectionIds |
string[] | At most 10 |
pageSize, pageToken |
1–100, default 50 |
Each entry carries dispute (provider, id, status, amount, reason, evidenceDueBy, openedAt,
closedAt, fundsWithdrawn), charge, the case, the latest action on it (actionType:
accept, submit evidence or a handoff, and actionStatus), evidenceState and
handledByProvider, which is true when a merchant of record (Paddle, FastSpring) runs the dispute
itself.
ListFraudWarnings
Early fraud warnings, newest first, with their charge and the refund decision the fraud-alert
workflow made for each (actionId is empty while it runs).
| Field | Type | Meaning |
|---|---|---|
from, to |
time | Required bounds on warnedAt, at most 400 days apart |
connectionIds |
string[] | At most 10 |
pageSize, pageToken |
1–100, default 50 |
Each entry carries provider, providerWarningId, fraudType, warnedAt, actionable (the
charge could still be acted on), charge, the case and the decision (actionStatus,
decisionReason), fromHistory (read from the provider's history and never handled) and
handledByProvider.
ListSubscriptions
Subscriptions across your payment connections, newest first by the chosen time.
| Field | Type | Meaning |
|---|---|---|
rangeField |
enum | SUBSCRIPTION_RANGE_FIELD_STARTED_AT or _CANCELED_AT. Required |
from, to |
time | Required bounds on that time, at most 400 days apart |
states |
enum[] | SUBSCRIPTION_STATE_ACTIVE, _TRIALING, _PAST_DUE, _CANCELED, _UNPAID, _INCOMPLETE, _PAUSED, _GRACE, _EXPIRED |
customer |
string | The provider's customer reference (cus_…, your customer_ref). The email form is for the panel only and answers permission_denied to an API key |
planRef |
string | The provider's plan or price reference, exactly |
currency |
string | The price's ISO 4217 currency |
billingInterval |
object | {unit, count}: only subscriptions renewing at exactly this interval |
connectionIds |
string[] | At most 10 |
pageSize, pageToken |
1–100, default 50 |
{
"subscriptions": [
{
"id": "01a1b700-732b-73e9-bdff-36fe4600be3c",
"provider": "stripe",
"providerSubscriptionId": "sub_zRD1wEx9r98zQYWAjTRFPDf2",
"state": "SUBSCRIPTION_STATE_ACTIVE",
"customer": {"id": "01a1b700-661c-74aa-a45d-8db455c36d2b", "providerRef": "cus_xXbSfStFNII7WdETMI6oe0l6"},
"planRef": "price_kGmpyErZM0VmStFVzlC6gTqs",
"price": {"amountMinor": "1500", "currency": "USD"},
"billingInterval": {"unit": "BILLING_INTERVAL_UNIT_WEEK", "count": 1},
"startedAt": "2026-11-06T17:13:35Z",
"currentPeriodEnd": "2026-11-13T17:13:35Z",
"cancelAtPeriodEnd": true,
"canceledAt": "2026-11-07T17:13:35Z",
"connectionId": "6e776da5-5c08-8dd5-aa9d-6c9736dfdbea",
"connectionLabel": "Stripe"
}
],
"nextPageToken": "MTc5Mzk4NTIx…"
}The plan's display name (planName, and the planName filter) is for the panel only.
ListProviderCalls
The calls Chargeless made to your own payment system (a merchant-hosted connection, see Connecting your own billing system), newest first, kept 90 days. No bodies and no personal data. A workspace without such a connection gets an empty page.
| Field | Type | Meaning |
|---|---|---|
from, to |
time | Optional bounds on at |
operations |
enum[] | PROVIDER_OPERATION_DESCRIBE, _FIND_CUSTOMERS, _GET_CUSTOMER, _LIST_TRANSACTIONS, _GET_TRANSACTION, _GET_SUBSCRIPTION, _REFUND, _CANCEL_SUBSCRIPTION, _GET_OPERATION, _LIST_PAYMENT_ATTEMPTS |
outcomes |
enum[] | PROVIDER_CALL_OUTCOME_OK, _NOT_FOUND, _REFUSED, _INVALID_REQUEST, _CREDENTIALS_REJECTED, _IDEMPOTENCY_CONFLICT, _IN_PROGRESS, _RATE_LIMITED, _SERVER_ERROR, _TIMEOUT, _TRANSPORT_ERROR, _CONTRACT_VIOLATION |
correlationId |
string | Only the calls made for this action, case, ticket, session or check |
connectionId |
string | One connection; empty: all |
pageSize, pageToken |
1–200, default 50 |
Each call carries at, operation, outcome, outcomeDetail (a refusal code, or what the answer
violated), httpStatus, latencyMs, attempt, requestId (the X-SR-Request-Id your logs
have), correlationId and its kind, idempotencyKey for mutations, and errorRetryable with
retryableMismatch when your error envelope's flag disagreed with its status.
GetProviderHealth
The health of a merchant-hosted connection, for your monitoring: the open provider alerts, the ones resolved in the last 7 days, and the last 24 hours of calls by outcome.
| Field | Type | Meaning |
|---|---|---|
connectionId |
string | The merchant-hosted connection; empty: your first connection |
The answer carries openAlerts and resolvedAlerts (kind: credentials rejected, contract
violation, unavailable, credentials expiring; figures with up to three request ids), and
supportPath, sync and check tallies by outcome over [windowStart, windowEnd). A Stripe
connection gets {}. The answer may be up to 30 seconds old and is shared by every caller, so
polling faster returns the same answer; resource_exhausted asks you to wait at least a second.