# GET /api/customers/{id}

Get customer by ID

Retrieves detailed information about a specific customer by their unique identifier.

**Operation ID:** `customersGet`

## Authorization

Requires: Customers.Customers, merchant scope.

Requires API key scope: `customers`

Required permissions:
- `Customers.Customers`

## Parameters

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| id | path | yes | string(uuid) |  |
| suppressNulls | query | no | boolean | If true, omit properties with null values. |

## Responses

### 200

OK

**Content type:** `application/json`

Schema: [`CustomerDto`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CustomerDto.md)

Properties:
- `extraProperties` (object): (nullable, read only)
- `id` (string(uuid))
- `creationTime` (string(date-time)): The date and time when this entity was created.
- `creatorId` (string(uuid)): The ID of the user who created this entity. (nullable)
- `lastModificationTime` (string(date-time)): The date and time when this entity was last modified. (nullable)
- `lastModifierId` (string(uuid)): The ID of the user who last modified this entity. (nullable)
- `isDeleted` (boolean): Indicates whether this entity has been deleted.
- `deleterId` (string(uuid)): The ID of the user who deleted this entity, if it is deleted. (nullable)
- `deletionTime` (string(date-time)): The date and time when this entity was deleted, if it is deleted. (nullable)
- `createdFromTemplateId` (string(uuid)): The template this record was created from, or `null` for one started blank. Set by the create-from-template path and by the add/edit page's Load Template action when the record is saved. (nullable)
- `name` (string): Gets or sets the name of the customer. Unique within the owning merchant, compared case-insensitively. (nullable)
- `displayName` (string): An optional human-readable label for the customer that is deliberately not unique. Null when the customer carries no separate label, in which case `name` is what merchants see wherever customers are listed. (nullable)
- `concurrencyStamp` (string): Gets or sets the concurrency stamp used for optimistic concurrency control. (nullable)
- `tenantId` (string(uuid)): Gets the tenant identifier for multi-tenancy isolation. (nullable, read only)
- `merchantId` (string(uuid)): Gets or sets the identifier of the merchant that owns this customer.
- `isActive` (boolean): Gets or sets a value indicating whether this customer is active.
- `legacyNumber` (integer(int64)): Gets or sets the customer's legacy numeric key, the integer identifier the v1 API resolves customers by. Server-owned: stamped at create and ignored on every inbound payload. Null on customers created before the field existed and not yet back-filled by the v1 data migration. (nullable)
- `merchantAssignedId` (string): An optional identifier the merchant assigns to the customer as a friendly reference, distinct from the system Guid `Id` and the server-owned `legacyNumber`. Surfaces the v1 API's `CustomerId` field. (nullable)
- `contactDetail` ([CustomerContactDetail](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CustomerContactDetail.md)): Gets or sets the customer's contact details (email, phone, address).
  Fields:
  - `primaryContactName` (string): (nullable, max length 100)
  - `emailAddress` (string): The contact's email address. Optional. A value that is not blank must be a valid email address. Conditional: When EmailAddress is not empty. Must be a valid email. (nullable)
  - `phone` (string): The contact's phone number. Optional. A value that is not empty must be a phone number: digits, with an optional leading `+` and country code, optional parentheses around the area code, and a space, hyphen or period between groups. Letters, including an extension such as `x123`, are rejected. (nullable)
  - `fax` (string): The contact's fax number. Optional. Follows the same rule as the phone number. (nullable)
  - `companyUrl` (string): The company's website address. Optional. A value sent without a scheme, such as `www.example.com`, is stored with `https://` added. A value that starts with `http://` or `https://` is stored exactly as sent. Any other scheme, and text that is not a website address, is rejected. (nullable)
  - `addresses` (array<[CustomerAddress](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CustomerAddress.md)>): The customer's addresses on file. Optional: omit the member, send an empty array, or send null to record a customer with no address on file. When one or more entries are supplied, exactly one must be flagged `isDefault`, and every entry must carry `address1`, `city`, `state` and `zip`. (nullable)
- `tags` (array<[EntityTag](https://devportal.signapay-sbx.winkpg.io/docs/api/models/EntityTag.md)>): Gets or sets the collection of tags used to categorize or label this customer. (nullable)
  Fields of each item:
  - `name` (string): (nullable, min length 0, max length 50)
  - `value` (string): (nullable, min length 0, max length 150)
- `storedPaymentMethods` (array<[CustomerStoredPaymentMethod](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CustomerStoredPaymentMethod.md)>): Gets or sets the customer's stored payment methods for recurring or future transactions. (nullable)
  Fields of each item:
  - `id` (string(uuid)): The stable identifier of this stored payment method. Stored credential consents are recorded against it, so a merchant-initiated charge on the backing token is authorized only while the method keeps this id.
  - `paymentMethodType` ([CustomerStoredPaymentMethodType](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CustomerStoredPaymentMethodType.md)): Specifies the type of a stored payment method associated with a customer.
    Values:
    - `Card`
    - `Check`
  - `paymentTokenId` (string(uuid)): (nullable)
  - `cardData` ([CardData](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CardData.md)): Required: When PaymentMethodType == Card.
  - `checkData` ([CheckData](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CheckData.md)): Required: When PaymentMethodType == Check.
  - `isDefault` (boolean)
  - `displayName` (string): (nullable, read only)
  - `isDeleted` (boolean): Indicates whether this entity has been deleted. (nullable)
  - `customName` (string): Optional user-defined name for this stored payment method (e.g., "Marriott Chase", "Bank of America Checkcard"). When set, takes priority over the computed display name. (nullable)
  - `initialSchemeTransactionId` (string): The initial (first-in-series) network / scheme transaction id captured from the processor's authorization response on the cardholder-initiated credential-on-file transaction that stored this credential. Lets any later transaction (CIT-COF or MIT, any card brand) resolve and echo the original network transaction id from just the stored token / payment method, without depending on a StoredCredentialConsent lookup. (nullable)
  - `lastSchemeTransactionId` (string): The most-recent-in-series network / scheme transaction id. At tokenization this equals `initialSchemeTransactionId`. Card-network rules (Visa 11.1.5, Mastercard 13.1.1) allow the most recent value in a series to be sent when the original is unavailable, so a later story can advance this on each subsequent authorization response without losing the first-in- series anchor. Nullable / non-sensitive as for `initialSchemeTransactionId`. (nullable)
  - `schemeTransactionIdBrand` (string): The card brand at the moment the network transaction id was captured (e.g. "Visa", "Mastercard", "Discover", "Amex"). Retained because a co-branded card can switch networks after storage; Mastercard's AltNetTranID handling requires the brand-at-capture to detect the case where a stored Visa id must be sent in a Mastercard field. Emitting AltNetTranID is out of scope here: this only preserves the data so the downstream echo work is not precluded. Nullable / non-sensitive. (nullable)
  - `schemeTransactionIdProcessor` (string): The processor the network transaction id originated from ("tsys" / "fiserv"). The two processors' response formats differ (TSYS a single 15-char Transaction Identifier; Fiserv per-brand, whose Visa value can exceed 15 chars including the trailing Validation Code), so the id is stored opaquely and tagged with its originating processor. Nullable / non-sensitive. (nullable)
  - `publicReference` (string): The externally-emitted, chargeable `PublicReference` of the backing `PaymentToken` (the opaque `pt_`-prefixed handle) captured when this stored method was minted. Persisted here so the tokenize-on-payment path can surface it on the transaction response / webhook without re-reading the token, and so a later charge can round-trip the merchant-facing handle. Distinct from `paymentTokenId`, which is the internal vault Guid (PAN-correlated, never emitted). May be `null` on a stored method created before this field existed, until a one-time platform backfill copies the backing token's handle onto it; the backfill only ever fills a null and never rewrites an assigned handle. Non-sensitive (an opaque random handle, not a PAN). (nullable)
  - `origin` ([StoredPaymentMethodOrigin](https://devportal.signapay-sbx.winkpg.io/docs/api/models/StoredPaymentMethodOrigin.md)): The surface / add-channel this stored payment method was captured through (manual admin, hosted payment page, virtual terminal, or API). Stamped once at creation and not changed afterward. (nullable)
    Values:
    - `Unknown`: The add-channel was not recorded. Assigned to methods that predate this field (materialized from a null value) and to any path that did not classify its origin.
    - `ManualAdmin`: Added by an operator through a Customer admin surface that mints the stored method on the customer document directly (the customer edit form's payment-methods dialog, and the create-customer / create-contract-from-transaction dialogs). Note: the contract add-payment dialog submits through the HTTP add endpoint and is therefore recorded as Api, since origin on that boundary is server-authoritative and cannot be caller-supplied.
    - `HostedPaymentPage`: Vaulted from a hosted payment page: either tokenize-on-payment or the direct ACH vault flow.
    - `VirtualTerminal`: Vaulted from a Virtual Terminal save-on-payment.
    - `Api`: Added through the HTTP add-stored-payment-method endpoint. Covers external API token registration and any in-process operator surface that submits through that same gated endpoint (e.g. the contract add-payment dialog): origin on this boundary is stamped server-side and is never caller-supplied.
  - `accountHolderType` ([CheckType](https://devportal.signapay-sbx.winkpg.io/docs/api/models/CheckType.md)): Whether a saved bank account belongs to a business or a person, as captured when the account was saved: `Business` or `Personal`. `null` when nothing captured it, and always `null` for a card. (nullable)
    Values:
    - `Personal`: Represents a personal check. This is typically issued by an individual from their personal account.
    - `Business`: Represents a business check. This is typically issued by a company or organization from their business account.
  - `readerTokenFingerprint` (string): Dedupe fingerprint for a reader-token-backed (PAN-less) stored method: a one-way SHA-256 digest of the processor reader token that this method's backing vault token carries. Set only for the reader-token shape (decrypt-and-forward processors such as MagTek DAF, where the gateway never sees the PAN); `null` for every PAN- or account-backed method. (nullable)
  - `checkFingerprint` (string): Dedupe fingerprint for a check / ACH stored method: a keyed, non-reversible HMAC-SHA-256 digest of the routing number combined with the account number's last four digits. Set only for the Check shape; `null` for card and reader-token methods. (nullable)
  - `achAuthorizationEvidence` ([AchAuthorizationEvidence](https://devportal.signapay-sbx.winkpg.io/docs/api/models/AchAuthorizationEvidence.md)): The ACH authorization the account holder gave when this bank account was saved: the channel, SEC code, kind of authorization text, its version, when it was accepted, the consumer IP for a hosted payment page authorization, and the id of the transaction that saved the account (which carries the full text).
- `contracts` (array<[Contract](https://devportal.signapay-sbx.winkpg.io/docs/api/models/Contract.md)>): Gets or sets the recurring billing contracts associated with this customer. (nullable)
  Fields of each item:
  - `id` (string(uuid))
  - `name` (string): (nullable)
  - `notes` (array<[EntityNote](https://devportal.signapay-sbx.winkpg.io/docs/api/models/EntityNote.md)>): (nullable)
  - `tags` (array<[EntityTag](https://devportal.signapay-sbx.winkpg.io/docs/api/models/EntityTag.md)>): (nullable)
  - `customerId` (string(uuid))
  - `legacyNumber` (integer(int64)): The contract's legacy numeric key, as used by the v1 API where contracts are identified by an integer rather than a Guid. Server-owned: stamped once at create from a tenant-scoped counter and never settable from an inbound payload. (nullable)
  - `customFields` (array<[ContractCustomField](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractCustomField.md)>): The merchant-defined custom field values carried on this contract, or `null` when it carries none. (nullable)
  - `perBillInvoice` ([ContractPerBillInvoice](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractPerBillInvoice.md))
  - `thresholds` ([ContractThresholds](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractThresholds.md))
  - `aggregates` ([ContractAggregates](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractAggregates.md))
  - `schedule` ([ContractSchedule](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractSchedule.md))
  - `payMethod` ([ContractPayMethod](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractPayMethod.md))
  - `emailNotifications` ([ContractEmailNotifications](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractEmailNotifications.md))
  - `isActive` (boolean)
  - `planId` (string(uuid)): The reusable plan this contract subscribes to for its price, or `null` when the contract carries its own inline amount. (nullable)
  - `trialEndDate` (string(date-time)): The calendar day the contract's trial period ends, in UTC, or `null` when the contract has no trial. Nothing is charged before this day: the first charge falls on it (or on the schedule's first occurrence after it) at the full contract amount. (nullable)
  - `priceChangedAt` (string(date-time)): When an operator last changed what this contract charges per cycle (the plan it subscribes to, or its inline amount), in UTC, or `null` when that has never happened since the field existed. Server owned: stamped by the contract update path, never settable from an inbound payload. (nullable)
  - `deactivationReason` ([ContractDeactivationReason](https://devportal.signapay-sbx.winkpg.io/docs/api/models/ContractDeactivationReason.md)): Describes why this contract was deactivated. Null when the contract is active or was never deactivated by the billing engine. Set automatically by the recurring billing manager and cleared when the contract is reactivated. (nullable)
    Values:
    - `Suspended`: Suspended by the billing engine after consecutive failures exceeded the maxFailCount threshold.
    - `MaxPaymentsReached`: Total execution count (successful + failed) reached maxNumPayments.
    - `MaxSuccessfulBillsReached`: Successful execution count reached maxSuccessfulBills.
    - `MaxAmountReached`: Cumulative amount billed reached maxAmount.
    - `ScheduleExhausted`: The recurrence schedule produced no further occurrences: either the until end date was reached or the recurrence count was exhausted.
    - `Manual`: Manually deactivated by an operator via the UI or API.
    - `CancelledByCardholder`: Cancelled by the cardholder themselves, from the link in an upcoming-charge reminder, with no operator involved.
    - `Paused`: Paused on purpose by an operator, to be resumed later: either by hand, or automatically by the daily billing run once the contract's expected resume date has passed. A pause is a voluntary stop, which is what separates it from Suspended (the billing engine gave up after consecutive failures) and from Manual (deactivated with no intention to resume). The pause itself is described by the contract's pause fields (reason, paused date, expected resume date), which are cleared when the contract resumes.
  - `pauseReason` (string): Why an operator paused this contract, or `null` when it is not paused. Server owned: written by the pause operation and cleared by every resume, never settable from an inbound payload. (nullable)
  - `pausedDate` (string(date-time)): When the contract was paused, in UTC, or `null` when it is not paused. Server owned, stamped by the pause operation and cleared on resume. (nullable)
  - `expectedResumeDate` (string(date-time)): The calendar day, in UTC, on which the daily billing run resumes this contract on its own, or `null` for a pause with no end date, which only an operator can resume. A calendar date compared as written, never timezone-converted, the same rule `trialEndDate` follows: the run that finds this day at or before today resumes the contract and projects its next charge from that moment. (nullable)
  - `convenienceFeeWaived` (boolean): Whether the cardholder enrolled in this contract on terms that disclosed no convenience fee, so no scheduled charge under it may carry one. `null` or `false` means the fee is decided at each charge from the merchant's live configuration, as it always has been. Server owned: stamped once when a Hosted Payment Page session carrying the `suppress_convenience_fee` directive creates the contract, never settable from an inbound payload. (nullable)
  - `dualPricingConsentedRate` (number(double)): The dual pricing card price rate, as a decimal fraction (`0.04` is 4 percent), that the cardholder's stored-credential consent stated when they enrolled in this contract, or `null` when it stated none. A scheduled charge of a card under this contract is charged the card price only when it (or `dualPricingConsentedAmount`) is set, and never uplifted by more than it states: an increase in the merchant's rate waits for a new consent, and a decrease applies at once. Server owned: stamped once when the contract is created, from the consent its card's recurring charges run under (a Hosted Payment Page enrollment, a back-office attestation or an API consent that recorded the card price), never settable from an inbound payload. (nullable)
  - `dualPricingConsentedAmount` (number(double)): The dual pricing card price uplift, as an amount in the contract currency (`2.50`), that the cardholder's stored-credential consent stated when they enrolled in this contract under a fixed-amount configuration, or `null` when it stated none. A consent records exactly one measure, the one matching the mode it was captured under, so at most one of this and `dualPricingConsentedRate` is set. A scheduled charge of a card under this contract is never uplifted by more than it: an increase in the merchant's fixed amount waits for a new consent, and a decrease applies at once. Server owned: stamped once when the contract is created, on the terms of `dualPricingConsentedRate`, never settable from an inbound payload. (nullable)
  - `upcomingChargeReminderSentForRun` (string(date-time)): The scheduled run, in UTC, that the pre-charge reminder was last sent for, or `null` when no reminder has been sent. Server owned: written only by the recurring billing run after the reminder email is queued, never settable from an inbound payload. (nullable)
  - `description` (string): (nullable)
  - `creationTime` (string(date-time)): The date and time when this entity was created. (nullable)
  - `creatorId` (string(uuid)): The ID of the user who created this entity. (nullable)
  - `lastModificationTime` (string(date-time)): The date and time when this entity was last modified. (nullable)
  - `lastModifierId` (string(uuid)): The ID of the user who last modified this entity. (nullable)
  - `isDeleted` (boolean): Indicates whether this entity has been deleted. (nullable)
  - `deleterId` (string(uuid)): The ID of the user who deleted this entity, if it is deleted. (nullable)
  - `deletionTime` (string(date-time)): The date and time when this entity was deleted, if it is deleted. (nullable)
  - `concurrencyStamp` (string): Optimistic-concurrency stamp for the contract, checked independently of the customer that holds it. A null stamp means "no concurrency check" until the contract is next updated, which stamps it going forward. (nullable)
- `entityVersion` (integer(int32)): Gets the entity version, incremented on each modification for optimistic concurrency. (read only)
- `notes` (array<[EntityNote](https://devportal.signapay-sbx.winkpg.io/docs/api/models/EntityNote.md)>): Gets or sets operational notes attached to the customer. (nullable)
  Fields of each item:
  - `id` (string(uuid))
  - `enteredBy` (string(uuid))
  - `timestamp` (string(date-time))
  - `enteredByName` (string): Denormalized display name of the user who created the note. Populated at creation time from `ICurrentUser` to avoid N+1 lookups when rendering. (nullable)
  - `content` (string): (nullable, min length 0, max length 1000)

_Example: Customer with a stored card_

An active customer with contact details, a mailing address and one stored Visa card set as the default. Charge the stored card by its publicReference.

```json
{
  "id": "2a7c9e14-6b3d-4f58-9a0c-1d2e3f4a5b01",
  "name": "Jordan Lee",
  "concurrencyStamp": "9a5c3e7f1b2d4c6e8f0a1b2c3d4e5f6a",
  "merchantId": "00000000-0000-0000-0000-000000000001",
  "isActive": true,
  "merchantAssignedId": "CUST-1001",
  "contactDetail": {
    "addresses": [
      {
        "id": "2a7c9e14-6b3d-4f58-9a0c-1d2e3f4a5b03",
        "address1": "200 Elm St",
        "address2": "",
        "city": "Tempe",
        "state": "AZ",
        "province": "",
        "zip": "85281",
        "postalCode": "",
        "country": "USA",
        "isMailing": true,
        "isDefault": true
      }
    ],
    "primaryContactName": "Jordan Lee",
    "emailAddress": "jordan.lee@example.com",
    "phone": "555-555-0123",
    "fax": "",
    "companyUrl": ""
  },
  "tags": [
    {
      "name": "wholesale"
    }
  ],
  "storedPaymentMethods": [
    {
      "id": "2a7c9e14-6b3d-4f58-9a0c-1d2e3f4a5b02",
      "paymentMethodType": "Card",
      "paymentTokenId": "00000000-0000-0000-0000-00000000a001",
      "cardData": {
        "cardNumber": "************1111",
        "last4CardNumber": 1111,
        "expirationMonth": 12,
        "expirationYear": 2030,
        "binData": {
          "bin": 411111,
          "issuedEntity": "Personal",
          "brand": "Visa",
          "type": "Credit",
          "panLength": 16,
          "fundingSource": "Credit"
        },
        "nameOnCard": "Jordan Lee",
        "entryMode": "Unknown",
        "cvPresence": "None"
      },
      "isDefault": true,
      "displayName": "Visa Credit ending in 1111",
      "isDeleted": false,
      "publicReference": "pt_SampleRefDocsExampleOnly",
      "origin": "Api"
    }
  ],
  "contracts": [ ],
  "entityVersion": 0,
  "notes": [ ],
  "isDeleted": false,
  "lastModificationTime": "2026-01-12T16:05:00Z",
  "creationTime": "2025-09-08T19:20:00Z",
  "extraProperties": { }
}
```

### 429

The request was refused because a rate limit was exceeded, or because something a later retry can clear stopped it. A rate limit refusal carries an `application/problem+json` body: wait at least the interval `Retry-After` names before retrying, then back off. Limits are tuned per deployment, so read the allowance from the response headers rather than assuming a fixed ceiling. Any other refusal carries the standard error envelope as `application/json`, and its `error.code` names the cause.

**Content type:** `application/problem+json`

Schema: [`RateLimitProblemDetails`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RateLimitProblemDetails.md)

Properties:
- `type` (string) required: The problem type identifier. Always the same value: the failure is the status code itself, so there is no sub-type for a caller to branch on. (nullable)
- `title` (string) required: A short, human-readable summary of the problem type. (nullable)
- `status` (integer(int32)) required: The HTTP status code, repeated in the body as the problem-details format defines.
- `detail` (string) required: A human-readable explanation of this occurrence of the problem. (nullable)
- `retryAfterSeconds` (integer(int32)) required: How long to wait before retrying, in whole seconds, carrying the same figure as the `Retry-After` header. Always at least one: a value of zero would invite an immediate retry that is certain to be rejected again.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

### 400

Bad request. The request was malformed or failed validation. When validation rejected it, `error.validationErrors` names each member at fault and why.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

_Example: Request failed validation_

One or more fields were rejected before any rule ran. error.code is null on the envelope and the causes are in validationErrors, one entry per failure. An entry carries code when the validator declared a publishable one, and memberPaths when the full path says more than members does (an element inside a collection, for example). Both keys are omitted when they would add nothing.

```json
{
  "error": {
    "code": null,
    "message": "Your request is not valid!",
    "details": "The following errors were detected during validation.",
    "data": {
      "correlationId": "0123456789abcdef0123456789abcdef"
    },
    "validationErrors": [
      {
        "message": "'Amount' must be greater than 0.",
        "members": [
          "amount"
        ]
      },
      {
        "message": "The card has expired.",
        "members": [
          "expirationDate"
        ],
        "code": "CardData:CardExpired"
      },
      {
        "message": "'Value' must not be empty.",
        "members": [
          "value"
        ],
        "memberPaths": [
          "customFields[2].value"
        ]
      }
    ]
  }
}
```

### 401

Unauthorized. The request carried no API key or signed-in session, or the key it carried is invalid, revoked or expired.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

### 403

Forbidden. The API key lacks the scope this operation needs, the signed-in user lacks the permission, or a business rule refused the request. The error code in the body names the reason.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

### 404

Not found. The resource the request names does not exist or is not visible to the caller: a resource that belongs to a merchant or tenant outside the caller's scope is reported as not found.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

_Example: Resource not found_

The id in the path names no record the caller can see. Most resources answer with a null error.code and a message naming the resource type and the id; a resource that publishes a not-found code in the catalog above carries it in error.code instead, so check for one before matching on the message.

```json
{
  "error": {
    "code": null,
    "message": "There is no entity Transaction with id = 7d1f3a52-0c4e-4b8a-9e21-5a6b7c8d9e00!",
    "details": null,
    "data": {
      "correlationId": "0123456789abcdef0123456789abcdef"
    },
    "validationErrors": null
  }
}
```

### 500

Internal server error. The platform failed unexpectedly while handling the request. If it keeps failing, contact support and quote the correlation id, which the response carries in the `X-Correlation-Id` header and as `error.data.correlationId`.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

### 501

Not implemented. The operation is declared but has no implementation on this deployment, so no request to it can succeed. Retrying does not help.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

### default

The request failed. The body carries the standard error envelope: a machine-readable `error.code`, a human-readable `error.message`, and `error.validationErrors` when the failure was a validation rejection. The [error reference](https://devportal.signapay-sbx.winkpg.io/docs/errors.md) lists the values `error.code` can take.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))

_Example: Business rule refused the request_

The request was understood and rejected by a platform rule. This is the shape that carries a code from the catalog above, so branch on error.code rather than on the message text. The HTTP status is the one listed against the code.

```json
{
  "error": {
    "code": "Transactions:OperationNotAllowed",
    "message": "The operation 'Void' is not allowed on a Sale transaction with result 'Approved' and settlement state 'Settled'.",
    "details": null,
    "data": {
      "correlationId": "0123456789abcdef0123456789abcdef"
    },
    "validationErrors": null
  }
}
```

### Errors

These statuses answer with the error envelope outlined below.

- `400`: Bad request. The request was malformed or failed validation. When validation rejected it, `error.validationErrors` names each member at fault and why.
- `401`: Unauthorized. The request carried no API key or signed-in session, or the key it carried is invalid, revoked or expired.
- `403`: Forbidden. The API key lacks the scope this operation needs, the signed-in user lacks the permission, or a business rule refused the request. The error code in the body names the reason.
- `404`: Not found. The resource the request names does not exist or is not visible to the caller: a resource that belongs to a merchant or tenant outside the caller's scope is reported as not found.
- `500`: Internal server error. The platform failed unexpectedly while handling the request. If it keeps failing, contact support and quote the correlation id, which the response carries in the `X-Correlation-Id` header and as `error.data.correlationId`.
- `501`: Not implemented. The operation is declared but has no implementation on this deployment, so no request to it can succeed. Retrying does not help.
- `default`: The request failed. The body carries the standard error envelope: a machine-readable `error.code`, a human-readable `error.message`, and `error.validationErrors` when the failure was a validation rejection. The [error reference](https://devportal.signapay-sbx.winkpg.io/docs/errors.md) lists the values `error.code` can take.

**Content type:** `application/json`

Schema: [`RemoteServiceErrorResponse`](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorResponse.md)

Properties:
- `error` ([RemoteServiceErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceErrorInfo.md))
  Fields:
  - `code` (string): (nullable)
  - `message` (string): (nullable)
  - `details` (string): (nullable)
  - `data` (object): (nullable)
  - `validationErrors` (array<[RemoteServiceValidationErrorInfo](https://devportal.signapay-sbx.winkpg.io/docs/api/models/RemoteServiceValidationErrorInfo.md)>): (nullable)

_Example: Request failed validation_

One or more fields were rejected before any rule ran. error.code is null on the envelope and the causes are in validationErrors, one entry per failure. An entry carries code when the validator declared a publishable one, and memberPaths when the full path says more than members does (an element inside a collection, for example). Both keys are omitted when they would add nothing.

```json
{
  "error": {
    "code": null,
    "message": "Your request is not valid!",
    "details": "The following errors were detected during validation.",
    "data": {
      "correlationId": "0123456789abcdef0123456789abcdef"
    },
    "validationErrors": [
      {
        "message": "'Amount' must be greater than 0.",
        "members": [
          "amount"
        ]
      },
      {
        "message": "The card has expired.",
        "members": [
          "expirationDate"
        ],
        "code": "CardData:CardExpired"
      },
      {
        "message": "'Value' must not be empty.",
        "members": [
          "value"
        ],
        "memberPaths": [
          "customFields[2].value"
        ]
      }
    ]
  }
}
```

## Authentication

- `ApiKey`

## Example request

Every block below sends the same request. Replace {{BASE_URL}} with the address of the API you are calling and {{API_KEY}} with your own key.

### cURL

```bash
curl -X GET "{{BASE_URL}}/api/customers/{id}" \
  -H "api-key: {{API_KEY}}"
```

### PowerShell

```powershell
$headers = @{
    'api-key' = '{{API_KEY}}'
}

$response = Invoke-RestMethod -Method GET -Uri '{{BASE_URL}}/api/customers/{id}' `
    -Headers $headers
```

### TypeScript (SDK)

```bash
npm install @winkpg/winkpg-api
```

```typescript
import { Configuration, CustomersApi } from '@winkpg/winkpg-api';

const api = new CustomersApi(new Configuration({
  basePath: '{{BASE_URL}}',
  apiKey: '{{API_KEY}}',
}));

const { data } = await api.customersGet("3fa85f64-5717-4562-b3fc-2c963f66afa6");
```

### TypeScript (raw HTTP)

```typescript
const response = await fetch('{{BASE_URL}}/api/customers/{id}', {
  method: 'GET',
  headers: {
    "api-key": "{{API_KEY}}",
  },
});

const data = await response.json();
```

### C# (SDK)

```bash
dotnet add package WinkPg.Api.Client
```

```csharp
using WinkPg.Api.Client.Api;
using WinkPg.Api.Client.Client;

var config = new Configuration { BasePath = "{{BASE_URL}}" };
config.AddApiKey("api-key", "{{API_KEY}}");

var api = new CustomersApi(config);
var result = await api.CustomersGetAsync(Guid.Parse("3fa85f64-5717-4562-b3fc-2c963f66afa6"));
```

### C# (raw HTTP)

```csharp
using var http = new HttpClient { BaseAddress = new Uri("{{BASE_URL}}") };

var request = new HttpRequestMessage(new HttpMethod("GET"), "/api/customers/{id}");
request.Headers.Add("api-key", "{{API_KEY}}");

var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
```

### Python (SDK)

```bash
pip install winkpg-api
```

```python
import winkpg_api

configuration = winkpg_api.Configuration(host="{{BASE_URL}}")
configuration.api_key["ApiKey"] = "{{API_KEY}}"

with winkpg_api.ApiClient(configuration) as client:
    api = winkpg_api.CustomersApi(client)
    result = api.customers_get("3fa85f64-5717-4562-b3fc-2c963f66afa6")
```

### Python (raw HTTP)

```bash
pip install requests
```

```python
import requests

headers = {
    "api-key": "{{API_KEY}}",
}

response = requests.request(
    "GET",
    "{{BASE_URL}}/api/customers/{id}",
    headers=headers,
)
response.raise_for_status()
data = response.json()
```

## See also

- [All documentation](https://devportal.signapay-sbx.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
