---
title: "Purchase credits"
method: POST
path: "/credits/purchase"
tags: ["Credits", "Payments"]
---

# Purchase credits

`POST /credits/purchase`

## Request

- Base URL: `https://api.faireplace.com/api`
- URL: `https://api.faireplace.com/api/credits/purchase`
- Auth: HTTP bearer

Initiates credit purchase via Stripe Checkout Session.

## Purchase Process

```
┌─────────────────────────────────────────────────────────────┐
│  1. Client calls POST /credits/purchase                    │
│     with package_id, success_url, cancel_url               │
└─────────────────────────────────────────────────────────────┘
                      │
                      ▼
            ┌─────────────────────┐
            │ Verify package      │
            │ - Exists?           │
            │ - Active?           │
            │ - stripe_price_id?  │
            └─────────────────────┘
             │              │
    VALID    │              │      INVALID
             ▼              ▼
┌──────────────────┐  ┌──────────────────┐
│ Retrieve         │  │ Error 404/400    │
│ stripeCustomerId │  │                  │
│ from Better      │  │                  │
│ Auth             │  │                  │
└──────────────────┘  └──────────────────┘
             │
             ▼
┌──────────────────────────────┐
│ Create Stripe Checkout       │
│ Session with:                │
│ - customer_id                │
│ - price_id                   │
│ - metadata (tenant_id,       │
│   user_id, package_id)       │
│ - success_url                │
│ - cancel_url                 │
└──────────────────────────────┘
             │
             ▼
┌──────────────────────────────┐
│ Return checkout_url          │
│                              │
│ Client redirects to          │
│ Stripe Checkout              │
└──────────────────────────────┘
             │
             ▼
┌──────────────────────────────┐
│ User pays on Stripe          │
│                              │
│ Stripe sends webhook         │
│ checkout.session.completed   │
└──────────────────────────────┘
             │
             ▼
┌──────────────────────────────┐
│ Backend credits the credits  │
│ via webhook handler          │
└──────────────────────────────┘
```

## Prerequisites

- The user must have the **owner** role (no Stripe account for tenants)
- The user must have a `stripeCustomerId` in Better Auth
  - If absent, error 400: "You must have a Stripe account to purchase credits"
- The package must be active and have a `stripe_price_id` configured

## Validations

- ✅ Validation of `package_id` (valid UUID)
- ✅ URL validation (`success_url` and `cancel_url` must be valid URLs)
- ✅ Verification that the package exists and is active
- ✅ Verification that the package has a `stripe_price_id`
- ✅ Retrieval of `stripeCustomerId` from Better Auth
- ✅ Creation of Stripe Checkout Session with complete metadata

## Stripe Metadata

The Stripe Checkout Session includes the following metadata for webhook processing:

```json
{
  "tenant_id": "tenant_123",
  "user_id": "user_456",
  "package_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

This metadata allows the webhook to credit the right user
in the correct tenant after successful payment.

## Error Handling

- **400 Bad Request**: Invalid package, invalid URLs, or no Stripe account
- **401 Unauthorized**: Invalid or expired API key
- **403 Forbidden**: User is not an owner
- **404 Not Found**: Package not found or inactive
- **500 Internal Server Error**: Error creating the Checkout Session

## Usage Example

```bash
curl -X POST https://api.faireplace.com/v1/credits/purchase \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "package_id": "550e8400-e29b-41d4-a716-446655440000",
    "success_url": "https://app.faireplace.com/credits/success?session_id={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://app.faireplace.com/credits/cancel"
  }'
```

**Response**:
```json
{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3d4e5f6...",
  "package_id": "550e8400-e29b-41d4-a716-446655440000",
  "package_name": "Pack Starter",
  "credits_amount": 1000,
  "price_cents": 2990
}
```

The user must then be redirected to `checkout_url` to complete payment.

## Request body

- PurchaseCreditsRequest: Request to initiate a credit purchase. This request creates a Stripe Checkout Session and returns the payment URL. Payment is processed by Stripe, and credits are automatically credited via webhook when payment is confirmed.
  - `package_id` string, uuid, required: Identifier of the credit package to purchase. The package must be active and have a configured `stripe_price_id`.
  - `success_url` string, uri, required: Redirect URL after successful payment. The user will be redirected to this URL after completing the payment on the Stripe Checkout page. **Recommendations**: - Include a success parameter to identify the transaction - Example: `https://app.fairplace.com/credits/success?session_id={CHECKOUT_SESSION_ID}`
  - `cancel_url` string, uri, required: Redirect URL if the user cancels the payment. The user will be redirected to this URL if they close the Stripe Checkout page without completing the payment.

## Response `201`

Purchase session created successfully.

The user must be redirected to `checkout_url` to complete payment.
Credits will be automatically credited after successful payment via webhook.

- PurchaseCreditsResponse: Response after creating a credit purchase session. Contains the Stripe Checkout page URL where the user must be redirected to complete the payment.
  - `checkout_url` string, uri, required: Stripe Checkout page URL. **Required action**: Redirect the user to this URL to complete the payment. This URL is valid for a limited time (typically 24 hours). If the user does not complete the payment within this period, the session expires.
  - `package_id` string, uuid, required: Identifier of the selected package.
  - `package_name` string, required: Name of the selected package.
  - `credits_amount` integer, required: Number of credits that will be credited after successful payment (in cents).
  - `price_cents` integer, required: Package price in euro cents.

## Other responses

- `400`: Invalid request. **Possible causes**: - Invalid `package_id` (not a valid UUID) - Invalid `success_url` or `cancel_url` (not valid URLs) - User does not have a Stripe account (`stripeCustomerId` missing in Better Auth) - Package does not have a `stripe_price_id` configured
- `401`: Unauthorized
- `403`: Access forbidden. The user does not have the **owner** role required to purchase credits. Only owners can have a Stripe account and purchase credits.
- `404`: Package not found or inactive. The provided `package_id` does not exist, does not belong to the tenant, or is inactive (and `active_only=true` by default).
- `500`: Internal Server Error

---

[API](https://skmtc.dev/faireplace/apis/faireplace-property-management-api.md) · [All operations](https://skmtc.dev/faireplace/apis/faireplace-property-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/faireplace/apis/faireplace-property-management-api/revisions/69de39762919?raw)
