> For the complete documentation index, see [llms.txt](https://firstoken.gitbook.io/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://firstoken.gitbook.io/api-docs/api-reference/payments/payments-api-koin/card-validation.md).

# Card Validation

### Overview

The Card Validation endpoint allows you to perform card verification with fraud prevention in a single Firstoken API call.&#x20;

### What Happens Internally

```
Client Request → Firstoken API → [Tokenization + Antifraud + Validation] → Response
```

1. **Tokenization**: Card data is tokenized with KOIN
2. **Anti-fraud validation**
3. **Validation** with Zero Amount Transaction
4. **Response**: Transaction details returned

This process validates the card's authenticity while providing fraud risk assessment, without permanently charging the cardholder.

### Request Body

The request format is identical to the Authorization endpoint, but with `"type": "validation"` instead of `"type": "authorization"`.

***

#### Response Flow

The validation process can result in different outcomes:

```
┌─────────────────────────────────────────┐
│     Card Validation Request Sent        │
└───────────────┬─────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────┐
│      1. Tokenization with KOIN          │
└───────────────┬─────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────┐
│      2. Anti-Fraud Validation           │
└───────────────┬─────────────────────────┘
                │
        ┌───────┴────────┐
        │                │
        ▼                ▼
   ✅ Approved      ❌ Denied
        │                │
        │                └─────► status: "Denied"
        │                        risk_info.status: "Denied"
        │                        
        │
        ▼
┌─────────────────────────────────────────────────────┐
│      3. Validation with Zero Amount Transaction     │
└───────────────┬─────────────────────────────────────┘
                │
        ┌───────┴────────┐
        │                │
        ▼                ▼
   ✅ Validated      ❌ Declined
                         │
                         └─────► status: "Declined"
                                 risk_info.status: "Approved"
                                 error_info with status reason
                                 (e.g , ExpiredCard)

```

***

### Endpoint

**POST** `/v1/payments/`

***

### Field Specifications

#### transaction\_info (Required)

| Parameter        | Type   | Required | Description                                         |
| ---------------- | ------ | -------- | --------------------------------------------------- |
| `type`           | string | Yes      | Must be `"validation"` for card validation requests |
| `reference_code` | string | Yes      | Unique reference code for the transaction           |

#### card (Required)

| Parameter         | Type   | Required | Description                               |
| ----------------- | ------ | -------- | ----------------------------------------- |
| `number`          | string | Yes      | Card number (PAN)                         |
| `expiration_date` | string | Yes      | Card expiration date in format MM/YYYY    |
| `security_code`   | string | Yes      | Card CVV/CVC security code                |
| `holder`          | string | Yes      | Cardholder name as it appears on the card |

{% hint style="warning" %}

#### Note: Use a Firstoken token instead of card details; simply apply the token directly as follow

* Temporal token:&#x20;

  ```postman_json
  "{{08d40297-29d3-493e-b790-8e1085bc0a11: transaction}}"
  ```
* Permanent token (you can use all schema token types) :

  ```postman_json
  "{{419894dd-4211-409b-a31e-c76aba8da9f3: detokenize}}"
  ```

{% endhint %}

#### Order Info

| Parameter                     | Type   | Required | Description                                                 |
| ----------------------------- | ------ | -------- | ----------------------------------------------------------- |
| `amount_details`              | object | Yes      | Contains amount and currency information                    |
| `amount_details.total_amount` | number | Yes      | Total amount for validation (typically 1 or minimal amount) |
| `amount_details.currency`     | string | Yes      | Currency code (e.g., "BRL", "USD", "COP")                   |
| `amount_details.items_amount` | number | Yes      | Items amount                                                |
| `installments`                | number | Yes      | Number of installments (use 1 for validation)               |
| `descriptor`                  | string | Yes      | Description that appears on statement                       |

#### Cardholder Info

| Parameter              | Type   | Required | Description                              |
| ---------------------- | ------ | -------- | ---------------------------------------- |
| `first_name`           | string | Yes      | Cardholder first name                    |
| `last_name`            | string | Yes      | Cardholder last name                     |
| `document`             | object | Yes      | Document information                     |
| `document.type`        | string | Yes      | Document type (e.g., "CPF", "CC", "DNI") |
| `document.number`      | string | Yes      | Document number                          |
| `document.nationality` | string | Yes      | Nationality code (ISO 3166-1 alpha-2)    |
| `country`              | string | Yes      | Country code (ISO 3166-1 alpha-2)        |
| `address_1`            | string | Yes      | Primary address line                     |
| `city`                 | string | Yes      | City name                                |
| `state`                | string | Yes      | State or province code                   |
| `postal_code`          | string | Yes      | Postal/ZIP code                          |
| `email`                | string | Yes      | Email address                            |
| `phone_number`         | string | Yes      | Phone number                             |
| `phone_type`           | string | Yes      | Phone type (e.g., "Mobile", "Home")      |
| `phone_area_code`      | string | Yes      | Phone area code                          |

#### Bill To

Contains billing information with the same structure as cardholder\_info, plus:

| Parameter | Type   | Required | Description        |
| --------- | ------ | -------- | ------------------ |
| `id`      | string | Yes      | Billing ID         |
| `profile` | string | Yes      | Profile identifier |

#### Line Items

Array of purchase items (at least one required):

| Parameter       | Type   | Required | Description                 |
| --------------- | ------ | -------- | --------------------------- |
| `category`      | object | Yes      | Item category information   |
| `category.id`   | string | Yes      | Category ID                 |
| `category.name` | string | Yes      | Category name               |
| `id`            | string | Yes      | Item ID                     |
| `name`          | string | Yes      | Item name                   |
| `price`         | number | Yes      | Item unit price             |
| `quantity`      | number | Yes      | Quantity                    |
| `type`          | string | Yes      | Item type (e.g., "Generic") |

#### Device Info

<table><thead><tr><th width="286.38671875">Parameter</th><th>Type</th><th width="102.12890625">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>ip_address</code></td><td>string</td><td>Yes</td><td>Customer's IP address</td></tr><tr><td><code>fingerprint_session_id</code>*</td><td>string</td><td>Yes </td><td>A unique alpha-numerical ID generated for each user session on a website.</td></tr></tbody></table>

{% hint style="info" %}
`fingerprint_session_id` is required for all KOIN requests. See [KOIN - Device Fingerprint Session ID](/api-docs/guides/koin-device-fingerprint-session-id.md) for implementation by platform (Web, Android, iOS).
{% endhint %}

***

### Example Request

```bash
curl --location 'https://api.firstoken.co/v1/payments' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data-raw '{
  "transaction_info": {
    "type": "validation",
    "reference_code": "1753550023570"
  },
  "card": {
    "number": "4225289837059229",
    "expiration_date": "01/2030",
    "security_code": "123",
    "holder": "John Doe Jones"
  },
  "order_info": {
    "amount_details": {
      "total_amount": 0,
      "currency": "BRL",
      "items_amount": 1
    },
    "installments": 1,
    "descriptor": "Test Payment"
  },
  "cardholder_info": {
    "first_name": "John",
    "last_name": "Doe",
    "document": {
      "type": "CPF",
      "number": "54672396002",
      "nationality": "BR"
    },
    "country": "BR",
    "address_1": "123 Main St",
    "city": "Sao Paulo",
    "state": "SP",
    "postal_code": "04001-000",
    "email": "john@test.com",
    "phone_number": "4158880000",
    "phone_type": "Mobile",
    "phone_area_code": "22"
  },
  "bill_to": {
    "id": "1234567890",
    "first_name": "John",
    "last_name": "Doe",
    "document": {
      "type": "CPF",
      "number": "54672396002",
      "nationality": "BR"
    },
    "country": "BR",
    "address_1": "123 Main St",
    "city": "Sao Paulo",
    "state": "SP",
    "postal_code": "04001-000",
    "email": "test@cybs.com",
    "phone_number": "4158880000",
    "phone_type": "Mobile",
    "phone_area_code": "22",
    "profile": "1"
  },
  "line_items": [
    {
      "category": {
        "id": "1",
        "name": "Category 2"
      },
      "id": "1",
      "name": "Item ",
      "price": 0,
      "quantity": 1,
      "type": "Generic"
    }
  ],
  "device_info": {
    "ip_address": "127.0.0.1",
    "fingerprint_session_id": "cf01cee443d8529475f4841a3a78348c"
  }
}'
```

***

### Response Structure

#### Success Response - Card Validated

```json
{
  "status": "success",
  "message": "Validation successful",
  "data": {
    "transaction_info": {
      "type": "validation_response",
      "reference_code": "1753550023570",
      "transaction_id": "0001761666589459054367",
      "request_id": "6d488360-ce28-47c7-9722-6bebd1c5d8c9",
      "status": "Validated",
      "response_code": "777775",
      "created_at": "2025-10-28T15:49:57.331Z"
    },
    "order_info": {
      "amount_details": {
        "authorized_amount": 0,
        "currency": "BRL"
      }
    },
    "risk_info": {
      "status": "Approved",
      "date": "2025-10-28T15:50:03.000Z",
      "strategies": []
    }
  }
}
```

#### Declined Response - Authorization Failed

When the card passes anti-fraud validation but the authorization is declined (e.g., insufficient funds), no void is generated since authorization was not completed:

```json
{
    "status": "success",
    "message": "Validation failed",
    "data": {
        "transaction_info": {
            "type": "validation_response",
            "reference_code": "175355002357034",
            "transaction_id": "0001782257549284962310",
            "request_id": "4688c601-be82-4a0b-8f57-bbfb4fd7fb99",
            "status": "Declined",
            "created_at": "2025-10-28T15:49:57.331Z"
        },
        "order_info": {
            "amount_details": {
                "authorized_amount": 0,
                "currency": "BRL"
            }
        },
        "risk_info": {
            "status": "Denied",
            "date": "2025-10-28T15:49:57.331Z",
            "strategies": []
        },
        "error_info": {
            "reason": "Antifraud",
            "message": "Transaction failed"
        }
    }
}
```

> **Note:** When `transaction_info.status` is "Declined", there will be no `void_info` in the response because the authorization was not completed. An `error_info` object will be present instead with the reason for the decline.

#### Response Fields

**Transaction Info**

| Field            | Type   | Description                                    |
| ---------------- | ------ | ---------------------------------------------- |
| `type`           | string | Response type: "validation\_response"          |
| `reference_code` | string | Your original reference code                   |
| `transaction_id` | string | Unique transaction identifier assigned by KOIN |
| `request_id`     | string | Unique request identifier                      |
| `status`         | string | Validation status: "Validated" or "Declined"   |
| `response_code`  | string | Response code from the processor               |
| `created_at`     | string | Timestamp of transaction creation (ISO 8601)   |

**Order Info**

| Field                              | Type   | Description                |
| ---------------------------------- | ------ | -------------------------- |
| `amount_details.authorized_amount` | number | Amount that was authorized |
| `amount_details.currency`          | string | Currency code              |

**Risk Info**

Contains anti-fraud analysis results:

| Field        | Type   | Description                                    |
| ------------ | ------ | ---------------------------------------------- |
| `status`     | string | Risk assessment result: "Approved" or "Denied" |
| `date`       | string | Timestamp of risk analysis (ISO 8601)          |
| `strategies` | array  | Array of risk strategies applied (if any)      |

**Error Info**

Contains error details when authorization is declined (only present when `transaction_info.status` is "Declined"):

| Field     | Type   | Description                                                      |
| --------- | ------ | ---------------------------------------------------------------- |
| `reason`  | string | Reason code for the decline (e.g., "CardExpired", "InvalidCard") |
| `message` | string | Human-readable error message                                     |

***

### Response Status Values

#### Transaction Status

* **Validated**: Card validation successful, authorization completed and voided
* **Failed**: Card validation failed OR authorization was declined
  * When failed with `risk_info.status: "Approved"`: Validation failed (e.g., insufficient funds, card blocked)
  * When failed with `risk_info.status: "Denied"`: Anti-fraud checks failed

#### Risk Status

* **Approved**: Passed anti-fraud checks
* **Denied**: Failed anti-fraud checks

#### Error Reasons (in error\_info.reason)

Common decline reasons when validation fails:

| Reason              | Description                                         |
| ------------------- | --------------------------------------------------- |
| `CardExpired`       | Card expiration date has passed                     |
| `InvalidCard`       | Card number is invalid or card is not active        |
| `CardBlocked`       | Card has been blocked by the issuer                 |
| `ExceedsLimit`      | Transaction exceeds card limit                      |
| `IssuerUnavailable` | Card issuer system is temporarily unavailable       |
| `StolenCard`        | Card has been reported as stolen                    |
| `LostCard`          | Card has been reported as lost                      |
| `RestrictedCard`    | Card has restrictions that prevent this transaction |

***

### Error Handling

#### Error Response Example

```json
{
  "status": "fail",
  "message": "Invalid request",
  "data": {
    "description": "Must be a valid credit card number or token",
    "path": "card.number"
  }
}
```

#### Error Response Fields

| Field              | Type   | Description                       |
| ------------------ | ------ | --------------------------------- |
| `status`           | string | Always "fail" for error responses |
| `message`          | string | General error message             |
| `data.description` | string | Detailed description of the error |
| `data.path`        | string | Field path that caused the error  |

***

#### Testing

#### Test Scenarios

1. **Successful Validation**

* Use approved test card (4225289837059229)
* Verify `transaction_info.status: "Validated"`
* Verify `risk_info.status: "Approved"`
* Expected: Complete flow with void

2. **Fraud Decline**

* Use email: `john_autoreject@test.com`
* Verify `transaction_info.status: "Declined"`
* Verify `risk_info.status: "Denied"`
* Expected: Fails at fraud validation

3. **Invalid Card**

* Use invalid card number (e.g., 1234567890123456)
* Verify appropriate error response with `status: "fail"`
* Check `data.path: "card.number"`
* Expected: Request validation error

***
