> ## Documentation Index
> Fetch the complete documentation index at: https://docs.credibill.tech/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> Recurring billing relationships

A **Subscription** connects a [Customer](/docs/concepts/customers) to a [Plan](/docs/concepts/plans). It governs the generation of invoices and the automatic collection of payments on a recurring schedule.

## Subscription Lifecycle

Subscriptions move through several states:

<Steps>
  <Step title="Trialing">
    The subscription is in a free trial period. No payment has been collected
    yet.
  </Step>

  <Step title="Active">
    Payment has been successfully collected, and the customer has access to the
    service.
  </Step>

  <Step title="Past Due">
    Payment failed (e.g., expired card). Credibill will retry according to your
    retry settings. The customer may still have access.
  </Step>

  <Step title="Unpaid">
    Retries have been exhausted. Access should usually be revoked. This only
    applies to card payments.
  </Step>

  <Step title="Canceled">
    The subscription has been terminated. No further invoices will be generated.
  </Step>
</Steps>

## Proration

When a customer changes plans (upgrades or downgrades) in the middle of a billing cycle, Credibill automatically calculates [Proration](https://www.zoho.com/billing/academy/billing-basics/prorated-billing.html).

* **Upgrade**: Charged immediately for the difference.
* **Downgrade**: Nothing happens.

Proration cannot be disabled.

## How to subscribe a customer

Subscribe a customer to a plan, which creates a recurring billing relationship and starts generating invoices on the specified schedule.

* Endpoint: `https://giant-goldfish-922.convex.site/api/v1/subscriptions/create`
* Method: `POST`
* Authentication: Authorization: `Bearer <CREDIBILL_API_KEY>`

### Request Body Schema

| Field      | Type   | Required | Description                                                                      |
| ---------- | ------ | -------- | -------------------------------------------------------------------------------- |
| customerId | string | Yes      | The unique ID of the customer to subscribe.                                      |
| planId     | string | Yes      | The unique ID of the plan to subscribe the customer to.                          |
| startDate  | string | No       | ISO 8601 date string for when the subscription should start (defaults to today). |

### Examples

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    // src/hooks/useSubscribeCustomer.ts

    import { useMutation, UseMutationOptions } from "@tanstack/react-query";

    export type SubscriptionData = {
      customerId: string,
      planId: string,
      startDate?: string,
    };

    export type SubscribeCustomerResponse = {
      success: boolean,
      subscriptionId: string,
      status: string,
    };

    const useApiKey = () => {
    // !! REPLACE THIS PLACEHOLDER !!
    const apiKey = 'YOUR_CREDIBILL_API_KEY';
    return apiKey;
    };

    const CREDIBILL_URL = 'https://giant-goldfish-922.convex.site';
    const ENDPOINT = '/api/v1/subscriptions/create';

    type UseSubscribeCustomerOptions = Omit<UseMutationOptions<
    SubscribeCustomerResponse,
    Error,
    SubscriptionData

    > , 'mutationFn'>;

    /\*\*

    - Custom React Query hook for subscribing a customer to a plan
    - @param options - Optional configuration options for useMutation
    - @returns The mutation object provided by TanStack Query
      \*/
      export const useSubscribeCustomer = (
      options?: UseSubscribeCustomerOptions
      ) => {
      const apiKey = useApiKey();

    return useMutation<SubscribeCustomerResponse, Error, SubscriptionData>({
    mutationFn: async (data: SubscriptionData) => {
    if (!apiKey) {
    throw new Error("API Key is required for authentication.");
    }

          const response = await fetch(`${CREDIBILL_URL}${ENDPOINT}`, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': `Bearer ${apiKey}`,
            },
            body: JSON.stringify(data),
          });

          if (!response.ok) {
            const errorBody = await response.json();
            throw new Error(errorBody.error || `HTTP error! Status: ${response.status}`);
          }

          return response.json();
        },
        ...options,

    });
    };

    ```

    #### Usage in a Component

    ```typescript theme={null}
    import React, { useState } from 'react';
    import { useSubscribeCustomer } from './hooks/useSubscribeCustomer';

    export const SubscribeCustomerForm = () => {
      const [customerId, setCustomerId] = useState('');
      const [planId, setPlanId] = useState('');
      const [startDate, setStartDate] = useState('');

      const { mutate, isPending, isError, isSuccess, data, error, reset } = useSubscribeCustomer({
        onSuccess: (responseData) => {
          console.log(`Success! Subscription ID: ${responseData.subscriptionId}`);
        },
      });

      const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        if (isPending) return;

        reset();

        mutate({
          customerId: customerId.trim(),
          planId: planId.trim(),
          startDate: startDate.trim() || undefined,
        });
      };

      return (
        <div className="p-6 max-w-lg mx-auto bg-white rounded-xl shadow-lg space-y-4">
          <h2 className="text-2xl font-bold text-gray-800">Subscribe Customer to Plan</h2>
          <form onSubmit={handleSubmit} className="space-y-4">
            <div>
              <label htmlFor="customerId" className="block text-sm font-medium text-gray-700">
                Customer ID (Required)
              </label>
              <input
                id="customerId"
                type="text"
                value={customerId}
                onChange={(e) => setCustomerId(e.target.value)}
                required
                disabled={isPending}
                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
              />
            </div>

            <div>
              <label htmlFor="planId" className="block text-sm font-medium text-gray-700">
                Plan ID (Required)
              </label>
              <input
                id="planId"
                type="text"
                value={planId}
                onChange={(e) => setPlanId(e.target.value)}
                required
                disabled={isPending}
                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
              />
            </div>

            <div>
              <label htmlFor="startDate" className="block text-sm font-medium text-gray-700">
                Start Date (Optional - ISO 8601)
              </label>
              <input
                id="startDate"
                type="text"
                placeholder="2024-01-15"
                value={startDate}
                onChange={(e) => setStartDate(e.target.value)}
                disabled={isPending}
                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
              />
            </div>

            <button
              type="submit"
              disabled={isPending}
              className="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-150 ease-in-out disabled:opacity-50"
            >
              {isPending ? 'Processing...' : 'Subscribe Customer'}
            </button>
          </form>

          {isSuccess && (
            <div className="mt-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded-lg">
              Success! Subscription ID: <span className="font-mono">{data?.subscriptionId}</span>
            </div>
          )}
          {isError && (
            <div className="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
              Error: {error?.message}
            </div>
          )}
        </div>
      );
    };
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST 'https://giant-goldfish-922.convex.site/api/v1/subscriptions/create' \
      -H 'Content-Type: application/json' \
      -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY' \
      -d '{
        "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
        "planId": "k89cq3gkzhiys60bzuf53bh3hte8zx2d",
        "startDate": "2024-01-15"
      }'
    ```
  </Tab>
</Tabs>

### Response

When you subscribe a customer to a plan, the API responds with a JSON object containing the subscription details.

```json theme={null}
{
  "success": true,
  "subscriptionId": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
  "status": "active",
  "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
  "planId": "k89cq3gkzhiys60bzuf53bh3hte8zx2d",
  "startDate": "2024-01-15",
  "nextBillingDate": "2024-02-15"
}
```

## Getting Subscriptions

Retrieve subscription information by fetching a specific subscription or listing subscriptions with optional filters.

* Endpoint: `https://giant-goldfish-922.convex.site/api/subscriptions`
* Method: `GET`
* Authentication: Authorization: `Bearer <CREDIBILL_API_KEY>`

### Query Parameters

| Parameter      | Type   | Required | Description                                                                    |
| -------------- | ------ | -------- | ------------------------------------------------------------------------------ |
| subscriptionId | string | No       | The unique ID of the subscription. If provided, fetches a single subscription. |
| customerId     | string | No       | Filter subscriptions by customer ID.                                           |
| planId         | string | No       | Filter subscriptions by plan ID.                                               |
| status         | string | No       | Filter subscriptions by status (e.g., "active", "canceled", "past\_due").      |

### Examples

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    // src/hooks/useGetSubscriptions.ts

    import { useQuery, UseQueryOptions } from "@tanstack/react-query";

    export type Subscription = {
      _id: string,
      customerId: string,
      planId: string,
      status: "trialing" | "active" | "past_due" | "unpaid" | "canceled",
      startDate: string,
      nextBillingDate: string,
      [key: string]: any,
    };

    export type GetSubscriptionsResponse = {
      subscription?: Subscription,
      subscriptions?: Subscription[],
    };

    export type GetSubscriptionsFilters = {
      subscriptionId?: string,
      customerId?: string,
      planId?: string,
      status?: string,
    };

    const useApiKey = () => {
    // !! REPLACE THIS PLACEHOLDER !!
    const apiKey = 'YOUR_CREDIBILL_API_KEY';
    return apiKey;
    };

    const CREDIBILL_URL = 'https://giant-goldfish-922.convex.site';
    const ENDPOINT = '/api/subscriptions';

    type UseGetSubscriptionsOptions = Omit<UseQueryOptions<
    GetSubscriptionsResponse,
    Error

    > , 'queryFn' | 'queryKey'>;

    /\*\*

    - Custom React Query hook for fetching subscription(s)
    - @param filters - Optional filters (subscriptionId, customerId, planId, status)
    - @param options - Optional configuration options for useQuery
    - @returns The query object provided by TanStack Query
      \*/
      export const useGetSubscriptions = (
      filters?: GetSubscriptionsFilters,
      options?: UseGetSubscriptionsOptions
      ) => {
      const apiKey = useApiKey();

    return useQuery<GetSubscriptionsResponse, Error>({
    queryKey: ['subscriptions', filters],
    queryFn: async () => {
    if (!apiKey) {
    throw new Error("API Key is required for authentication.");
    }

          const url = new URL(`${CREDIBILL_URL}${ENDPOINT}`);
          if (filters?.subscriptionId) {
            url.searchParams.append('subscriptionId', filters.subscriptionId);
          }
          if (filters?.customerId) {
            url.searchParams.append('customerId', filters.customerId);
          }
          if (filters?.planId) {
            url.searchParams.append('planId', filters.planId);
          }
          if (filters?.status) {
            url.searchParams.append('status', filters.status);
          }

          const response = await fetch(url.toString(), {
            method: 'GET',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': `Bearer ${apiKey}`,
            },
          });

          if (!response.ok) {
            const errorBody = await response.json();
            throw new Error(errorBody.error || `HTTP error! Status: ${response.status}`);
          }

          return response.json();
        },
        ...options,

    });
    };

    ```

    #### Usage in a Component

    ```typescript theme={null}
    import React, { useState } from 'react';
    import { useGetSubscriptions } from './hooks/useGetSubscriptions';

    export const SubscriptionsViewer = () => {
      const [customerId, setCustomerId] = useState('');
      const [status, setStatus] = useState('');

      const { data, isLoading, isError, error } = useGetSubscriptions({
        customerId: customerId || undefined,
        status: status || undefined,
      });

      return (
        <div className="p-6 max-w-2xl mx-auto bg-white rounded-xl shadow-lg space-y-4">
          <h2 className="text-2xl font-bold text-gray-800">View Subscriptions</h2>

          <div className="space-y-3">
            <div>
              <label htmlFor="customerId" className="block text-sm font-medium text-gray-700">
                Customer ID (Optional)
              </label>
              <input
                id="customerId"
                type="text"
                value={customerId}
                onChange={(e) => setCustomerId(e.target.value)}
                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
              />
            </div>

            <div>
              <label htmlFor="status" className="block text-sm font-medium text-gray-700">
                Status (Optional)
              </label>
              <select
                id="status"
                value={status}
                onChange={(e) => setStatus(e.target.value)}
                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
              >
                <option value="">All Statuses</option>
                <option value="active">Active</option>
                <option value="trialing">Trialing</option>
                <option value="past_due">Past Due</option>
                <option value="unpaid">Unpaid</option>
                <option value="canceled">Canceled</option>
              </select>
            </div>
          </div>

          {isLoading && <p className="text-gray-600">Loading subscriptions...</p>}
          {isError && (
            <div className="p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
              Error: {error?.message}
            </div>
          )}

          {data?.subscriptions && data.subscriptions.length > 0 && (
            <div className="mt-6">
              <h3 className="text-lg font-semibold text-gray-700 mb-4">Subscriptions</h3>
              <div className="space-y-3">
                {data.subscriptions.map((sub) => (
                  <div key={sub._id} className="p-4 border border-gray-200 rounded-lg">
                    <p className="font-mono text-sm text-gray-600">ID: {sub._id}</p>
                    <p className="text-sm text-gray-700">Status: <span className="font-semibold">{sub.status}</span></p>
                    <p className="text-sm text-gray-700">Next Billing: {new Date(sub.nextBillingDate).toLocaleDateString()}</p>
                  </div>
                ))}
              </div>
            </div>
          )}

          {data?.subscription && (
            <div className="mt-6 p-4 border border-gray-200 rounded-lg bg-gray-50">
              <h3 className="text-lg font-semibold text-gray-700 mb-3">Subscription Details</h3>
              <p className="font-mono text-sm text-gray-600">ID: {data.subscription._id}</p>
              <p className="text-sm text-gray-700">Status: <span className="font-semibold">{data.subscription.status}</span></p>
              <p className="text-sm text-gray-700">Next Billing: {new Date(data.subscription.nextBillingDate).toLocaleDateString()}</p>
            </div>
          )}
        </div>
      );
    };
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # Get a single subscription by ID
    curl -X GET 'https://giant-goldfish-922.convex.site/api/subscriptions?subscriptionId=m90dro4hlizjt71cvwg64cj4juf9ze3e' \
      -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY'

    # List all subscriptions for a customer

    curl -X GET 'https://giant-goldfish-922.convex.site/api/subscriptions?customerId=j97bp2fjzhbx59ayye52ag2gsd7ydw1c' \
     -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY'

    # List active subscriptions for a plan

    curl -X GET 'https://giant-goldfish-922.convex.site/api/subscriptions?planId=k89cq3gkzhiys60bzuf53bh3hte8zx2d&status=active' \
     -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY'

    # List all subscriptions

    curl -X GET 'https://giant-goldfish-922.convex.site/api/subscriptions' \
     -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY'

    ```
  </Tab>
</Tabs>

### Response Examples

**Get Single Subscription:**

```json theme={null}
{
  "subscription": {
    "_id": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
    "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
    "planId": "k89cq3gkzhiys60bzuf53bh3hte8zx2d",
    "status": "active",
    "startDate": "2024-01-15",
    "nextBillingDate": "2024-02-15"
  }
}
```

**List Subscriptions:**

```json theme={null}
{
  "subscriptions": [
    {
      "_id": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
      "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
      "planId": "k89cq3gkzhiys60bzuf53bh3hte8zx2d",
      "status": "active",
      "startDate": "2024-01-15",
      "nextBillingDate": "2024-02-15"
    },
    {
      "_id": "n01eso5imjzku82dwxh75dk5kug0af4f",
      "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
      "planId": "l98dro4hlizjt71cvwg64cj4juf9ze3e",
      "status": "canceled",
      "startDate": "2023-12-01",
      "nextBillingDate": null
    }
  ]
}
```

## How to cancel a subscription

Cancel a customer's subscription. You can choose to cancel immediately or at the end of the current billing period.

* Endpoint: `https://giant-goldfish-922.convex.site/api/v1/subscriptions/cancel`
* Method: `DELETE`
* Authentication: Authorization: `Bearer <CREDIBILL_API_KEY>`

### Query Parameters

| Parameter         | Type    | Required | Description                                                                        |
| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------- |
| subscriptionId    | string  | Yes      | The unique ID of the subscription to cancel.                                       |
| cancelAtPeriodEnd | boolean | No       | If "true", cancels at the end of the current billing period. Otherwise, immediate. |

### Examples

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    // src/hooks/useCancelSubscription.ts

    import { useMutation, UseMutationOptions } from "@tanstack/react-query";

    export type CancelSubscriptionData = {
      subscriptionId: string,
      cancelAtPeriodEnd?: boolean,
    };

    export type CancelSubscriptionResponse = {
      success: boolean,
      message: string,
    };

    const useApiKey = () => {
    // !! REPLACE THIS PLACEHOLDER !!
    const apiKey = 'YOUR_CREDIBILL_API_KEY';
    return apiKey;
    };

    const CREDIBILL_URL = 'https://giant-goldfish-922.convex.site';
    const ENDPOINT = '/api/v1/subscriptions/cancel';

    type UseCancelSubscriptionOptions = Omit<UseMutationOptions<
    CancelSubscriptionResponse,
    Error,
    CancelSubscriptionData

    > , 'mutationFn'>;

    /\*\*

    - Custom React Query hook for canceling a subscription
    - @param options - Optional configuration options for useMutation
    - @returns The mutation object provided by TanStack Query
      \*/
      export const useCancelSubscription = (
      options?: UseCancelSubscriptionOptions
      ) => {
      const apiKey = useApiKey();

    return useMutation<CancelSubscriptionResponse, Error, CancelSubscriptionData>({
    mutationFn: async (data: CancelSubscriptionData) => {
    if (!apiKey) {
    throw new Error("API Key is required for authentication.");
    }

          const url = new URL(`${CREDIBILL_URL}${ENDPOINT}`);
          url.searchParams.append('subscriptionId', data.subscriptionId);
          if (data.cancelAtPeriodEnd !== undefined) {
            url.searchParams.append('cancelAtPeriodEnd', String(data.cancelAtPeriodEnd));
          }

          const response = await fetch(url.toString(), {
            method: 'DELETE',
            headers: {
              'Content-Type': 'application/json',
              'Authorization': `Bearer ${apiKey}`,
            },
          });

          if (!response.ok) {
            const errorBody = await response.json();
            throw new Error(errorBody.error || `HTTP error! Status: ${response.status}`);
          }

          return response.json();
        },
        ...options,

    });
    };

    ```

    #### Usage in a Component

    ```typescript theme={null}
    import React, { useState } from 'react';
    import { useCancelSubscription } from './hooks/useCancelSubscription';

    export const CancelSubscriptionForm = () => {
      const [subscriptionId, setSubscriptionId] = useState('');
      const [cancelAtPeriodEnd, setCancelAtPeriodEnd] = useState(false);

      const { mutate, isPending, isError, isSuccess, error, reset } = useCancelSubscription({
        onSuccess: () => {
          console.log('Subscription cancelled successfully');
          setSubscriptionId('');
          setCancelAtPeriodEnd(false);
        },
      });

      const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        if (isPending) return;

        reset();

        mutate({
          subscriptionId: subscriptionId.trim(),
          cancelAtPeriodEnd,
        });
      };

      return (
        <div className="p-6 max-w-lg mx-auto bg-white rounded-xl shadow-lg space-y-4">
          <h2 className="text-2xl font-bold text-gray-800">Cancel Subscription</h2>
          <form onSubmit={handleSubmit} className="space-y-4">
            <div>
              <label htmlFor="subscriptionId" className="block text-sm font-medium text-gray-700">
                Subscription ID (Required)
              </label>
              <input
                id="subscriptionId"
                type="text"
                value={subscriptionId}
                onChange={(e) => setSubscriptionId(e.target.value)}
                required
                disabled={isPending}
                className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
              />
            </div>

            <div className="flex items-center">
              <input
                id="cancelAtPeriodEnd"
                type="checkbox"
                checked={cancelAtPeriodEnd}
                onChange={(e) => setCancelAtPeriodEnd(e.target.checked)}
                disabled={isPending}
                className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
              />
              <label htmlFor="cancelAtPeriodEnd" className="ml-2 block text-sm text-gray-700">
                Cancel at end of billing period (otherwise cancel immediately)
              </label>
            </div>

            <button
              type="submit"
              disabled={isPending}
              className="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition duration-150 ease-in-out disabled:opacity-50"
            >
              {isPending ? 'Processing...' : 'Cancel Subscription'}
            </button>
          </form>

          {isSuccess && (
            <div className="mt-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded-lg">
              Success! Subscription has been cancelled.
            </div>
          )}
          {isError && (
            <div className="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
              Error: {error?.message}
            </div>
          )}
        </div>
      );
    };
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # Cancel subscription immediately
    curl -X DELETE 'https://giant-goldfish-922.convex.site/api/v1/subscriptions/cancel?subscriptionId=m90dro4hlizjt71cvwg64cj4juf9ze3e' \
      -H 'Content-Type: application/json' \
      -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY'

    # Cancel subscription at end of billing period

    curl -X DELETE 'https://giant-goldfish-922.convex.site/api/v1/subscriptions/cancel?subscriptionId=m90dro4hlizjt71cvwg64cj4juf9ze3e&cancelAtPeriodEnd=true' \
     -H 'Content-Type: application/json' \
     -H 'Authorization: Bearer $YOUR_CREDIBILL_API_KEY'

    ```
  </Tab>
</Tabs>

### Response

When you cancel a subscription, the API responds with a success message.

```json theme={null}
{
  "success": true,
  "message": "Subscription cancelled"
}
```
