{
  "openapi": "3.0.3",
  "info": {
    "title": "Subsail Public API",
    "description": "REST API for reading and updating Subsail account data (subscribers, subscriptions, issues, and copies). Create API keys in Subsail under Settings → API keys. Keys are scoped to your user and account. Use read-only keys for queries; read-write keys are required for PATCH and POST endpoints.",
    "version": "1.0.0",
    "contact": {
      "name": "Subsail",
      "url": "https://subsail.com/docs/api"
    }
  },
  "servers": [
    {
      "url": "https://api.subsail.com"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Me",
      "description": "Authenticated user and account for the current API key"
    },
    {
      "name": "Subscribers",
      "description": "Subscriber records for your account"
    },
    {
      "name": "Subscriptions",
      "description": "Subscription records and shipping details"
    },
    { "name": "Issues", "description": "Magazine issues" },
    {
      "name": "Copies",
      "description": "Individual subscription copies (issues sent to subscribers)"
    }
  ],
  "paths": {
    "/v1/me/": {
      "get": {
        "tags": ["Me"],
        "summary": "Get the current user and account",
        "description": "Verify an API key and return the authenticated user's email, the account the key is scoped to, and the key's permission scope.",
        "operationId": "getMe",
        "responses": {
          "200": {
            "description": "Authenticated user context",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Me" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" }
        }
      }
    },
    "/v1/subscribers/": {
      "get": {
        "tags": ["Subscribers"],
        "summary": "List subscribers",
        "operationId": "listSubscribers",
        "parameters": [
          { "$ref": "#/components/parameters/cursor" },
          { "$ref": "#/components/parameters/limit" },
          {
            "name": "search",
            "in": "query",
            "description": "Partial text search across subscriber ID, name, email, and related subscription fields.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of subscribers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedSubscribers"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" }
        }
      }
    },
    "/v1/subscribers/{id}/": {
      "get": {
        "tags": ["Subscribers"],
        "summary": "Get a subscriber",
        "operationId": "getSubscriber",
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "responses": {
          "200": {
            "description": "Subscriber",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Subscriber" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/subscriptions/": {
      "get": {
        "tags": ["Subscriptions"],
        "summary": "List subscriptions",
        "operationId": "listSubscriptions",
        "parameters": [
          { "$ref": "#/components/parameters/cursor" },
          { "$ref": "#/components/parameters/limit" },
          {
            "name": "subscriber",
            "in": "query",
            "description": "Filter by subscriber ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "country",
            "in": "query",
            "description": "Filter by subscription country code.",
            "schema": { "type": "string" }
          },
          {
            "name": "source",
            "in": "query",
            "description": "Filter by order source ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "is_gift",
            "in": "query",
            "description": "Filter by gift status. Use `true` for gift subscriptions, `false` for non-gifts.",
            "schema": { "type": "boolean" }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Partial text search across subscription and subscriber fields.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginatedSubscriptions"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" }
        }
      }
    },
    "/v1/subscriptions/{id}/": {
      "get": {
        "tags": ["Subscriptions"],
        "summary": "Get a subscription",
        "operationId": "getSubscription",
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "responses": {
          "200": {
            "description": "Subscription",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Subscription" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Subscriptions"],
        "summary": "Update a subscription",
        "description": "Partial update: include only the fields you want to change. Requires a read-write API key. Writable fields: first_name, last_name, company, address1, address2, city, state, postcode, country, phone, notes.",
        "operationId": "patchSubscription",
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubscriptionUpdate" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated subscription",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Subscription" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenWrite" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/subscriptions/{id}/cancel/": {
      "post": {
        "tags": ["Subscriptions"],
        "summary": "Cancel a subscription",
        "description": "Requires a read-write API key. For recurring subscriptions, typically `stop_renewals` is the preferred option (stop renewals and keep remaining issues active). Use `cancel` to stop renewals and cancel unsent issues. For non-recurring subscriptions, only `cancel` (cancel unsent issues) is supported, as there are no renewals to stop.",
        "operationId": "cancelSubscription",
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubscriptionCancel" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated subscription after cancel",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Subscription" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenWrite" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/subscriptions/{id}/status/": {
      "get": {
        "tags": ["Subscriptions"],
        "summary": "Get subscription status",
        "description": "Special endpoint for getting a subscription's status, useful when integrating Subsail with other systems. Does not require an API key.",
        "operationId": "getSubscriptionStatus",
        "security": [],
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "responses": {
          "200": {
            "description": "Subscription status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status"],
                  "properties": {
                    "status": {
                      "$ref": "#/components/schemas/SubscriptionStatus"
                    }
                  }
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/issues/": {
      "get": {
        "tags": ["Issues"],
        "summary": "List issues",
        "operationId": "listIssues",
        "parameters": [
          { "$ref": "#/components/parameters/cursor" },
          { "$ref": "#/components/parameters/limit" }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of issues",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaginatedIssues" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" }
        }
      },
      "post": {
        "tags": ["Issues"],
        "summary": "Create an issue",
        "description": "Requires a read-write API key. `number` is required and must be unique for the account. Other writable fields: name, release_date_actual, release_date_fuzzy, date_as_start_issue, recurring_charge_date, newsstand_ean. Date fields must be `YYYY-MM-DD` or null. If `name` is omitted, it defaults to `Issue {number}`.",
        "operationId": "createIssue",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/IssueCreate" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created issue",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Issue" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenWrite" }
        }
      }
    },
    "/v1/issues/{id}/": {
      "get": {
        "tags": ["Issues"],
        "summary": "Get an issue",
        "operationId": "getIssue",
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "responses": {
          "200": {
            "description": "Issue",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Issue" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Issues"],
        "summary": "Update an issue",
        "description": "Partial update: include only the fields you want to change. Requires a read-write API key. Writable fields: name, release_date_actual, release_date_fuzzy, date_as_start_issue, recurring_charge_date, newsstand_ean. Date fields must be `YYYY-MM-DD` or null.",
        "operationId": "patchIssue",
        "parameters": [{ "$ref": "#/components/parameters/resourceId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/IssueUpdate" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated issue",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Issue" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenWrite" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/issues/{id}/copies/": {
      "get": {
        "tags": ["Copies"],
        "summary": "List copies for an issue",
        "operationId": "listIssueCopies",
        "parameters": [
          { "$ref": "#/components/parameters/resourceId" },
          { "$ref": "#/components/parameters/cursor" },
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/copySent" },
          {
            "name": "subscriber",
            "in": "query",
            "description": "Filter by subscriber ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "subscription",
            "in": "query",
            "description": "Filter by subscription ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "country",
            "in": "query",
            "description": "Filter by subscription country code.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of copies",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaginatedCopies" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/copies/": {
      "get": {
        "tags": ["Copies"],
        "summary": "List copies",
        "operationId": "listCopies",
        "parameters": [
          { "$ref": "#/components/parameters/cursor" },
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/copySent" },
          {
            "name": "issue",
            "in": "query",
            "description": "Filter by issue ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "subscriber",
            "in": "query",
            "description": "Filter by subscriber ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "subscription",
            "in": "query",
            "description": "Filter by subscription ID.",
            "schema": { "type": "string" }
          },
          {
            "name": "country",
            "in": "query",
            "description": "Filter by subscription country code.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of copies",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaginatedCopies" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenInactive" }
        }
      }
    },
    "/v1/copies/mark-sent/": {
      "post": {
        "tags": ["Copies"],
        "summary": "Mark copies as sent",
        "description": "Requires a read-write API key. Marks one or more copies as sent via an `ids` list. Unknown or invalid copy IDs are skipped.",
        "operationId": "markCopiesSentBulk",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MarkSentRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Copies marked sent",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MarkSentResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenWrite" }
        }
      }
    },
    "/v1/copies/{copy_id}/mark-sent/": {
      "post": {
        "tags": ["Copies"],
        "summary": "Mark a copy as sent",
        "description": "Requires a read-write API key. Marks the copy with the given numeric ID as sent.",
        "operationId": "markCopySent",
        "parameters": [
          {
            "name": "copy_id",
            "in": "path",
            "required": true,
            "description": "Numeric copy (subscription item) ID.",
            "schema": { "type": "integer", "format": "int64" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MarkSentDateRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Copy marked sent",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MarkSentResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/ForbiddenWrite" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Subsail API key prefixed with `ssk_`. Example: `Authorization: Bearer ssk_…`"
      }
    },
    "parameters": {
      "cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Opaque cursor from the previous response. Omit for the first page.",
        "schema": { "type": "string" }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "description": "Items per page. Default: 50. Maximum: 200.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "resourceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Resource ID (alphanumeric).",
        "schema": { "type": "string", "pattern": "^[\\w\\d]+$" }
      },
      "copySent": {
        "name": "status",
        "in": "query",
        "description": "Filter by status.",
        "schema": {
          "type": "string",
          "enum": ["Sent", "Cancelled", "Not sent"]
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key (empty response body)."
      },
      "ForbiddenInactive": {
        "description": "Account is inactive (empty response body)."
      },
      "ForbiddenWrite": {
        "description": "Write request rejected. Inactive account returns an empty body. Read-only API key returns JSON with `code: read_only_key`.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ApiError" },
            "example": {
              "error": true,
              "message": "This API key is read-only. Create a read-write key in Subsail settings to perform write operations.",
              "code": "read_only_key"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ApiError" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ApiError" }
          }
        }
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "required": ["error", "message"],
        "properties": {
          "error": { "type": "boolean", "enum": [true] },
          "message": { "type": "string" },
          "code": {
            "type": "string",
            "description": "Machine-readable code, e.g. `read_only_key` for read-only key write attempts."
          }
        }
      },
      "PaginatedSubscribers": {
        "allOf": [
          { "$ref": "#/components/schemas/PaginationEnvelope" },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/Subscriber" }
              }
            }
          }
        ]
      },
      "PaginatedSubscriptions": {
        "allOf": [
          { "$ref": "#/components/schemas/PaginationEnvelope" },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/Subscription" }
              }
            }
          }
        ]
      },
      "PaginatedIssues": {
        "allOf": [
          { "$ref": "#/components/schemas/PaginationEnvelope" },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/Issue" }
              }
            }
          }
        ]
      },
      "PaginatedCopies": {
        "allOf": [
          { "$ref": "#/components/schemas/PaginationEnvelope" },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/Copy" }
              }
            }
          }
        ]
      },
      "Me": {
        "type": "object",
        "required": ["email", "account", "scope"],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the user who owns this API key."
          },
          "account": { "$ref": "#/components/schemas/AccountRef" },
          "scope": {
            "type": "string",
            "enum": ["read_only", "read_write"],
            "description": "Permission of this API key. `read_only` keys can query; `read_write` keys can also PATCH and POST."
          }
        }
      },
      "AccountRef": {
        "type": "object",
        "required": ["name", "subdomain"],
        "properties": {
          "name": { "type": "string", "description": "Account name." },
          "subdomain": {
            "type": "string",
            "description": "Account subdomain (e.g. `apimag` for apimag.subsail.com)."
          }
        }
      },
      "PaginationEnvelope": {
        "type": "object",
        "required": ["items", "has_more", "cursor"],
        "properties": {
          "has_more": {
            "type": "boolean",
            "description": "True if more items are available after this page."
          },
          "cursor": {
            "type": "string",
            "nullable": true,
            "description": "Pass this as `cursor` to fetch the next page. Null when `has_more` is false."
          }
        }
      },
      "Subscriber": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Subscriber ID." },
          "first_name": { "type": "string", "nullable": true },
          "last_name": { "type": "string", "nullable": true },
          "full_name": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "latest_country": { "type": "string", "nullable": true },
          "latest_order_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "has_active_subscription": { "type": "boolean" },
          "subscription_count": { "type": "integer" },
          "ltv": {
            "type": "string",
            "description": "Lifetime value (account currency)."
          },
          "ltv_currency": {
            "type": "string",
            "description": "ISO currency code for `ltv` (account selling currency)."
          },
          "date_created": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        }
      },
      "SubscriptionStatus": {
        "type": "string",
        "enum": [
          "Active",
          "Active - Renewals stopped",
          "Complete",
          "Cancelled",
          "Past due"
        ]
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Subscription ID." },
          "status": {
            "$ref": "#/components/schemas/SubscriptionStatus"
          },
          "medium": {
            "type": "string",
            "enum": ["Print", "Digital", "Print + digital"]
          },
          "first_name": { "type": "string", "nullable": true },
          "last_name": { "type": "string", "nullable": true },
          "shipping_address": {
            "$ref": "#/components/schemas/ShippingAddress"
          },
          "length": {
            "type": "integer",
            "nullable": true,
            "description": "How many issues have been purchased during the subscription."
          },
          "notes": {
            "type": "string",
            "nullable": true,
            "description": "Optional internalnotes about the subscription."
          },
          "is_recurring": { "type": "boolean" },
          "is_comp": {
            "type": "boolean",
            "description": "True if the subscription is a complimentary subscription (no purchase made)."
          },
          "is_gift": { "type": "boolean" },
          "gifter": {
            "$ref": "#/components/schemas/GifterRef",
            "nullable": true
          },
          "promo_code": { "type": "string", "nullable": true },
          "discount_amount": { "type": "string" },
          "refunded": { "type": "string" },
          "currency": { "type": "string", "nullable": true },
          "price": { "type": "string", "nullable": true },
          "order_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "start_issue": {
            "$ref": "#/components/schemas/IssueRef",
            "nullable": true
          },
          "issue_list": {
            "type": "array",
            "items": { "type": "integer" },
            "description": "Issue numbers included in this subscription."
          },
          "periods": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SubscriptionPeriod" }
          },
          "subscriber": { "$ref": "#/components/schemas/SubscriberRef" },
          "source": {
            "$ref": "#/components/schemas/SourceRef",
            "nullable": true
          }
        }
      },
      "SubscriptionUpdate": {
        "type": "object",
        "description": "Writable subscription fields only.",
        "properties": {
          "first_name": { "type": "string" },
          "last_name": { "type": "string" },
          "company": { "type": "string" },
          "address1": { "type": "string" },
          "address2": { "type": "string" },
          "city": { "type": "string" },
          "state": { "type": "string" },
          "postcode": { "type": "string" },
          "country": { "type": "string", "description": "ISO country code." },
          "phone": {
            "type": "string",
            "description": "Phone number with country code, e.g. +44 7700 900123."
          },
          "notes": { "type": "string" }
        }
      },
      "SubscriptionCancel": {
        "type": "object",
        "required": ["mode"],
        "properties": {
          "mode": {
            "type": "string",
            "enum": ["stop_renewals", "cancel"],
            "description": "`stop_renewals`: recurring only — stop renewals and keep remaining issues active. `cancel`: cancel the subscription and cancel unsent issues (only option for non-recurring)."
          }
        }
      },
      "ShippingAddress": {
        "type": "object",
        "properties": {
          "company": { "type": "string", "nullable": true },
          "address1": { "type": "string", "nullable": true },
          "address2": { "type": "string", "nullable": true },
          "city": { "type": "string", "nullable": true },
          "state": { "type": "string", "nullable": true },
          "postcode": { "type": "string", "nullable": true },
          "country": { "type": "string", "nullable": true },
          "country_name": { "type": "string", "nullable": true },
          "phone": { "type": "string", "nullable": true }
        }
      },
      "SubscriptionPeriod": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "order_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "is_complete": { "type": "boolean" },
          "length": {
            "type": "integer",
            "nullable": true,
            "description": "How many issues this period contains."
          },
          "issue_list": {
            "type": "array",
            "items": { "type": "integer" },
            "description": "Issue numbers included in this period."
          }
        }
      },
      "SubscriberRef": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "full_name": { "type": "string" },
          "email": { "type": "string", "format": "email" }
        }
      },
      "GifterRef": {
        "type": "object",
        "properties": {
          "full_name": { "type": "string" },
          "email": { "type": "string", "format": "email" }
        },
        "description": "Details of the person who gifted the subscription (when `is_gift` is true)."
      },
      "SourceRef": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" }
        }
      },
      "IssueRef": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "number": { "type": "integer" },
          "name": { "type": "string" }
        }
      },
      "Issue": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Issue ID." },
          "number": { "type": "integer" },
          "name": { "type": "string" },
          "release_date_actual": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "release_date_fuzzy": { "type": "string", "nullable": true },
          "date_as_start_issue": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "recurring_charge_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "newsstand_ean": { "type": "string", "nullable": true }
        }
      },
      "IssueCreate": {
        "allOf": [
          { "$ref": "#/components/schemas/IssueUpdate" },
          {
            "type": "object",
            "required": ["number"],
            "properties": {
              "number": {
                "type": "integer",
                "minimum": 1,
                "description": "Issue number. Must be unique."
              }
            }
          }
        ]
      },
      "IssueUpdate": {
        "type": "object",
        "description": "Writable issue fields only. Date fields use YYYY-MM-DD or null.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Optional public issue name. e.g. 'Spring issue'. If omitted, the issue name will be 'Issue [number]'."
          },
          "release_date_actual": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Actual release date. `YYYY-MM-DD` or null to remove."
          },
          "release_date_fuzzy": {
            "type": "string",
            "nullable": true,
            "description": "Textual release date, useful if you don't know the actual date yet. Use `null` to remove."
          },
          "date_as_start_issue": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date this issue becomes the start issue for flexi-start subscriptions. `YYYY-MM-DD` or null to remove."
          },
          "recurring_charge_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date you want renewal charges for subscriptions that renew with this issue be made on. `YYYY-MM-DD` or null to remove."
          },
          "newsstand_ean": {
            "type": "string",
            "nullable": true,
            "description": "Newsstand EAN (if you sync orders to Newsstand). Use `null` to remove."
          }
        }
      },
      "Copy": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Numeric copy (subscription item) ID."
          },
          "date_sent": { "type": "string", "format": "date", "nullable": true },
          "status": {
            "type": "string",
            "enum": ["Sent", "Cancelled", "Not sent"]
          },
          "country": { "type": "string", "description": "ISO country code." },
          "issue": { "$ref": "#/components/schemas/IssueRef" },
          "subscriber_id": { "type": "string" },
          "subscription_id": { "type": "string" }
        }
      },
      "MarkSentRequest": {
        "type": "object",
        "required": ["ids"],
        "properties": {
          "ids": {
            "type": "array",
            "items": { "type": "integer" },
            "minItems": 1,
            "description": "Copy IDs to mark as sent. May contain a single ID."
          },
          "date": {
            "type": ["string", "null"],
            "format": "date",
            "description": "Sent date in YYYY-MM-DD format. Use `null` to remove sent date. Omit to mark sent without a date."
          }
        }
      },
      "MarkSentDateRequest": {
        "type": "object",
        "properties": {
          "date": {
            "type": ["string", "null"],
            "format": "date",
            "description": "Sent date in YYYY-MM-DD format. Use `null` to remove sent date. Omit to mark sent without a date."
          }
        }
      },
      "MarkSentResponse": {
        "type": "object",
        "required": ["error", "updated_count", "items"],
        "properties": {
          "error": { "type": "boolean", "enum": [false] },
          "updated_count": { "type": "integer" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Copy" }
          }
        }
      }
    }
  }
}
