Skip to main content

Direct API Payments

The Direct API lets you collect card details on your own form and submit them to Flowlix for processing. This gives you full control over the payment experience but requires your systems to handle raw card data.
Handling raw card numbers requires PCI DSS compliance. If you prefer to avoid PCI scope, use the Hosted Payment Page instead.

How it works

  1. The customer enters their card details on your checkout page.
  2. Your server sends a POST /v1/payments request with the card details, amount, and currency.
  3. Flowlix processes the payment synchronously and returns the result.
  4. You show the customer the outcome (success or failure).

Create a payment

Send a POST request to /v1/payments with the payment details:
curl -X POST https://api.flowlix.eu/v1/payments \
  -H "Authorization: Bearer fl_test_sk_abc123def456" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234-payment" \
  -d '{
    "amount": 4999,
    "currency": "eur",
    "card": {
      "number": "4242424242424242",
      "exp_month": 12,
      "exp_year": 2027,
      "cvc": "314",
      "holder_name": "Jenny Rosen"
    },
    "customer": {
      "email": "jenny@example.com",
      "first_name": "Jenny",
      "last_name": "Rosen",
      "country": "DE",
      "phone": "+491234567890",
      "address": "100 Main St",
      "city": "Berlin",
      "zip": "10115"
    },
    "description": "Order #1234",
    "metadata": {
      "order_id": "ord_1234",
      "sku": "WIDGET-XL"
    }
  }'

Request parameters

ParameterRequiredDescription
amountYesAmount in minor units (e.g., 4999 = EUR 49.99).
currencyYesThree-letter ISO 4217 code (e.g., eur, usd, gbp).
card.numberYesThe full card number (digits only, no spaces).
card.exp_monthYesExpiration month (1-12).
card.exp_yearYesExpiration year (4 digits).
card.cvcYesSecurity code (3 digits for Visa/MC, 4 for Amex).
card.holder_nameYesCardholder name as printed on the card.
customer.emailYesCustomer email for receipts and fraud checks.
customer.first_nameYesCustomer first name.
customer.last_nameYesCustomer last name.
customer.countryYesCountry code (ISO 3166-1 alpha-2, e.g., DE).
customer.phoneYesPhone in international format (e.g., +491234567890).
customer.addressYesStreet address.
customer.cityYesCity.
customer.zipYesPostal code.
customer.stateNoState or province (required for US/CA).
descriptionNoFree-text description for your records.
metadataNoKey-value pairs for custom data (max 50 keys).

Successful response

{
  "id": "pay_1N3fKx2eZvKYlo2C0XjMr8pV",
  "object": "payment",
  "amount": 4999,
  "currency": "eur",
  "status": "succeeded",
  "description": "Order #1234",
  "card": {
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2027,
    "country": "US"
  },
  "customer": {
    "email": "jenny@example.com",
    "name": "Jenny Rosen"
  },
  "metadata": {
    "order_id": "ord_1234",
    "sku": "WIDGET-XL"
  },
  "decline_code": null,
  "decline_message": null,
  "redirect_url": null,
  "refunded_at": null,
  "succeeded_at": 1719792000,
  "failed_at": null,
  "created": 1719792000,
  "livemode": false
}

Handle declines

When a card is declined, you get a 402 response with a card_error:
{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "message": "Your card has insufficient funds.",
    "param": null,
    "decline_code": "insufficient_funds",
    "doc_url": "https://docs.flowlix.eu/declines/insufficient-funds",
    "request_id": "req_abc123def456"
  }
}
Use the decline_code to show an appropriate message to the customer:
Decline codeCustomer message
insufficient_funds”Your card has insufficient funds. Please try a different card.”
expired_card”Your card has expired. Please use a different card.”
do_not_honor”Your card was declined. Please contact your bank or try a different card.”
generic_decline”Your card was declined. Please try a different card.”
See the full list of decline codes.

Full integration example

Here is a complete example in Python:
import requests
import uuid

FLOWLIX_API_KEY = "fl_test_sk_abc123def456"

def create_payment(amount, currency, card, customer=None, description=None, metadata=None):
    """Create a Direct API payment."""
    idempotency_key = str(uuid.uuid4())

    payload = {
        "amount": amount,
        "currency": currency,
        "card": card,
    }
    if customer:
        payload["customer"] = customer
    if description:
        payload["description"] = description
    if metadata:
        payload["metadata"] = metadata

    response = requests.post(
        "https://api.flowlix.eu/v1/payments",
        headers={
            "Authorization": f"Bearer {FLOWLIX_API_KEY}",
            "Content-Type": "application/json",
            "Idempotency-Key": idempotency_key,
        },
        json=payload,
    )

    if response.status_code == 201:
        payment = response.json()
        print(f"Payment succeeded: {payment['id']}")
        return payment

    error = response.json()["error"]

    if error["type"] == "card_error":
        print(f"Card declined: {error['decline_code']} - {error['message']}")
    elif error["type"] == "invalid_request_error":
        print(f"Invalid request: {error['message']} (param: {error['param']})")
    else:
        print(f"Error: {error['type']} - {error['message']}")

    return None


# Usage
payment = create_payment(
    amount=4999,
    currency="eur",
    card={
        "number": "4242424242424242",
        "exp_month": 12,
        "exp_year": 2027,
        "cvc": "314",
    },
    customer={
        "email": "jenny@example.com",
        "first_name": "Jenny",
        "last_name": "Rosen",
        "country": "DE",
        "phone": "+491234567890",
        "address": "100 Main St",
        "city": "Berlin",
        "zip": "10115",
    },
    description="Order #1234",
    metadata={"order_id": "ord_1234"},
)

Best practices

  • Always include an Idempotency-Key to prevent duplicate charges on retries.
  • Validate card input client-side before submitting (check Luhn, expiry, CVC length) to reduce unnecessary API calls.
  • Store the payment.id in your database linked to the order for future retrieval and refunds.
  • Handle all error types — not just declines, but also validation errors and server errors.
  • Never log full card numbers — only log the last4 from the response.
  • Use metadata to link payments to your internal records (order IDs, customer IDs, etc.).