openapi: 3.1.0
info:
  title: Moneypenny Notion Service
  version: 1.0.0
  description: |
    Notion integration via Will's VPS. Cleaner than the old Cloud Run proxy.
    
    ## QUICK REFERENCE
    
    | Task | Endpoint | Method |
    |------|----------|--------|
    | Search workspace | /search | POST |
    | Query database rows | /database/{id}/query | POST |
    | Get database schema | /database/{id} | GET |
    | Create database | /database | POST |
    | Create page/row | /page | POST |
    | Get page | /page/{id} | GET |
    | Update page | /page/{id} | PATCH |
    | Archive page | /page/{id}/archive | POST |
    | Get page content | /page/{id}/content | GET |
    | Add content to page | /page/{id}/content | POST |
    | Get block | /block/{id} | GET |
    | Update block | /block/{id} | PATCH |
    | Delete block | /block/{id} | DELETE |
    
    ## SIMPLIFIED FORMATS
    
    This service accepts simplified property and block formats - you don't need 
    to construct the full Notion JSON. See each endpoint for examples.

servers:
  - url: https://claude.willcureton.com/notion

security:
  - apiKey: []

components:
  schemas: {}
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for authentication

paths:
  # ============================================================
  # SEARCH
  # ============================================================
  /search:
    post:
      operationId: search
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Search the Notion workspace
      description: |
        Find pages and databases by text search.
        
        Use this when you don't know which database something is in,
        or to find pages by name.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [query]
              properties:
                query:
                  type: string
                  description: Text to search for
                  example: "project notes"
                filter:
                  type: string
                  enum: [page, database]
                  description: Limit to pages or databases only
            examples:
              basic:
                summary: Simple search
                value:
                  query: "meeting notes"
              pages_only:
                summary: Search pages only
                value:
                  query: "client proposal"
                  filter: "page"
      responses:
        '200':
          description: Search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object

  # ============================================================
  # DATABASES
  # ============================================================
  /database/{database_id}/query:
    post:
      operationId: queryDatabase
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Query rows from a database
      description: Fetch rows from a database with optional filter and sorts. Auto-paginates. See filter/sorts fields for syntax.
      parameters:
        - name: database_id
          in: path
          required: true
          schema:
            type: string
          description: The database ID to query
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                filter:
                  type: object
                  description: Filter conditions
                sorts:
                  type: array
                  items:
                    type: object
                  description: Sort order
                  example:
                    - property: "Created"
                      direction: "descending"
                page_size:
                  type: integer
                  description: Results per page (max 100)
                  default: 100
            examples:
              all_rows:
                summary: Get all rows
                value: {}
              filtered:
                summary: Active items only
                value:
                  filter:
                    property: "Status"
                    select:
                      equals: "Active"
              sorted:
                summary: Sorted by date
                value:
                  sorts:
                    - property: "Due Date"
                      direction: "ascending"
      responses:
        '200':
          description: Query results
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object

  /database/{database_id}:
    get:
      operationId: getDatabase
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Get database schema
      description: |
        Retrieve a database's properties/columns and metadata.
        Use this to understand the structure before querying or creating pages.
      parameters:
        - name: database_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Database schema

  /database:
    post:
      operationId: createDatabase
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Create a new database
      description: |
        Create a database under a page.
        
        **Property types you can create:**
        - title (required - one per database)
        - rich_text
        - number
        - select
        - multi_select
        - date
        - checkbox
        - url
        - email
        - phone_number
        - people
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [parent_id, title, properties]
              properties:
                parent_id:
                  type: string
                  description: Page ID to create database under
                title:
                  type: string
                  description: Database name
                  example: "Task List"
                is_inline:
                  type: boolean
                  description: Embed in page (true) or full-page (false)
                  default: true
                properties:
                  type: object
                  description: |
                    Database schema. Must include one title property.
                    
                    Example:
                    ```json
                    {
                      "Name": {"title": {}},
                      "Status": {
                        "select": {
                          "options": [
                            {"name": "To Do", "color": "red"},
                            {"name": "Done", "color": "green"}
                          ]
                        }
                      },
                      "Due Date": {"date": {}},
                      "Priority": {"number": {}},
                      "Notes": {"rich_text": {}}
                    }
                    ```
            examples:
              simple:
                summary: Simple task database
                value:
                  parent_id: "abc123"
                  title: "Tasks"
                  properties:
                    Name:
                      title: {}
                    Status:
                      select:
                        options:
                          - name: "To Do"
                            color: "red"
                          - name: "Done"
                            color: "green"
                    Due Date:
                      date: {}
      responses:
        '200':
          description: Created database

  # ============================================================
  # PAGES
  # ============================================================
  /page:
    post:
      operationId: createPage
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Create a page or database row
      description: Create a page in a database (row) or under a page (subpage). Accepts simplified properties and children formats.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [parent_id]
              properties:
                parent_id:
                  type: string
                  description: Database ID (for row) or Page ID (for subpage)
                parent_type:
                  type: string
                  enum: [database, page]
                  description: Optional - auto-detected if not provided
                title:
                  type: string
                  description: Page title
                  example: "Meeting Notes"
                properties:
                  type: object
                  description: |
                    Property values. Can use simplified format:
                    - Strings for text/select
                    - Numbers for number fields
                    - Booleans for checkboxes
                    - Arrays for multi-select
                children:
                  type: array
                  description: Content blocks (simplified format accepted)
                  items:
                    oneOf:
                      - type: string
                      - type: object
            examples:
              database_row:
                summary: Add row to database
                value:
                  parent_id: "abc123-database-id"
                  title: "New Task"
                  properties:
                    Status: "To Do"
                    Priority: 3
                    Tags: ["backend", "urgent"]
              with_content:
                summary: Page with body content
                value:
                  parent_id: "abc123-database-id"
                  title: "Meeting Notes"
                  properties:
                    Type: "Meeting"
                    Date: "2025-01-15"
                  children:
                    - type: heading_2
                      text: "Attendees"
                    - type: bulleted_list_item
                      text: "Alice"
                    - type: bulleted_list_item
                      text: "Bob"
                    - type: heading_2
                      text: "Action Items"
                    - type: to_do
                      text: "Send follow-up email"
                      checked: false
      responses:
        '200':
          description: Created page

  /page/{page_id}:
    get:
      operationId: getPage
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Get page metadata
      description: Retrieve page properties and metadata
      parameters:
        - name: page_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Page object

    patch:
      operationId: updatePage
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Update page properties
      description: |
        Update one or more properties on a page.
        
        Simplified format accepted:
        ```json
        {
          "properties": {
            "Status": "Done",
            "Completed": true
          }
        }
        ```
      parameters:
        - name: page_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                properties:
                  type: object
                  description: Properties to update (simplified format OK)
                archived:
                  type: boolean
                  description: Set true to archive
                icon:
                  type: object
                  description: Page icon
                  example:
                    type: "emoji"
                    emoji: "🎯"
                cover:
                  type: object
                  description: Cover image
            examples:
              update_status:
                summary: Update status
                value:
                  properties:
                    Status: "Done"
                    Completed: true
              archive:
                summary: Archive page
                value:
                  archived: true
      responses:
        '200':
          description: Updated page

  /page/{page_id}/archive:
    post:
      operationId: archivePage
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Archive a page
      description: Soft delete - moves to trash
      parameters:
        - name: page_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Archived page

  /page/{page_id}/content:
    get:
      operationId: getPageContent
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Get page body content
      description: Retrieve all blocks from a page
      parameters:
        - name: page_id
          in: path
          required: true
          schema:
            type: string
        - name: start_cursor
          in: query
          required: false
          schema:
            type: string
          description: Pagination cursor (optional, ignored)
      responses:
        '200':
          description: Page blocks

    post:
      operationId: appendPageContent
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Add content to a page
      description: |
        Append blocks to the end of a page.
        
        **SIMPLIFIED FORMAT:**
        ```json
        {
          "children": [
            "Plain text becomes paragraph",
            {"type": "heading_2", "text": "New Section"},
            {"type": "callout", "text": "Note!", "emoji": "📝"}
          ]
        }
        ```
      parameters:
        - name: page_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [children]
              properties:
                children:
                  type: array
                  items:
                    oneOf:
                      - type: string
                      - type: object
            examples:
              simple:
                summary: Add paragraphs
                value:
                  children:
                    - "First paragraph"
                    - "Second paragraph"
              structured:
                summary: Add structured content
                value:
                  children:
                    - type: divider
                    - type: heading_2
                      text: "Update"
                    - type: callout
                      text: "Completed the review"
                      emoji: "✅"
      responses:
        '200':
          description: Added blocks

  # ============================================================
  # BLOCKS
  # ============================================================
  /block/{block_id}:
    get:
      operationId: getBlock
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Get a block
      description: Retrieve a single block's content
      parameters:
        - name: block_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Block object

    patch:
      operationId: updateBlock
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Update a block
      description: Update block content. Use simplified format {type, text} or full Notion format.
      parameters:
        - name: block_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: string
                  description: Block type (paragraph, heading_2, to_do, etc.)
                text:
                  type: string
                  description: Text content for simplified format
                checked:
                  type: boolean
                  description: For to_do blocks
            examples:
              simplified:
                summary: Simplified format
                value:
                  type: "paragraph"
                  text: "Updated text"
              checkbox:
                summary: Check a to-do
                value:
                  type: "to_do"
                  text: "Task complete"
                  checked: true
      responses:
        '200':
          description: Updated block

    delete:
      operationId: deleteBlock
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Delete a block
      description: Archive (soft delete) a block
      parameters:
        - name: block_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Deleted block

  /block/{block_id}/children:
    get:
      operationId: getBlockChildren
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Get block children
      description: Get nested content inside a block (toggle, etc)
      parameters:
        - name: block_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Child blocks

  # ============================================================
  # USERS
  # ============================================================
  /users:
    get:
      operationId: listUsers
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: List workspace users
      responses:
        '200':
          description: User list

  /users/me:
    get:
      operationId: getMe
      x-openai-isConsequential: false
      x-openai-is-consequential: false
      x-openai-isSystemAction: true
      summary: Get bot user info
      responses:
        '200':
          description: Bot user
