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

# Subscription contracts

> Find, inspect, pause, resume, and cancel subscription contracts

Find and manage subscription contracts that were created through Appfox.

## Contract identifiers

The API base URL is `https://subscriptions-app-new.getappfox.com`.

The `contractId` parameter can be either:

* The numeric Shopify contract ID (e.g., `123456789`)
* The full Shopify GID (e.g., `gid://shopify/SubscriptionContract/123456789`)

Official routes live under `/api/v1/subscription-contracts`. Do not use `/api/external/contracts`.

## Search contracts

```http theme={null}
GET /api/v1/subscription-contracts
```

All filters are optional and combine with each other.

| Parameter       | Type    | Description                                                  |
| --------------- | ------- | ------------------------------------------------------------ |
| `customerId`    | string  | Numeric Shopify customer ID or full Customer GID             |
| `customerEmail` | string  | Exact customer email address                                 |
| `contractId`    | string  | Numeric Shopify contract ID or full SubscriptionContract GID |
| `status`        | string  | `ACTIVE`, `PAUSED`, `CANCELLED`, `EXPIRED`, or `FAILED`      |
| `page`          | integer | Page number. Defaults to `1`                                 |
| `pageSize`      | integer | Results per page from `1` to `100`. Defaults to `25`         |

For example, find a customer's active contracts:

```bash theme={null}
curl "https://subscriptions-app-new.getappfox.com/api/v1/subscription-contracts?customerEmail=jane@example.com&status=ACTIVE" \
  -H "Authorization: Bearer afx_live_..."
```

```json Response (200 OK) theme={null}
{
  "data": [
    {
      "id": "gid://shopify/SubscriptionContract/123456789",
      "status": "ACTIVE",
      "customer": {
        "id": "gid://shopify/Customer/456",
        "firstName": "Jane",
        "lastName": "Doe",
        "email": "jane@example.com"
      },
      "billingPolicy": {
        "interval": "MONTH",
        "intervalCount": 1,
        "minCycles": 3,
        "maxCycles": null
      },
      "nextBillingDate": "2026-10-01T00:00:00.000Z",
      "scheduledResumeAt": null,
      "currencyCode": "EUR",
      "amount": 80,
      "createdAt": "2026-01-15T10:30:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPreviousPage": false
  }
}
```

Email filter is an exact match. Search results come from Appfox's shop-scoped contract mirror. Get-by-id reads live Shopify data.

## Get a contract

```http theme={null}
GET /api/v1/subscription-contracts/{contractId}
```

This endpoint returns the current status, customer, billing policy, delivery
price, and product lines directly from Shopify. It also returns the scheduled
Appfox resume time when the contract is paused.

## Manage a contract

```http theme={null}
POST /api/v1/subscription-contracts/{contractId}/actions
```

## Pause a subscription

Pause a subscription for up to 60 days. The `resumeAt` field is **required** and must be a future ISO-8601 timestamp. The contract auto-resumes at that time via the billing cron.

<CodeGroup>
  ```bash curl theme={null}
  curl https://subscriptions-app-new.getappfox.com/api/v1/subscription-contracts/123456789/actions \
    -X POST \
    -H "Authorization: Bearer afx_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "action": "pause",
      "resumeAt": "2026-10-15T09:00:00Z"
    }'
  ```

  ```json Request body theme={null}
  {
    "action": "pause",
    "resumeAt": "2026-10-15T09:00:00Z"
  }
  ```

  ```json Response (200 OK) theme={null}
  {
    "data": {
      "id": "gid://shopify/SubscriptionContract/123456789",
      "status": "PAUSED",
      "scheduledResumeAt": "2026-10-15T09:00:00.000Z"
    }
  }
  ```
</CodeGroup>

**Validation:**

* `resumeAt` is required on pause
* `resumeAt` must be a future timestamp
* Maximum pause duration is 60 days from the request time

## Resume a subscription

Resume a paused subscription immediately.

<CodeGroup>
  ```bash curl theme={null}
  curl https://subscriptions-app-new.getappfox.com/api/v1/subscription-contracts/123456789/actions \
    -X POST \
    -H "Authorization: Bearer afx_live_..." \
    -H "Content-Type: application/json" \
    -d '{"action": "resume"}'
  ```

  ```json Request body theme={null}
  {
    "action": "resume"
  }
  ```

  ```json Response (200 OK) theme={null}
  {
    "data": {
      "id": "gid://shopify/SubscriptionContract/123456789",
      "status": "ACTIVE",
      "scheduledResumeAt": null
    }
  }
  ```
</CodeGroup>

## Cancel a subscription

Permanently cancel a subscription. This action cannot be undone. Merchant and API cancels are not blocked by the plan-level minimum cycles setting. That setting only blocks customer-portal cancels.

<CodeGroup>
  ```bash curl theme={null}
  curl https://subscriptions-app-new.getappfox.com/api/v1/subscription-contracts/123456789/actions \
    -X POST \
    -H "Authorization: Bearer afx_live_..." \
    -H "Content-Type: application/json" \
    -d '{"action": "cancel"}'
  ```

  ```json Request body theme={null}
  {
    "action": "cancel"
  }
  ```

  ```json Response (200 OK) theme={null}
  {
    "data": {
      "id": "gid://shopify/SubscriptionContract/123456789",
      "status": "CANCELLED",
      "scheduledResumeAt": null
    }
  }
  ```
</CodeGroup>

## POS

Shopify POS cannot enroll a customer into a selling plan at the register. Use this API from your own backend so a custom POS extension can look up the customer, pause, resume, or cancel. New memberships still start on the storefront or in Shopify admin.

## Error responses

All errors use the following structure:

```json theme={null}
{
  "error": {
    "code": "error_code_here",
    "message": "Human-readable error message"
  }
}
```

### Status codes and error codes

| Status | Error Code                | Description                                                    |
| ------ | ------------------------- | -------------------------------------------------------------- |
| `400`  | `invalid_json`            | Request body is not valid JSON                                 |
| `400`  | `invalid_contract_id`     | Contract ID format is invalid                                  |
| `400`  | `invalid_customer_id`     | Customer ID format is invalid                                  |
| `400`  | `invalid_customer_email`  | Customer email is too long                                     |
| `400`  | `invalid_status`          | Status filter is invalid                                       |
| `400`  | `invalid_pagination`      | Page or page size is invalid                                   |
| `401`  | `unauthorized`            | Invalid or revoked API key                                     |
| `403`  | `plan_required`           | Shop is not on the Enterprise plan                             |
| `404`  | `contract_not_found`      | Contract doesn't exist or doesn't belong to this shop          |
| `422`  | `invalid_action`          | Action is not one of: pause, resume, cancel                    |
| `422`  | `resume_at_required`      | Pause action requires a `resumeAt` field                       |
| `422`  | `invalid_resume_at`       | `resumeAt` is not a valid ISO-8601 timestamp or is in the past |
| `422`  | `suspension_too_long`     | `resumeAt` is more than 60 days in the future                  |
| `422`  | `shopify_rejected_action` | Shopify rejected the action (includes Shopify's error details) |
| `502`  | `shopify_request_failed`  | Shopify could not return live contract details                 |
| `500`  | `internal_error`          | An unexpected error occurred on the server                     |

### Example error responses

**Contract not found:**

```json theme={null}
{
  "error": {
    "code": "contract_not_found",
    "message": "No subscription contract with that ID belongs to this shop."
  }
}
```

**Invalid pause duration:**

```json theme={null}
{
  "error": {
    "code": "suspension_too_long",
    "message": "Subscriptions can only be paused for a maximum of 60 days."
  }
}
```

**Shopify rejected the action:**

```json theme={null}
{
  "error": {
    "code": "shopify_rejected_action",
    "message": "Shopify rejected the action: Contract is already paused."
  }
}
```
