---
title: "What can I do with REST API v1?"
description: "REST API v1 on RevenueDot: delete customers, grant promotional access, override offerings and run store actions with a secret key."
url: https://revenuedot.app/docs/api/rest-v1
---

# What can I do with REST API v1?

REST API v1 uses the same `/v1/subscribers` paths as the SDK endpoints, with a **secret key** (`sk_...`). Customer info answers include `subscriber_attributes`.
`GET /v1/subscribers/{app_user_id}` and `POST /v1/receipts` also accept a secret key; they are listed on [SDK endpoints](https://revenuedot.app/docs/api/sdk-endpoints.md). With a secret key, send `X-Platform` (ios, android ...) to `POST /v1/receipts` so RevenueDot knows the app.

Base URL: your server, for example `http://localhost:8787` or `https://revenuedot.example.com`. The examples read `REVENUEDOT_URL`, `PUBLIC_KEY`, `SECRET_KEY` and `PROJECT_ID` from your shell.

## Operations on this page (10)

- **Customers (v1)**: [Delete a customer](#delete-a-customer)
- **Promotional entitlements (v1)**: [Grant promotional access](#grant-promotional-access), [Revoke promotional access](#revoke-promotional-access)
- **Offering overrides (v1)**: [Show a customer another offering](#show-a-customer-another-offering), [Remove a customer's offering override](#remove-a-customers-offering-override)
- **Store actions (v1)**: [Refund and revoke a Google Play subscription](#refund-and-revoke-a-google-play-subscription), [Defer a Google Play renewal](#defer-a-google-play-renewal), [Refund a Google Play order](#refund-a-google-play-order), [Cancel a Google Play subscription](#cancel-a-google-play-subscription), [Extend an App Store subscription](#extend-an-app-store-subscription)

## Customers (v1)

Secret-key customer operations.

### Delete a customer

`DELETE /v1/subscribers/{app_user_id}` · Auth: secret key

Deletes the customer with its aliases, attributes, purchases and events. Cannot be undone.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |

**Example request**

```bash
curl -s -X DELETE "$REVENUEDOT_URL/v1/subscribers/user_1" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Deleted.
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "app_user_id": "user_1"
}
```

## Promotional entitlements (v1)

Grant and revoke access without a purchase.

### Grant promotional access

`POST /v1/subscribers/{app_user_id}/entitlements/{entitlement_identifier}/promotional` · Auth: secret key

Gives the customer the entitlement until `end_time_ms`, or for a `duration`. Creates the customer when needed. A grant whose end is within 2 hours of an existing promotional grant for the same entitlement is a duplicate and changes nothing.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `entitlement_identifier` | string | yes | Entitlement lookup key, for example `pro`. |

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `end_time_ms` | integer | no | When access ends, epoch milliseconds. Preferred. |
| `duration` | `daily`, `three_day`, `weekly`, `two_week`, `monthly`, `two_month`, `three_month`, `six_month`, `yearly`, `lifetime` | no | Deprecated alternative to end_time_ms. |
| `start_time_ms` | integer | no | Start for `duration`. Default now. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/entitlements/pro/promotional" -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" -d '{"duration":"weekly"}'
```

**Responses**

- **200**: Customer info with the grant (store `promotional`). Returns [CustomerInfo](#customerinfo).
- **400**: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns [V1Error](#v1error).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

### Revoke promotional access

`POST /v1/subscribers/{app_user_id}/entitlements/{entitlement_identifier}/revoke_promotionals` · Auth: secret key

Ends every active promotional grant of this entitlement now.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `entitlement_identifier` | string | yes |  |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/entitlements/pro/revoke_promotionals" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

## Offering overrides (v1)

Show one customer a different offering.

### Show a customer another offering

`POST /v1/subscribers/{app_user_id}/offerings/{offering_identifier}/override` · Auth: secret key

The customer's `current_offering_id` becomes this offering.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `offering_identifier` | string | yes | Offering id (ofrng...) or lookup key. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/offerings/default/override" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

### Remove a customer's offering override

`DELETE /v1/subscribers/{app_user_id}/offerings/override` · Auth: secret key

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |

**Example request**

```bash
curl -s -X DELETE "$REVENUEDOT_URL/v1/subscribers/user_1/offerings/override" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

## Store actions (v1)

Refund, revoke, cancel, defer and extend through the store that sold the subscription.

### Refund and revoke a Google Play subscription

`POST /v1/subscribers/{app_user_id}/subscriptions/{product_identifier}/revoke` · Auth: secret key

Google Play only: refunds the latest payment and ends access now. Other stores answer 400 with code 7000.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `product_identifier` | string | yes | Store product id of the subscription. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/subscriptions/$PRODUCT_IDENTIFIER/revoke" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **400**: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns [V1Error](#v1error).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).
- **503**: The store could not be reached. Retry later. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

### Defer a Google Play renewal

`POST /v1/subscribers/{app_user_id}/subscriptions/{product_identifier}/defer` · Auth: secret key

Google Play only: moves the next renewal date. Send `expiry_time_ms` or `extend_by_days`. Use extend for App Store subscriptions.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `product_identifier` | string | yes |  |

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `expiry_time_ms` | integer | no | New expiry, epoch milliseconds; later than the current one. |
| `extend_by_days` | integer | no | Days to add, 1 to 365. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/subscriptions/$PRODUCT_IDENTIFIER/defer" -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" -d '{"extend_by_days":7}'
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **400**: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns [V1Error](#v1error).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).
- **503**: The store could not be reached. Retry later. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

### Refund a Google Play order

`POST /v1/subscribers/{app_user_id}/transactions/{store_transaction_identifier}/refund` · Auth: secret key

Google Play only: refunds and revokes the order.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `store_transaction_identifier` | string | yes | Google order id. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/transactions/$STORE_TRANSACTION_IDENTIFIER/refund" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **400**: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns [V1Error](#v1error).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).
- **503**: The store could not be reached. Retry later. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

### Cancel a Google Play subscription

`POST /v1/subscribers/{app_user_id}/subscriptions/{store_transaction_identifier}/cancel` · Auth: secret key

Google Play only: turns auto-renew off; access continues to the end of the period.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `store_transaction_identifier` | string | yes | Store transaction id of the subscription. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/subscriptions/$STORE_TRANSACTION_IDENTIFIER/cancel" -H "Authorization: Bearer $SECRET_KEY"
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **400**: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns [V1Error](#v1error).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).
- **503**: The store could not be reached. Retry later. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

### Extend an App Store subscription

`POST /v1/subscribers/{app_user_id}/subscriptions/{store_transaction_identifier}/extend` · Auth: secret key

App Store only (needs the app's in-app purchase key): Apple extends the renewal date. Use defer for Google Play.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `app_user_id` | string | yes | App user id, URL-encoded (anonymous ids look like `$RCAnonymousID:...`). |
| `store_transaction_identifier` | string | yes |  |

**Request body** (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `extend_by_days` | integer | no | 1 to 90. |
| `extend_reason_code` | integer | no | Apple's reason code: 0 undeclared, 1 customer satisfaction, 2 other, 3 service issue or outage. |

**Example request**

```bash
curl -s -X POST "$REVENUEDOT_URL/v1/subscribers/user_1/subscriptions/$STORE_TRANSACTION_IDENTIFIER/extend" -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" -d '{"extend_by_days":7,"extend_reason_code":1}'
```

**Responses**

- **200**: Customer info. Returns [CustomerInfo](#customerinfo).
- **400**: Bad request. For receipts, a 4xx tells the SDK the purchase can never be accepted, so it finishes the transaction. Returns [V1Error](#v1error).
- **401**: Unknown API key. Returns [V1Error](#v1error).
- **403**: A public app key was used for a secret-key endpoint. Returns [V1Error](#v1error).
- **404**: Not found. Returns [V1Error](#v1error).
- **503**: The store could not be reached. Retry later. Returns [V1Error](#v1error).

Example 200 response:

```json
{
  "request_date": "2026-09-30T20:41:54Z",
  "request_date_ms": 1790800914034,
  "subscriber": {
    "entitlements": {
      "pro": {
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "product_identifier": "pro_monthly",
        "purchase_date": "2026-09-30T20:41:54Z"
      }
    },
    "first_seen": "2026-09-30T20:41:54Z",
    "last_seen": "2026-09-30T20:41:54Z",
    "management_url": null,
    "non_subscriptions": {},
    "original_app_user_id": "user_1",
    "original_application_version": null,
    "original_purchase_date": "2026-09-30T20:41:54Z",
    "other_purchases": {},
    "subscriptions": {
      "pro_monthly": {
        "auto_resume_date": null,
        "billing_issues_detected_at": null,
        "display_name": null,
        "expires_date": "2026-10-30T20:41:54Z",
        "grace_period_expires_date": null,
        "is_sandbox": true,
        "management_url": null,
        "original_purchase_date": "2026-09-30T20:41:54Z",
        "ownership_type": "PURCHASED",
        "period_type": "normal",
        "purchase_date": "2026-09-30T20:41:54Z",
        "refunded_at": null,
        "store": "test_store",
        "store_transaction_id": "test_1790800914000_quickstart",
        "unsubscribe_detected_at": null,
        "price": {
          "amount": 9.99,
          "currency": "USD"
        }
      }
    }
  }
}
```

## Objects

The shapes the operations above send and return.

### CustomerInfo

| Field | Type | Required | Description |
|---|---|---|---|
| `request_date` | string | yes | Server time of the response. ISO 8601 in UTC, whole seconds. |
| `request_date_ms` | integer | yes | Server time of the response. Epoch milliseconds. |
| `subscriber` | object | yes |  |
| `subscriber.entitlements` | object | yes | Entitlements the customer has now or had, keyed by lookup key. Check `expires_date` (or use the SDK's `isActive`). |
| `subscriber.first_seen` | string | yes | When the customer was first seen. ISO 8601 in UTC, whole seconds. |
| `subscriber.last_seen` | string | yes | When the customer was last seen. ISO 8601 in UTC, whole seconds. |
| `subscriber.management_url` | string or null | no | Always null today. |
| `subscriber.non_subscriptions` | object | yes | One-time purchases by product id, oldest first. |
| `subscriber.original_app_user_id` | string | yes | The customer's first app user id. |
| `subscriber.original_application_version` | string or null | no | Always null today. |
| `subscriber.original_purchase_date` | string or null | no | Earliest purchase. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `subscriber.other_purchases` | object | no | Always empty. |
| `subscriber.subscriber_attributes` | object | no | Only in answers to secret-key requests. |
| `subscriber.subscriptions` | object | yes | Latest subscription per product id. |

### EntitlementInfo

| Field | Type | Required | Description |
|---|---|---|---|
| `expires_date` | string or null | yes | When access ends. Null for lifetime access. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `grace_period_expires_date` | string or null | yes | End of the billing grace period, when the store grants one. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `product_identifier` | string | yes | Store product id that gives this access. |
| `product_plan_identifier` | string | no | Google Play base plan id, only when there is one. |
| `purchase_date` | string | yes | Start of the current period. ISO 8601 in UTC, whole seconds. |

### NonSubscriptionInfo

| Field | Type | Required | Description |
|---|---|---|---|
| `display_name` | string or null | no |  |
| `id` | string | yes | RevenueDot purchase id. |
| `is_sandbox` | boolean | yes |  |
| `original_purchase_date` | string or null | no | Purchase time. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `purchase_date` | string or null | yes | Purchase time. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `store` | string | yes |  |
| `store_transaction_id` | string | yes | Store transaction id. |
| `price` | object | no |  |
| `price.amount` | number | yes | Price in the purchase currency. |
| `price.currency` | string | yes | ISO 4217 currency code. |

### SubscriptionInfo

| Field | Type | Required | Description |
|---|---|---|---|
| `auto_resume_date` | string or null | no | When a paused Google Play subscription resumes. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `billing_issues_detected_at` | string or null | no | When the latest renewal failed. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `display_name` | string or null | no | Product display name. |
| `expires_date` | string or null | yes | End of the current period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `grace_period_expires_date` | string or null | no | End of the billing grace period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `is_sandbox` | boolean | yes | True for sandbox and Test Store purchases. |
| `management_url` | string or null | no | Always null today. |
| `original_purchase_date` | string or null | no | Start of the subscription. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `ownership_type` | `PURCHASED`, `FAMILY_SHARED` | no |  |
| `period_type` | `normal`, `trial`, `intro`, `promotional`, `prepaid` | yes |  |
| `purchase_date` | string or null | yes | Start of the current period. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `refunded_at` | string or null | no | When the store refunded it. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `store` | `app_store`, `mac_app_store`, `play_store`, `amazon`, `stripe`, `rc_billing`, `promotional`, `test_store`, `paddle`, `roku`, `external` | yes |  |
| `store_transaction_id` | string or null | no | Latest store transaction id (Apple), order id (Google) or Test Store token. |
| `unsubscribe_detected_at` | string or null | no | When auto-renew was turned off. ISO 8601 in UTC, whole seconds (for example 2026-10-30T20:41:54Z), or null. |
| `product_plan_identifier` | string | no | Google Play base plan id, only when there is one. |
| `price` | object | no |  |
| `price.amount` | number | yes | Price in the purchase currency. |
| `price.currency` | string | yes | ISO 4217 currency code. |

### V1Error

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | integer | yes | RevenueCat-compatible backend error code. See the error table. |
| `message` | string | yes | What went wrong. |
| `attribute_errors` | array of object | no | Only for 7263. |
| `attribute_errors[].key_name` | string | no |  |
| `attribute_errors[].message` | string | no |  |

## Related

- [API overview](https://revenuedot.app/docs/api.md)
- [Authentication](https://revenuedot.app/docs/api/authentication.md)
- [Errors](https://revenuedot.app/docs/api/errors.md)
- [OpenAPI document](https://revenuedot.app/docs/api/openapi.yaml)
