{
  "openapi": "3.1.0",
  "info": {
    "title": "Feedback Pilgrim Public API",
    "version": "1.0.0",
    "description": "The Feedback Pilgrim Public API lets you integrate feedback collection and management into your own applications.\n\nAuthenticate with a personal API token (Bearer). Every request is automatically scoped to the client that the token belongs to — you can only read and modify data owned by that client.\n\nGenerate and manage tokens in **Settings → API**.",
    "contact": {
      "name": "Feedback Pilgrim Support"
    }
  },
  "servers": [
    {
      "url": "/api/public/v1",
      "description": "Current host"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Init",
      "description": "Contact tracking and authentication bootstrap."
    },
    {
      "name": "Posts",
      "description": "Feedback posts from contacts."
    },
    {
      "name": "Release Notes",
      "description": "Published changelog entries."
    },
    {
      "name": "Contacts",
      "description": "Customer contacts."
    },
    {
      "name": "Testimonials",
      "description": "Customer testimonials."
    },
    {
      "name": "Tags",
      "description": "Reusable labels for posts and contacts."
    }
  ],
  "paths": {
    "/init": {
      "post": {
        "tags": [
          "Init"
        ],
        "summary": "Bootstrap contact tracking",
        "description": "Initialize contact tracking and (optionally) generate an authentication URL. Typically called when a contact first interacts with your feedback widget. If a `contact` object with an `email` is provided, the contact is created or updated and an `auth_url` is returned. With no contact, an empty object is returned.",
        "operationId": "init",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InitRequest"
              },
              "example": {
                "contact": {
                  "name": "John Doe",
                  "email": "john@example.com",
                  "mrr": 99.99,
                  "ltv": 1200,
                  "avatar": "https://example.com/avatar.jpg",
                  "plan": "premium"
                },
                "releaseNotes": {
                  "show": true
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK. Returns an auth URL when a contact was supplied, otherwise an empty object.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InitResponse"
                },
                "example": {
                  "auth_url": "https://acme.feedbackpilgrim.com/authenticate-contact?contactUuid=550e8400-e29b-41d4-a716-446655440000"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/posts": {
      "get": {
        "tags": [
          "Posts"
        ],
        "summary": "List posts",
        "operationId": "listPosts",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by post status.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "in_progress",
                "completed"
              ]
            }
          },
          {
            "name": "board_id",
            "in": "query",
            "description": "Filter posts by board ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/TagFilter"
          },
          {
            "name": "published",
            "in": "query",
            "description": "When `true`, only published posts; when `false`, only drafts.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "title",
                "status",
                "published_at"
              ],
              "default": "created_at"
            }
          },
          {
            "$ref": "#/components/parameters/SortDirection"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of posts.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Post"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Posts"
        ],
        "summary": "Create a post",
        "operationId": "createPost",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostWriteRequired"
              },
              "example": {
                "title": "Add dark mode",
                "content": "Please support a dark theme.",
                "status": "pending",
                "tags": [
                  1,
                  2
                ],
                "board_id": 1
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Post created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/posts/{post}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PostId"
        }
      ],
      "get": {
        "tags": [
          "Posts"
        ],
        "summary": "Get a post",
        "operationId": "getPost",
        "responses": {
          "200": {
            "description": "The post, including votes and comments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Posts"
        ],
        "summary": "Update a post",
        "operationId": "updatePost",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostWrite"
              },
              "example": {
                "status": "in_progress"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Posts"
        ],
        "summary": "Delete a post (not supported)",
        "description": "Deleting posts via the public API is not supported and always returns `405 Method Not Allowed`.",
        "operationId": "deletePost",
        "responses": {
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/release-notes": {
      "get": {
        "tags": [
          "Release Notes"
        ],
        "summary": "List release notes",
        "operationId": "listReleaseNotes",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "name": "published",
            "in": "query",
            "description": "When `true`, only published release notes; when `false`, only drafts.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/TagFilter"
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "title",
                "published_at"
              ],
              "default": "created_at"
            }
          },
          {
            "$ref": "#/components/parameters/SortDirection"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of release notes.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ReleaseNote"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Release Notes"
        ],
        "summary": "Create a release note",
        "description": "Creates a release note. The `status` is set to `draft` and a `slug` is generated automatically from the title.",
        "operationId": "createReleaseNote",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseNoteWriteRequired"
              },
              "example": {
                "title": "Version 2.1.0",
                "content": "## What's New\n- Dark mode\n- Performance improvements",
                "tags": [
                  1
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Release note created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/release-notes/{release_note}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ReleaseNoteId"
        }
      ],
      "get": {
        "tags": [
          "Release Notes"
        ],
        "summary": "Get a release note",
        "operationId": "getReleaseNote",
        "responses": {
          "200": {
            "description": "The release note, including votes and comments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Release Notes"
        ],
        "summary": "Update a release note",
        "operationId": "updateReleaseNote",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReleaseNoteWrite"
              },
              "example": {
                "title": "Version 2.1.1"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated release note.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReleaseNote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Release Notes"
        ],
        "summary": "Delete a release note (not supported)",
        "description": "Deleting release notes via the public API is not supported and always returns `405 Method Not Allowed`.",
        "operationId": "deleteReleaseNote",
        "responses": {
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "operationId": "listContacts",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "$ref": "#/components/parameters/TagFilter"
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "name",
                "email",
                "mrr",
                "ltv"
              ],
              "default": "created_at"
            }
          },
          {
            "$ref": "#/components/parameters/SortDirection"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Contact"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Contacts"
        ],
        "summary": "Create or update a contact",
        "description": "Creates a contact. If a contact with the same email already exists for your client, that record is updated instead (returns `200`). Any properties beyond the documented fields are stored as custom metadata and returned flattened onto the contact object.",
        "operationId": "createContact",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactWriteRequired"
              },
              "example": {
                "name": "Jane Smith",
                "email": "jane@example.com",
                "mrr": 149.99,
                "ltv": 1800,
                "avatar": "https://example.com/jane.jpg",
                "tags": [
                  1,
                  2
                ],
                "plan": "pro"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Contact created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "200": {
            "description": "Existing contact updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/contacts/{contact}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ContactId"
        }
      ],
      "get": {
        "tags": [
          "Contacts"
        ],
        "summary": "Get a contact",
        "operationId": "getContact",
        "responses": {
          "200": {
            "description": "The contact, including tags and votes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Contacts"
        ],
        "summary": "Update a contact",
        "operationId": "updateContact",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactWrite"
              },
              "example": {
                "name": "Jane S.",
                "mrr": 199
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/testimonials": {
      "get": {
        "tags": [
          "Testimonials"
        ],
        "summary": "List testimonials",
        "description": "Lists testimonials. When `approved` is omitted, only pending (not-yet-approved) testimonials are returned.",
        "operationId": "listTestimonials",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "name": "approved",
            "in": "query",
            "description": "When `true`, only approved testimonials; when `false` or omitted, only pending.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "rating",
            "in": "query",
            "description": "Filter by exact rating (1–5).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "rating",
                "approved_at"
              ],
              "default": "id"
            }
          },
          {
            "$ref": "#/components/parameters/SortDirection"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of testimonials.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Testimonial"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Testimonials"
        ],
        "summary": "Create a testimonial",
        "operationId": "createTestimonial",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestimonialWriteRequired"
              },
              "example": {
                "description": "Amazing product, exceeded expectations!",
                "rating": 5,
                "username": "happy_customer",
                "contact_id": 42
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Testimonial created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Testimonial"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/testimonials/{testimonial}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TestimonialId"
        }
      ],
      "get": {
        "tags": [
          "Testimonials"
        ],
        "summary": "Get a testimonial",
        "operationId": "getTestimonial",
        "responses": {
          "200": {
            "description": "The testimonial, including votes and comments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Testimonial"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Testimonials"
        ],
        "summary": "Update a testimonial",
        "description": "Updates a testimonial. Set `approved_at` to approve it.",
        "operationId": "updateTestimonial",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestimonialWrite"
              },
              "example": {
                "approved_at": "2026-06-15T10:00:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated testimonial.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Testimonial"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Testimonials"
        ],
        "summary": "Delete a testimonial (not supported)",
        "description": "Deleting testimonials via the public API is not supported and always returns `405 Method Not Allowed`.",
        "operationId": "deleteTestimonial",
        "responses": {
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/tags": {
      "get": {
        "tags": [
          "Tags"
        ],
        "summary": "List tags",
        "description": "Lists tags with usage counts (`posts_count`, `contacts_count`).",
        "operationId": "listTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/Search"
          },
          {
            "name": "sort_by",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "title",
                "created_at",
                "updated_at"
              ],
              "default": "title"
            }
          },
          {
            "name": "sort_direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "$ref": "#/components/parameters/PerPage"
          },
          {
            "$ref": "#/components/parameters/Page"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of tags.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Pagination"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Tag"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "Tags"
        ],
        "summary": "Create a tag",
        "operationId": "createTag",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Unique per client."
                  }
                }
              },
              "example": {
                "title": "feature"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tag created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tag"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      }
    },
    "/tags/{tag}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TagId"
        }
      ],
      "patch": {
        "tags": [
          "Tags"
        ],
        "summary": "Update a tag",
        "operationId": "updateTag",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title"
                ],
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 255,
                    "description": "Unique per client."
                  }
                }
              },
              "example": {
                "title": "enhancement"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated tag.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tag"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          }
        }
      },
      "delete": {
        "tags": [
          "Tags"
        ],
        "summary": "Delete a tag",
        "description": "Deletes a tag. Any associations to posts and contacts are detached first.",
        "operationId": "deleteTag",
        "responses": {
          "204": {
            "description": "Tag deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Personal API token issued by Feedback Pilgrim (Laravel Sanctum). Send it as `Authorization: Bearer <token>`."
      }
    },
    "parameters": {
      "Search": {
        "name": "search",
        "in": "query",
        "description": "Partial-match search.",
        "schema": {
          "type": "string"
        }
      },
      "TagFilter": {
        "name": "tag",
        "in": "query",
        "description": "Filter by exact tag title.",
        "schema": {
          "type": "string"
        }
      },
      "SortDirection": {
        "name": "sort_direction",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "description": "Items per page.",
        "schema": {
          "type": "integer",
          "default": 15,
          "minimum": 1,
          "maximum": 100
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "description": "Page number.",
        "schema": {
          "type": "integer",
          "default": 1,
          "minimum": 1
        }
      },
      "PostId": {
        "name": "post",
        "in": "path",
        "required": true,
        "description": "Post ID.",
        "schema": {
          "type": "integer"
        }
      },
      "ReleaseNoteId": {
        "name": "release_note",
        "in": "path",
        "required": true,
        "description": "Release note ID.",
        "schema": {
          "type": "integer"
        }
      },
      "ContactId": {
        "name": "contact",
        "in": "path",
        "required": true,
        "description": "Contact ID.",
        "schema": {
          "type": "integer"
        }
      },
      "TestimonialId": {
        "name": "testimonial",
        "in": "path",
        "required": true,
        "description": "Testimonial ID.",
        "schema": {
          "type": "integer"
        }
      },
      "TagId": {
        "name": "tag",
        "in": "path",
        "required": true,
        "description": "Tag ID.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "Unauthenticated."
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found within your client scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "No query results for model."
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Operation not supported.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "message": "Method not allowed"
            }
          }
        }
      },
      "ValidationError": {
        "description": "The request failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "example": {
              "message": "The title field is required.",
              "errors": {
                "title": [
                  "The title field is required."
                ]
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {}
          },
          "links": {
            "type": "object",
            "properties": {
              "first": {
                "type": "string",
                "nullable": true
              },
              "last": {
                "type": "string",
                "nullable": true
              },
              "prev": {
                "type": "string",
                "nullable": true
              },
              "next": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "current_page": {
                "type": "integer"
              },
              "from": {
                "type": "integer",
                "nullable": true
              },
              "last_page": {
                "type": "integer"
              },
              "path": {
                "type": "string"
              },
              "per_page": {
                "type": "integer"
              },
              "to": {
                "type": "integer",
                "nullable": true
              },
              "total": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "client_id": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "posts_count": {
            "type": "integer",
            "description": "Present on list responses only."
          },
          "contacts_count": {
            "type": "integer",
            "description": "Present on list responses only."
          }
        }
      },
      "Vote": {
        "type": "object",
        "description": "A vote on a post, release note, or testimonial.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "integer"
          },
          "contact_id": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "Comment": {
        "type": "object",
        "description": "A comment on a post, release note, or testimonial.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "integer"
          },
          "content": {
            "type": "string"
          }
        }
      },
      "Post": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "client_id": {
            "type": "integer"
          },
          "stage_id": {
            "type": "integer",
            "nullable": true
          },
          "board_id": {
            "type": "integer",
            "nullable": true
          },
          "contact_id": {
            "type": "integer",
            "nullable": true
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "pending",
              "in_progress",
              "completed",
              null
            ]
          },
          "stage_position": {
            "type": "integer",
            "nullable": true
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "archived_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "contact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Contact"
              },
              {
                "type": "null"
              }
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          },
          "votes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vote"
            }
          },
          "comments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            },
            "description": "Present on single-post responses only."
          }
        }
      },
      "PostWrite": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "content": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "in_progress",
              "completed"
            ]
          },
          "board_id": {
            "type": "integer",
            "description": "Board ID belonging to your client. Defaults to the client's first board."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Tag IDs belonging to your client."
          }
        }
      },
      "PostWriteRequired": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PostWrite"
          },
          {
            "type": "object",
            "required": [
              "title",
              "content"
            ]
          }
        ]
      },
      "ReleaseNote": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "client_id": {
            "type": "integer"
          },
          "user_id": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "nullable": true,
            "description": "Set to `draft` on creation."
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          },
          "votes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vote"
            }
          },
          "comments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            }
          }
        }
      },
      "ReleaseNoteWrite": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "content": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Tag IDs belonging to your client."
          }
        }
      },
      "ReleaseNoteWriteRequired": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ReleaseNoteWrite"
          },
          {
            "type": "object",
            "required": [
              "title",
              "content"
            ]
          }
        ]
      },
      "Contact": {
        "type": "object",
        "description": "Custom metadata fields are returned flattened onto this object alongside the documented properties.",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "integer"
          },
          "uuid": {
            "type": "string"
          },
          "user_id": {
            "type": "integer",
            "nullable": true
          },
          "client_id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "email": {
            "type": "string",
            "format": "email",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "avatar": {
            "type": "string",
            "nullable": true
          },
          "mrr": {
            "type": "number",
            "nullable": true
          },
          "ltv": {
            "type": "number",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          },
          "votes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vote"
            }
          }
        }
      },
      "ContactWrite": {
        "type": "object",
        "additionalProperties": true,
        "description": "Unknown properties are stored as custom metadata on the contact.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255
          },
          "avatar": {
            "type": "string",
            "format": "uri"
          },
          "mrr": {
            "type": "number"
          },
          "ltv": {
            "type": "number"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Tag IDs belonging to your client."
          }
        }
      },
      "ContactWriteRequired": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ContactWrite"
          },
          {
            "type": "object",
            "required": [
              "name",
              "email"
            ]
          }
        ]
      },
      "Testimonial": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "client_id": {
            "type": "integer"
          },
          "contact_id": {
            "type": "integer",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string"
          },
          "rating": {
            "type": "integer",
            "nullable": true,
            "minimum": 1,
            "maximum": 5
          },
          "avatar_image": {
            "type": "string",
            "nullable": true
          },
          "username": {
            "type": "string",
            "nullable": true
          },
          "subtitle": {
            "type": "string",
            "nullable": true
          },
          "approved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "contact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Contact"
              },
              {
                "type": "null"
              }
            ]
          },
          "votes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vote"
            }
          },
          "comments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            },
            "description": "Present on single-testimonial responses only."
          }
        }
      },
      "TestimonialWrite": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "username": {
            "type": "string",
            "maxLength": 255
          },
          "approved_at": {
            "type": "string",
            "format": "date-time",
            "description": "Set to approve the testimonial. Update only."
          }
        }
      },
      "TestimonialWriteRequired": {
        "type": "object",
        "required": [
          "description",
          "username"
        ],
        "properties": {
          "description": {
            "type": "string"
          },
          "rating": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "username": {
            "type": "string",
            "maxLength": 255
          },
          "contact_id": {
            "type": "integer",
            "description": "ID of a contact belonging to your client."
          }
        }
      },
      "InitRequest": {
        "type": "object",
        "properties": {
          "contact": {
            "type": "object",
            "additionalProperties": true,
            "description": "Contact attributes. Requires `email` to create/update a contact. Unknown keys are stored as metadata.",
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "name": {
                "type": "string"
              },
              "mrr": {
                "type": "number"
              },
              "ltv": {
                "type": "number"
              },
              "avatar": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "releaseNotes": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional release-notes widget configuration."
          },
          "testimonials": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional testimonials widget configuration."
          }
        }
      },
      "InitResponse": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "auth_url": {
            "type": "string",
            "format": "uri",
            "description": "Returned when a contact with an email was supplied."
          }
        }
      }
    }
  }
}
