[object Object]

← back to Cli Printing Press

feat(cli): add Quo (formerly OpenPhone) catalog entry and OpenAPI spec (#1597)

1a243810db5ff3c379385271b67030beb1a6e556 · 2026-05-19 17:20:45 -0500 · HotFix Ops

Add catalog/quo.yaml plus an in-repo OpenAPI spec for Quo (formerly
OpenPhone), so the Printing Press can generate a printed CLI for the
Quo business-phone API.

Spec covers 18 operations across 14 paths in 6 resource groups:
contacts, conversations, phone-numbers, users, messages, calls. Auth
is via a plain API key in the Authorization header (non-Bearer),
captured in the spec's securitySchemes block.

Provenance:
- tier: community. Quo does not publish a vendor OpenAPI document,
  so this spec is community-curated from their public docs. Matches
  the kayak / plaid / telegram / sentry pattern.
- info.x-spec-provenance records source: official-docs, the docs URL,
  and a confirmed_quirks list. Every path is derived from the
  official Quo API reference and cross-verified by live testing
  against api.openphone.com/v1. No endpoint relies on MCP tool
  schemas or undocumented probing as its sole source of truth.

Quo API quirks captured in x-spec-provenance.confirmed_quirks and
inline schema descriptions:
- POST /messages: 'to' is an array of E.164 strings on both the
  request body and the Message response schema. The API rejects a
  bare string and returns 'to' as an array even for single-recipient
  messages.
- POST /messages: the request body uses 'content' for the message
  text while the Message response returns the same text under 'body'.
  Real Quo API naming asymmetry, not a curator typo.

POST /messages requestBody wraps the schema in oneOf so the parser
emits a --body-json fallback (and a body_json MCP input) instead of
per-field typed flags. The generator's typed body path serializes
array fields as JSON-strings, which the Quo API rejects; the
--body-json surface lets agents and humans send the array verbatim
("to": ["+155..."]) without round-tripping through a string.
Verified by regenerating and reading
internal/cli/messages_send.go + internal/mcp/tools.go.

testdata/golden/expected/catalog-list/stdout.txt: insert the new Quo
row in social-and-messaging (alphabetical between front and telegram).

No generator code, template, scorer, MCP, or pipeline code changes.

Verification:
- go build -o ./printing-press ./cmd/printing-press
- go test ./... (all packages pass)
- go fmt ./... (no Go files touched)
- go vet ./... (clean)
- scripts/golden.sh verify (20/20 cases pass)
- ./printing-press catalog show quo renders the new tier, provenance,
  and notes
- ./printing-press catalog list places Quo in
  social-and-messaging between front and telegram
- ./printing-press generate --spec catalog/specs/quo-spec.yaml emits
  the expected --body-json fallback warning for POST /messages and
  produces a buildable printed CLI that PASSes go mod tidy,
  govulncheck, go vet, go build, --help, version, and doctor.

Closes #782 (superseded by #1597).

Co-authored-by: ninjaforhire <andrew@mightyphotobooths.com>

Files touched

Diff

commit 1a243810db5ff3c379385271b67030beb1a6e556
Author: HotFix Ops <116854618+ninjaforhire@users.noreply.github.com>
Date:   Tue May 19 17:20:45 2026 -0500

    feat(cli): add Quo (formerly OpenPhone) catalog entry and OpenAPI spec (#1597)
    
    Add catalog/quo.yaml plus an in-repo OpenAPI spec for Quo (formerly
    OpenPhone), so the Printing Press can generate a printed CLI for the
    Quo business-phone API.
    
    Spec covers 18 operations across 14 paths in 6 resource groups:
    contacts, conversations, phone-numbers, users, messages, calls. Auth
    is via a plain API key in the Authorization header (non-Bearer),
    captured in the spec's securitySchemes block.
    
    Provenance:
    - tier: community. Quo does not publish a vendor OpenAPI document,
      so this spec is community-curated from their public docs. Matches
      the kayak / plaid / telegram / sentry pattern.
    - info.x-spec-provenance records source: official-docs, the docs URL,
      and a confirmed_quirks list. Every path is derived from the
      official Quo API reference and cross-verified by live testing
      against api.openphone.com/v1. No endpoint relies on MCP tool
      schemas or undocumented probing as its sole source of truth.
    
    Quo API quirks captured in x-spec-provenance.confirmed_quirks and
    inline schema descriptions:
    - POST /messages: 'to' is an array of E.164 strings on both the
      request body and the Message response schema. The API rejects a
      bare string and returns 'to' as an array even for single-recipient
      messages.
    - POST /messages: the request body uses 'content' for the message
      text while the Message response returns the same text under 'body'.
      Real Quo API naming asymmetry, not a curator typo.
    
    POST /messages requestBody wraps the schema in oneOf so the parser
    emits a --body-json fallback (and a body_json MCP input) instead of
    per-field typed flags. The generator's typed body path serializes
    array fields as JSON-strings, which the Quo API rejects; the
    --body-json surface lets agents and humans send the array verbatim
    ("to": ["+155..."]) without round-tripping through a string.
    Verified by regenerating and reading
    internal/cli/messages_send.go + internal/mcp/tools.go.
    
    testdata/golden/expected/catalog-list/stdout.txt: insert the new Quo
    row in social-and-messaging (alphabetical between front and telegram).
    
    No generator code, template, scorer, MCP, or pipeline code changes.
    
    Verification:
    - go build -o ./printing-press ./cmd/printing-press
    - go test ./... (all packages pass)
    - go fmt ./... (no Go files touched)
    - go vet ./... (clean)
    - scripts/golden.sh verify (20/20 cases pass)
    - ./printing-press catalog show quo renders the new tier, provenance,
      and notes
    - ./printing-press catalog list places Quo in
      social-and-messaging between front and telegram
    - ./printing-press generate --spec catalog/specs/quo-spec.yaml emits
      the expected --body-json fallback warning for POST /messages and
      produces a buildable printed CLI that PASSes go mod tidy,
      govulncheck, go vet, go build, --help, version, and doctor.
    
    Closes #782 (superseded by #1597).
    
    Co-authored-by: ninjaforhire <andrew@mightyphotobooths.com>
---
 catalog/quo.yaml                                 |  22 +
 catalog/specs/quo-spec.yaml                      | 889 +++++++++++++++++++++++
 testdata/golden/expected/catalog-list/stdout.txt |   1 +
 3 files changed, 912 insertions(+)

diff --git a/catalog/quo.yaml b/catalog/quo.yaml
new file mode 100644
index 00000000..661a9a87
--- /dev/null
+++ b/catalog/quo.yaml
@@ -0,0 +1,22 @@
+name: quo
+display_name: Quo
+description: Business phone system API for contacts, SMS messaging, call transcripts, and inbox management (formerly OpenPhone)
+category: social-and-messaging
+spec_url: https://raw.githubusercontent.com/mvanhorn/cli-printing-press/main/catalog/specs/quo-spec.yaml
+spec_format: yaml
+openapi_version: "3.0"
+tier: community
+spec_source: docs
+auth_required: true
+client_pattern: rest
+http_transport: standard
+verified_date: "2026-05-09"
+homepage: https://www.quo.com/docs/mdx/api-reference/introduction
+notes: >
+  Quo (formerly OpenPhone) business phone system. API lives at api.openphone.com/v1
+  (legacy domain). Auth via plain API key in Authorization header (not Bearer).
+  All endpoints in this spec are derived from the official Quo API docs
+  (https://www.quo.com/docs/mdx/api-reference) and cross-verified by live
+  testing against the production API. Transcripts require Business plan.
+  No MMS support via API. Credit-based pricing for API messaging. Quo does
+  not publish a vendor OpenAPI document, so this spec is community-curated.
diff --git a/catalog/specs/quo-spec.yaml b/catalog/specs/quo-spec.yaml
new file mode 100644
index 00000000..1fa7433a
--- /dev/null
+++ b/catalog/specs/quo-spec.yaml
@@ -0,0 +1,889 @@
+openapi: "3.0.3"
+info:
+  title: Quo API
+  description: |
+    Quo (formerly OpenPhone) REST API for business phone system management.
+    Supports contacts, phone numbers (inboxes), users, SMS messaging, and
+    call transcripts. Auth via API key in the Authorization header.
+  version: "1.0.0"
+  contact:
+    name: Quo
+  x-display-name: Quo
+  x-website: https://www.quo.com
+  x-spec-provenance:
+    source: official-docs
+    docs_url: https://www.quo.com/docs/mdx/api-reference
+    notes: >
+      All paths and operations in this spec are derived from the official Quo
+      API reference docs. Each endpoint was cross-verified by live testing
+      against the production API (api.openphone.com/v1). Quo does not publish
+      a vendor OpenAPI document; this spec is community-curated from their
+      public docs. No endpoint in this spec relies on MCP tool schemas or
+      undocumented probing as its sole source of truth.
+    confirmed_quirks:
+      - "POST /messages: `to` is an array of E.164 strings, not a single string. The API rejects a bare string and requires `[\"+15555555556\"]` even for a single recipient. Confirmed by live testing against production."
+      - "POST /messages: request body uses `content` for the message text; the Message response schema returns the same text under `body`. This request/response naming asymmetry is real in the Quo API, not a curator typo."
+
+servers:
+  - url: https://api.openphone.com/v1
+    description: Quo production API (legacy OpenPhone domain)
+
+tags:
+  - name: contacts
+    description: Contact management (CRUD)
+  - name: phone-numbers
+    description: Phone number / inbox listing
+  - name: users
+    description: Workspace user management
+  - name: conversations
+    description: Conversation threads (list and manage)
+  - name: messages
+    description: SMS messaging (send and retrieve)
+  - name: calls
+    description: Call history and transcripts
+
+components:
+  securitySchemes:
+    apiKey:
+      type: apiKey
+      in: header
+      name: Authorization
+      x-auth-env-vars:
+        - QUO_API_KEY
+      x-auth-key-url: https://www.quo.com/docs/mdx/api-reference/authentication
+      x-auth-title: Quo API Key
+      x-auth-description: API key from Quo workspace settings (owner/admin only)
+
+  schemas:
+    Contact:
+      type: object
+      properties:
+        id:
+          type: string
+          description: Unique contact ID
+        firstName:
+          type: string
+        lastName:
+          type: string
+        company:
+          type: string
+        role:
+          type: string
+        emails:
+          type: array
+          items:
+            type: object
+            properties:
+              value:
+                type: string
+                format: email
+              type:
+                type: string
+        phoneNumbers:
+          type: array
+          items:
+            type: object
+            properties:
+              value:
+                type: string
+              type:
+                type: string
+        customFields:
+          type: array
+          items:
+            type: object
+            properties:
+              key:
+                type: string
+              value:
+                type: string
+        source:
+          type: string
+        createdAt:
+          type: string
+          format: date-time
+        updatedAt:
+          type: string
+          format: date-time
+
+    PhoneNumber:
+      type: object
+      properties:
+        id:
+          type: string
+          description: Phone number ID (PN... format)
+        number:
+          type: string
+          description: E.164 formatted number
+        formattedNumber:
+          type: string
+        name:
+          type: string
+          description: Display name for this inbox
+        users:
+          type: array
+          items:
+            type: object
+            properties:
+              id:
+                type: string
+              firstName:
+                type: string
+              lastName:
+                type: string
+
+    User:
+      type: object
+      properties:
+        id:
+          type: string
+          description: User ID (US... format)
+        firstName:
+          type: string
+        lastName:
+          type: string
+        email:
+          type: string
+          format: email
+        role:
+          type: string
+          enum: [owner, admin, member]
+
+    Message:
+      type: object
+      properties:
+        id:
+          type: string
+        object:
+          type: string
+          enum: [message]
+        from:
+          type: string
+          description: Sender phone number (E.164)
+        to:
+          type: array
+          description: |
+            Recipient phone numbers (E.164). The Quo API returns this as an
+            array even for single-recipient messages — confirmed by live
+            testing against production.
+          items:
+            type: string
+            format: E.164
+        body:
+          type: string
+        direction:
+          type: string
+          enum: [incoming, outgoing]
+        status:
+          type: string
+        createdAt:
+          type: string
+          format: date-time
+        userId:
+          type: string
+        phoneNumberId:
+          type: string
+        conversationId:
+          type: string
+
+    Call:
+      type: object
+      properties:
+        id:
+          type: string
+        object:
+          type: string
+          enum: [call]
+        from:
+          type: string
+        to:
+          type: string
+        direction:
+          type: string
+          enum: [incoming, outgoing]
+        duration:
+          type: integer
+          description: Call duration in seconds
+        status:
+          type: string
+        userId:
+          type: string
+        phoneNumberId:
+          type: string
+        conversationId:
+          type: string
+        createdAt:
+          type: string
+          format: date-time
+
+    Transcript:
+      type: object
+      properties:
+        id:
+          type: string
+        callId:
+          type: string
+        text:
+          type: string
+        segments:
+          type: array
+          items:
+            type: object
+            properties:
+              speaker:
+                type: string
+              text:
+                type: string
+              startTime:
+                type: number
+              endTime:
+                type: number
+
+    Conversation:
+      type: object
+      properties:
+        id:
+          type: string
+          description: Conversation ID (CN... format)
+        phoneNumberId:
+          type: string
+          description: Phone number ID this conversation belongs to
+        participants:
+          type: array
+          items:
+            type: string
+          description: Participant phone numbers (E.164)
+        name:
+          type: string
+          nullable: true
+        assignedTo:
+          type: string
+          nullable: true
+        lastActivityAt:
+          type: string
+          format: date-time
+        lastActivityId:
+          type: string
+        snoozedUntil:
+          type: string
+          format: date-time
+          nullable: true
+        mutedUntil:
+          type: string
+          format: date-time
+          nullable: true
+        createdAt:
+          type: string
+          format: date-time
+        updatedAt:
+          type: string
+          format: date-time
+
+    PaginatedResponse:
+      type: object
+      properties:
+        data:
+          type: array
+          items: {}
+        nextPageToken:
+          type: string
+          description: Token for next page of results
+        totalCount:
+          type: integer
+
+security:
+  - apiKey: []
+
+paths:
+  /contacts:
+    get:
+      operationId: listContacts
+      summary: List contacts
+      description: List contacts in the workspace with optional filtering by external IDs or sources. Supports pagination.
+      tags: [contacts]
+      parameters:
+        - name: pageToken
+          in: query
+          description: Pagination token from previous response
+          schema:
+            type: string
+        - name: maxResults
+          in: query
+          description: Maximum contacts per page (1-50)
+          schema:
+            type: integer
+            default: 10
+            minimum: 1
+            maximum: 50
+        - name: externalIds
+          in: query
+          description: Filter by external identifiers (comma-separated)
+          schema:
+            type: string
+        - name: sources
+          in: query
+          description: Filter by contact source (comma-separated)
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Paginated list of contacts
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/components/schemas/PaginatedResponse"
+                  - type: object
+                    properties:
+                      data:
+                        type: array
+                        items:
+                          $ref: "#/components/schemas/Contact"
+    post:
+      operationId: createContact
+      summary: Create a contact
+      description: Create a new contact in the workspace. Only firstName is required.
+      tags: [contacts]
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              required:
+                - firstName
+              properties:
+                firstName:
+                  type: string
+                lastName:
+                  type: string
+                company:
+                  type: string
+                role:
+                  type: string
+                email:
+                  type: string
+                  format: email
+                phoneNumber:
+                  type: string
+                  description: E.164 format
+      responses:
+        "201":
+          description: Created contact
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Contact"
+
+  /contacts/{contactId}:
+    get:
+      operationId: getContact
+      summary: Get a contact
+      description: Retrieve a single contact by ID, including custom fields.
+      tags: [contacts]
+      parameters:
+        - name: contactId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Contact details
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Contact"
+    patch:
+      operationId: updateContact
+      summary: Update a contact
+      description: Update fields on an existing contact. Omit fields to leave unchanged; set to null to clear.
+      tags: [contacts]
+      parameters:
+        - name: contactId
+          in: path
+          required: true
+          schema:
+            type: string
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                firstName:
+                  type: string
+                lastName:
+                  type: string
+                company:
+                  type: string
+                role:
+                  type: string
+                email:
+                  type: string
+                  format: email
+                phoneNumber:
+                  type: string
+      responses:
+        "200":
+          description: Updated contact
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Contact"
+    delete:
+      operationId: deleteContact
+      summary: Delete a contact
+      description: Permanently delete a contact by ID.
+      tags: [contacts]
+      parameters:
+        - name: contactId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "204":
+          description: Contact deleted
+
+  /phone-numbers:
+    get:
+      operationId: listPhoneNumbers
+      summary: List phone numbers (inboxes)
+      description: List phone numbers in the workspace with assigned users. Use to discover inbox IDs for messaging and call transcript queries.
+      tags: [phone-numbers]
+      parameters:
+        - name: userId
+          in: query
+          description: Filter by user ID (US... format)
+          schema:
+            type: string
+      responses:
+        "200":
+          description: List of phone numbers
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  data:
+                    type: array
+                    items:
+                      $ref: "#/components/schemas/PhoneNumber"
+
+  /phone-numbers/{phoneNumberId}:
+    get:
+      operationId: getPhoneNumber
+      summary: Get a phone number
+      description: Retrieve details for a specific phone number.
+      tags: [phone-numbers]
+      parameters:
+        - name: phoneNumberId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Phone number details
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/PhoneNumber"
+
+  /users:
+    get:
+      operationId: listUsers
+      summary: List workspace users
+      description: List users (members) in the workspace. Returns ID, name, email, and role. Supports pagination.
+      tags: [users]
+      parameters:
+        - name: maxResults
+          in: query
+          description: Maximum users per page (1-50)
+          schema:
+            type: integer
+            default: 10
+            minimum: 1
+            maximum: 50
+        - name: pageToken
+          in: query
+          description: Pagination token
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Paginated list of users
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/components/schemas/PaginatedResponse"
+                  - type: object
+                    properties:
+                      data:
+                        type: array
+                        items:
+                          $ref: "#/components/schemas/User"
+
+  /users/{userId}:
+    get:
+      operationId: getUser
+      summary: Get a user
+      description: Retrieve details for a specific workspace user.
+      tags: [users]
+      parameters:
+        - name: userId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: User details
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/User"
+
+  /conversations:
+    get:
+      operationId: listConversations
+      summary: List conversations
+      description: |
+        List conversation threads for one or more phone numbers. Each conversation
+        has participants (external phone numbers) and tracks last activity. Use this
+        to discover conversations before fetching messages or calls per conversation.
+      tags: [conversations]
+      parameters:
+        - name: phoneNumbers[]
+          in: query
+          required: true
+          description: Phone number IDs to list conversations for (array)
+          schema:
+            type: array
+            items:
+              type: string
+        - name: updatedAfter
+          in: query
+          description: ISO 8601 datetime — only conversations updated after this time
+          schema:
+            type: string
+            format: date-time
+        - name: maxResults
+          in: query
+          description: Maximum conversations per page (default 50, max 100)
+          schema:
+            type: integer
+            default: 50
+            maximum: 100
+        - name: pageToken
+          in: query
+          description: Pagination token from previous response
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Paginated list of conversations
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/components/schemas/PaginatedResponse"
+                  - type: object
+                    properties:
+                      data:
+                        type: array
+                        items:
+                          $ref: "#/components/schemas/Conversation"
+
+  /messages:
+    get:
+      operationId: listMessages
+      summary: List messages
+      description: |
+        Fetch message history for an inbox conversation. Requires both phoneNumberId
+        and participants[] (array of external phone numbers from the conversation).
+        Use /conversations first to discover participant phone numbers.
+      tags: [messages]
+      parameters:
+        - name: phoneNumberId
+          in: query
+          required: true
+          description: Inbox phone number ID (PN... format)
+          schema:
+            type: string
+        - name: participants[]
+          in: query
+          required: true
+          description: Participant phone numbers (E.164 array). Required — get from /conversations.
+          schema:
+            type: array
+            items:
+              type: string
+        - name: maxResults
+          in: query
+          description: Maximum messages (default 50, max 100)
+          schema:
+            type: integer
+            default: 50
+            maximum: 100
+        - name: createdAfter
+          in: query
+          description: ISO 8601 datetime filter
+          schema:
+            type: string
+            format: date-time
+        - name: createdBefore
+          in: query
+          description: ISO 8601 datetime filter
+          schema:
+            type: string
+            format: date-time
+        - name: pageToken
+          in: query
+          description: Pagination token
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Message history
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/components/schemas/PaginatedResponse"
+                  - type: object
+                    properties:
+                      data:
+                        type: array
+                        items:
+                          $ref: "#/components/schemas/Message"
+    post:
+      operationId: sendMessage
+      summary: Send an SMS message
+      description: |
+        Send an SMS from a Quo inbox to a recipient. The from number must belong
+        to the workspace. Recipient must be in E.164 format.
+      tags: [messages]
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              # Wrapped in oneOf so the Printing Press parser falls back to a
+              # --body-json flag (and a body_json MCP input) instead of
+              # per-field typed flags. Required because Quo's `to` field is an
+              # array of E.164 strings (live-tested), and the generator's typed
+              # flag path serializes array body fields as a JSON-string rather
+              # than a JSON array, which the API rejects. The --body-json
+              # surface lets agents and humans send the array verbatim.
+              oneOf:
+                - type: object
+                  required:
+                    - from
+                    - to
+                    - content
+                  properties:
+                    from:
+                      type: string
+                      description: Quo inbox number (E.164 or phone number ID)
+                    to:
+                      type: array
+                      description: |
+                        Recipient phone numbers (E.164). The Quo API accepts an array
+                        even when sending to a single recipient; live-tested against
+                        production. A single-string form is not accepted by the API.
+                      items:
+                        type: string
+                        format: E.164
+                    content:
+                      type: string
+                      description: |
+                        Message body for the outbound SMS. Note: the Message response
+                        schema exposes the same text under `body`, not `content` —
+                        this request/response naming asymmetry mirrors the Quo API and
+                        is intentional, not a curator typo.
+      responses:
+        "201":
+          description: Message sent
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Message"
+
+  /messages/{messageId}:
+    get:
+      operationId: getMessage
+      summary: Get a message
+      description: Retrieve a single message by ID.
+      tags: [messages]
+      parameters:
+        - name: messageId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Message details
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Message"
+
+  /calls:
+    get:
+      operationId: listCalls
+      summary: List calls
+      description: |
+        Fetch call history for an inbox conversation. Requires both phoneNumberId
+        and participants[] (array of external phone numbers). Use /conversations
+        first to discover participant phone numbers.
+      tags: [calls]
+      parameters:
+        - name: phoneNumberId
+          in: query
+          required: true
+          description: Inbox phone number ID (PN... format)
+          schema:
+            type: string
+        - name: participants[]
+          in: query
+          required: true
+          description: Participant phone numbers (E.164 array). Required — get from /conversations.
+          schema:
+            type: array
+            items:
+              type: string
+        - name: maxResults
+          in: query
+          description: Maximum results (default 50, max 100)
+          schema:
+            type: integer
+            default: 50
+            maximum: 100
+        - name: createdAfter
+          in: query
+          description: ISO 8601 datetime filter
+          schema:
+            type: string
+            format: date-time
+        - name: createdBefore
+          in: query
+          description: ISO 8601 datetime filter
+          schema:
+            type: string
+            format: date-time
+        - name: pageToken
+          in: query
+          description: Pagination token
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Call history
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: "#/components/schemas/PaginatedResponse"
+                  - type: object
+                    properties:
+                      data:
+                        type: array
+                        items:
+                          $ref: "#/components/schemas/Call"
+
+  /calls/{callId}:
+    get:
+      operationId: getCall
+      summary: Get a call
+      description: Retrieve details for a specific call.
+      tags: [calls]
+      parameters:
+        - name: callId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Call details
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Call"
+
+  /calls/{callId}/transcription:
+    get:
+      operationId: getCallTranscript
+      summary: Get call transcript
+      description: Retrieve the transcript for a call (requires Quo Business plan).
+      tags: [calls]
+      parameters:
+        - name: callId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Call transcript
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Transcript"
+
+  /calls/{callId}/voicemail:
+    get:
+      operationId: getCallVoicemail
+      summary: Get call voicemail
+      description: Retrieve voicemail audio URL for a call.
+      tags: [calls]
+      parameters:
+        - name: callId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Voicemail data
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  url:
+                    type: string
+                    format: uri
+                  duration:
+                    type: integer
+
+  /calls/{callId}/recordings:
+    get:
+      operationId: getCallRecording
+      summary: Get call recording
+      description: Retrieve recording URL for a call.
+      tags: [calls]
+      parameters:
+        - name: callId
+          in: path
+          required: true
+          schema:
+            type: string
+      responses:
+        "200":
+          description: Recording data
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  url:
+                    type: string
+                    format: uri
+                  duration:
+                    type: integer
\ No newline at end of file
diff --git a/testdata/golden/expected/catalog-list/stdout.txt b/testdata/golden/expected/catalog-list/stdout.txt
index cff87db9..e87f11b8 100644
--- a/testdata/golden/expected/catalog-list/stdout.txt
+++ b/testdata/golden/expected/catalog-list/stdout.txt
@@ -44,6 +44,7 @@ sales-and-crm:
 social-and-messaging:
   discord              Chat and community platform API
   front                Customer communication platform API
+  quo                  Business phone system API for contacts, SMS messaging, call transcripts, and inbox management (formerly OpenPhone)
   telegram             Telegram Bot API for building bots - send messages, manage chats, webhooks, stickers
   twilio               Communication APIs for SMS, voice, and messaging
 

← 015478bf feat(cli): detect Auth0 SPA in-memory auth + emit CDP extrac  ·  back to Cli Printing Press  ·  fix(cli): handle binary-only response endpoints (Accept + ba d8178077 →