openapi: 3.1.0
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
info:
  title: Medal Social API
  version: 1.1.7
  description: Public API contract for the Medal Social TypeScript SDK.
  license:
    name: Apache-2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
  - url: https://io.medalsocial.com
    description: Medal Social production API
security:
  - bearerAuth: []
tags:
  - name: Posts
    description: Create, schedule, publish, and inspect posts.
  - name: Emails
    description: Send transactional email and inspect templates.
  - name: Contacts
    description: Manage workspace CRM contacts.
  - name: Deals
    description: Manage sponsorship and brand deals.
  - name: GDPR
    description: Manage exports, consent records, and cookie consent.
  - name: Workspaces
    description: Inspect workspaces accessible to the current credential.
  - name: Helpdesk
    description: Read helpdesk conversations, reply, and manage assignment/status.
  - name: Webhooks
    description: Manage webhook endpoints and inspect their deliveries.
  - name: Capabilities
    description: >-
      Mint short-lived capability confirmation tokens for confirmable write
      routes.
  - name: Channels
    description: >-
      Partner channel connect — mint hosted connect links and manage the
      resulting channel connections.
  - name: Scan
    description: >-
      Company & website scans (Nettsjekk) — queue a scan by URL, organisation
      number, or company name, poll the result, and search the Norwegian
      company registry.
paths:
  /api/v1/posts:
    get:
      tags: [Posts]
      operationId: listPosts
      summary: List posts
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: status
          in: query
          schema:
            type: string
        - name: type
          in: query
          schema:
            $ref: "#/components/schemas/PostType"
      responses:
        "200":
          description: Posts page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_Post"
        default:
          $ref: "#/components/responses/ApiError"
    post:
      tags: [Posts]
      operationId: createPost
      summary: Create a post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePostInput"
      responses:
        "200":
          description: Created post reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_PostCreateResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/posts/channels:
    get:
      tags: [Posts]
      operationId: listPostChannels
      summary: List connected post channels
      responses:
        "200":
          description: Connected channels.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ChannelArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/posts/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Posts]
      operationId: getPost
      summary: Get post detail
      responses:
        "200":
          description: Post detail.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_PostDetail"
        default:
          $ref: "#/components/responses/ApiError"
    patch:
      tags: [Posts]
      operationId: updatePost
      summary: Update a draft post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePostInput"
      responses:
        "200":
          description: Update result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_Success"
        default:
          $ref: "#/components/responses/ApiError"
    delete:
      tags: [Posts]
      operationId: deletePost
      summary: Delete a post
      responses:
        "200":
          description: Delete result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_Success"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/posts/{id}/schedule:
    parameters:
      - $ref: "#/components/parameters/Id"
    post:
      tags: [Posts]
      operationId: schedulePost
      summary: Schedule a post
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SchedulePostInput"
      responses:
        "200":
          description: Schedule result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ScheduleResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/posts/{id}/publish:
    parameters:
      - $ref: "#/components/parameters/Id"
    post:
      tags: [Posts]
      operationId: publishPost
      summary: Publish a post
      responses:
        "200":
          description: Publish result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_PublishResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/emails:
    post:
      tags: [Emails]
      operationId: sendEmail
      summary: Send a transactional email
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendEmailInput"
      responses:
        "202":
          description: Email queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_EmailSendResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/emails/batch:
    post:
      tags: [Emails]
      operationId: batchSendEmails
      summary: Send a batch of transactional emails
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchSendInput"
      responses:
        "202":
          description: Batch queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_BatchSendSummary"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/emails/templates:
    get:
      tags: [Emails]
      operationId: listEmailTemplates
      summary: List active email templates
      responses:
        "200":
          description: Active templates.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_EmailTemplateArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/emails/templates/{slug}:
    parameters:
      - $ref: "#/components/parameters/Slug"
    get:
      tags: [Emails]
      operationId: getEmailTemplate
      summary: Get an email template
      parameters:
        - name: locale
          in: query
          schema:
            type: string
        - name: fallback_locale
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Email template detail.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_EmailTemplateDetail"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/emails/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Emails]
      operationId: getEmailSend
      summary: Get sent email status
      responses:
        "200":
          description: Sent email status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_EmailSend"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/contacts:
    get:
      tags: [Contacts]
      operationId: listContacts
      summary: List contacts
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ContactStatus"
        - name: email_status
          in: query
          schema:
            $ref: "#/components/schemas/EmailStatus"
        - name: label_ids
          in: query
          schema:
            type: string
          description: Comma-separated label IDs.
        - name: search
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Contacts page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_Contact"
        default:
          $ref: "#/components/responses/ApiError"
    post:
      tags: [Contacts]
      operationId: createContact
      summary: Create a contact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateContactInput"
      responses:
        "200":
          description: Created contact reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ContactCreateResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/contacts/import:
    post:
      tags: [Contacts]
      operationId: importContacts
      summary: Import contacts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [contacts]
              properties:
                contacts:
                  type: array
                  maxItems: 500
                  items:
                    $ref: "#/components/schemas/ImportContactInput"
      responses:
        "200":
          description: Import summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ImportContactsResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/contacts/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Contacts]
      operationId: getContact
      summary: Get a contact
      responses:
        "200":
          description: Contact.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_Contact"
        default:
          $ref: "#/components/responses/ApiError"
    patch:
      tags: [Contacts]
      operationId: updateContact
      summary: Update a contact
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateContactInput"
      responses:
        "200":
          description: Update result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ContactUpdateResult"
        default:
          $ref: "#/components/responses/ApiError"
    delete:
      tags: [Contacts]
      operationId: deleteContact
      summary: Delete a contact
      responses:
        "200":
          description: Delete result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ContactRemoveResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/contacts/{id}/activities:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Contacts]
      operationId: listContactActivities
      summary: List contact activities
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Contact activities page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_Activity"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/contacts/{id}/notes:
    parameters:
      - $ref: "#/components/parameters/Id"
    post:
      tags: [Contacts]
      operationId: addContactNote
      summary: Add a contact note
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddNoteInput"
      responses:
        "200":
          description: Created note reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ContactNoteResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/deals:
    get:
      tags: [Deals]
      operationId: listDeals
      summary: List deals
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/DealStatus"
        - name: search
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Deals page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_Deal"
        default:
          $ref: "#/components/responses/ApiError"
    post:
      tags: [Deals]
      operationId: createDeal
      summary: Create a deal
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDealInput"
      responses:
        "200":
          description: Created deal reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_DealCreateResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/deals/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Deals]
      operationId: getDeal
      summary: Get a deal
      responses:
        "200":
          description: Deal.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_Deal"
        default:
          $ref: "#/components/responses/ApiError"
    patch:
      tags: [Deals]
      operationId: updateDeal
      summary: Update a deal
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateDealInput"
      responses:
        "200":
          description: Update result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_DealUpdateResult"
        default:
          $ref: "#/components/responses/ApiError"
    delete:
      tags: [Deals]
      operationId: deleteDeal
      summary: Delete a deal
      responses:
        "200":
          description: Delete result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_DealRemoveResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/gdpr/export:
    post:
      tags: [GDPR]
      operationId: requestGdprExport
      summary: Request a GDPR export
      responses:
        "200":
          description: Export request reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_GdprExportRequest"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/gdpr/exports:
    get:
      tags: [GDPR]
      operationId: listGdprExports
      summary: List GDPR exports
      responses:
        "200":
          description: Export requests.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_GdprExportArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/gdpr/exports/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [GDPR]
      operationId: getGdprExport
      summary: Get a GDPR export
      responses:
        "200":
          description: Export request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_GdprExport"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/gdpr/consent:
    post:
      tags: [GDPR]
      operationId: recordGdprConsent
      summary: Record GDPR consent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RecordConsentInput"
      responses:
        "200":
          description: Consent reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ConsentResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/gdpr/consent/{email}:
    parameters:
      - name: email
        in: path
        required: true
        schema:
          type: string
          format: email
    get:
      tags: [GDPR]
      operationId: getGdprConsent
      summary: Get GDPR consent records
      responses:
        "200":
          description: Consent records.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ConsentRecordArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/cookie-consent:
    post:
      tags: [GDPR]
      operationId: recordCookieConsent
      summary: Record cookie consent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CookieConsentInput"
      responses:
        "200":
          description: Cookie consent result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CookieConsentResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/me/workspaces:
    get:
      tags: [Workspaces]
      operationId: listWorkspaces
      summary: List accessible workspaces
      responses:
        "200":
          description: Workspaces.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WorkspaceArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/helpdesk/conversations:
    get:
      tags: [Helpdesk]
      operationId: listHelpdeskConversations
      summary: List helpdesk conversations
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
        - $ref: "#/components/parameters/Cursor"
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/HelpdeskConversationStatus"
        - name: assignee_user_id
          in: query
          schema:
            type: string
          description: Only conversations assigned to this user.
        - name: requester
          in: query
          schema:
            type: string
          description: Match against visitor name/email.
        - name: query
          in: query
          schema:
            type: string
          description: Free-text search query.
        - name: channels
          in: query
          schema:
            type: string
          description: Comma-separated channel types (e.g. `widget,whatsapp`).
      responses:
        "200":
          description: Conversations page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_HelpdeskConversation"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/helpdesk/conversations/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Helpdesk]
      operationId: getHelpdeskConversation
      summary: Get a helpdesk conversation
      responses:
        "200":
          description: Conversation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_HelpdeskConversation"
        default:
          $ref: "#/components/responses/ApiError"
    patch:
      tags: [Helpdesk]
      operationId: updateHelpdeskConversation
      summary: Update a conversation's status or assignee
      description: >-
        Set `status` and/or `assignee_user_id` (`null` unassigns). At least one
        field is required. Capability-scoped tokens must send `Idempotency-Key`
        and `X-Capability-Confirmation` headers on this route; API keys with
        legacy scopes may omit them.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateHelpdeskConversationInput"
      responses:
        "200":
          description: Update result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_HelpdeskConversationUpdateResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/helpdesk/conversations/{id}/messages:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Helpdesk]
      operationId: listHelpdeskConversationMessages
      summary: List conversation messages
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Messages page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_HelpdeskMessage"
        "404":
          description: Conversation not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/helpdesk/replies:
    post:
      tags: [Helpdesk]
      operationId: createHelpdeskReply
      summary: Send an operator reply or internal note
      description: >-
        Send an `Idempotency-Key` header so retried requests do not create
        duplicate messages — it is required for capability-scoped tokens
        (together with `X-Capability-Confirmation`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateHelpdeskReplyInput"
      responses:
        "201":
          description: Created reply reference.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_HelpdeskReplyCreateResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List webhook endpoints
      responses:
        "200":
          description: Webhook endpoints.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookEndpointArray"
        default:
          $ref: "#/components/responses/ApiError"
    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Create a webhook endpoint
      description: >-
        The response's `data.secret` contains the signing secret EXACTLY ONCE —
        it can never be retrieved again, so store it securely immediately. An
        idempotent replay (same `Idempotency-Key`) returns the existing
        endpoint WITHOUT `secret`. Deliveries are HTTP POSTs signed with
        `X-Medal-Signature: sha256=<base64(HMAC-SHA256("{timestamp}.{rawBody}",
        secret))>` plus `X-Medal-Timestamp`, `X-Medal-Event`, and
        `X-Medal-Delivery-Id` / `Idempotency-Key` headers.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookInput"
      responses:
        "201":
          description: Created endpoint, including the one-time `secret`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookEndpoint"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/webhooks/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Webhooks]
      operationId: getWebhook
      summary: Get a webhook endpoint
      responses:
        "200":
          description: Webhook endpoint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookEndpoint"
        default:
          $ref: "#/components/responses/ApiError"
    patch:
      tags: [Webhooks]
      operationId: updateWebhook
      summary: Update a webhook endpoint
      description: >-
        Only provided fields change. Pass `null` for `channels` or
        `channel_connection_ids` to clear that filter.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWebhookInput"
      responses:
        "200":
          description: Updated endpoint.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookEndpoint"
        default:
          $ref: "#/components/responses/ApiError"
    delete:
      tags: [Webhooks]
      operationId: deleteWebhook
      summary: Delete a webhook endpoint
      description: >-
        Permanently deletes the endpoint and stops all outbound deliveries.
        Capability-scoped tokens must send `Idempotency-Key` and
        `X-Capability-Confirmation` headers on this route; API keys with legacy
        scopes may omit them.
      responses:
        "200":
          description: Delete result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookDeleteResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/webhooks/{id}/deliveries:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Webhooks]
      operationId: listWebhookDeliveries
      summary: List recent deliveries
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Recent deliveries, most recent first.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookDeliveryArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/webhooks/{id}/test:
    parameters:
      - $ref: "#/components/parameters/Id"
    post:
      tags: [Webhooks]
      operationId: testWebhook
      summary: Queue a test delivery
      description: Queues a signed `test.ping` delivery to the endpoint.
      responses:
        "202":
          description: Test delivery queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_WebhookTestResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/capability-confirmations:
    post:
      tags: [Capabilities]
      operationId: issueCapabilityConfirmation
      summary: Issue a capability confirmation token
      description: >-
        Mints a short-lived `X-Capability-Confirmation` token for one pending
        write. Confirmable write routes require BOTH `Idempotency-Key` and
        `X-Capability-Confirmation` when the credential holds the capability
        scope directly; API keys with legacy scopes are exempt. The token is
        bound to the workspace, auth subject, method, path, required scopes and
        idempotency key, and expires within 15 minutes. `user_approved: true`
        asserts that a human on the caller's side approved this exact action.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IssueCapabilityConfirmationInput"
      responses:
        "200":
          description: Minted confirmation token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_CapabilityConfirmation"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/channels/connect-links:
    post:
      tags: [Channels]
      operationId: createChannelConnectLink
      summary: Mint a hosted connect link
      description: >-
        Mints a single-use hosted connect link that lets an external person
        (no Medal account required) attach a channel account (e.g.
        `telegram_inbox`) to the workspace's helpdesk. The response's
        `data.url` contains the one-time link token EXACTLY ONCE — an
        idempotent replay (same `Idempotency-Key`) returns the link WITHOUT
        `url`. Requires the `channel.connect.manage` scope; OAuth callers
        additionally need the workspace `admin` role.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConnectLinkInput"
      responses:
        "201":
          description: Minted connect link, including the one-time `url`.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ConnectLinkCreateResult"
        default:
          $ref: "#/components/responses/ApiError"
    get:
      tags: [Channels]
      operationId: listChannelConnectLinks
      summary: List connect links
      description: >-
        Link tokens are never returned. Newest first, cursor-paginated. The
        `channel_type` / `status` filters are applied within each page, so a
        page may hold fewer than `limit` items while `pagination.has_more` is
        still true — page off `has_more`, not the item count.
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
          description: Page size (default 50, capped at 100).
        - $ref: "#/components/parameters/Cursor"
        - name: channel_type
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ConnectLinkStatus"
      responses:
        "200":
          description: Connect links page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_ConnectLink"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/channels/connect-links/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    delete:
      tags: [Channels]
      operationId: revokeChannelConnectLink
      summary: Revoke a connect link
      description: >-
        Revokes a pending connect link so it can no longer be consumed. OAuth
        callers need the workspace `admin` role.
      responses:
        "200":
          description: Revoke result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ConnectLinkRevokeResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/channels/connections:
    get:
      tags: [Channels]
      operationId: listChannelConnections
      summary: List channel connections
      description: >-
        The workspace's channel connections in a generic, channel-agnostic
        shape. Newest first, cursor-paginated. Rows that are not projectable
        as connections are dropped within the page, so a page may hold fewer
        than `limit` items while `pagination.has_more` is still true — page
        off `has_more`, not the item count.
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
          description: Page size (default 50, capped at 100).
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Channel connections page.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedResponse_ChannelConnection"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/channels/connections/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    delete:
      tags: [Channels]
      operationId: disconnectChannelConnection
      summary: Disconnect a channel connection
      description: >-
        Disconnects a connected channel account (best-effort platform logout,
        then local revoke). Emits a `helpdesk.channel_disconnected` webhook
        event with `reason: "api_disconnect"` if the account was previously
        connected. OAuth callers need the workspace `admin` role.
      responses:
        "200":
          description: Disconnect result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ChannelConnectionDisconnectResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/scan:
    post:
      tags: [Scan]
      operationId: createScan
      summary: Queue a company/site scan
      description: >-
        Queues an asynchronous scan. Provide exactly one of `url`, `orgnr`, or
        `name`; poll the returned id via `GET /api/v1/scan/{id}`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScanCreateInput"
      responses:
        "202":
          description: Scan job queued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ScanCreateResult"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/scan/companies:
    get:
      tags: [Scan]
      operationId: searchScanCompanies
      summary: Search the Norwegian company registry
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 2
      responses:
        "200":
          description: Registry matches (top 5, relevance-ranked).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ScanCompanyArray"
        default:
          $ref: "#/components/responses/ApiError"
  /api/v1/scan/{id}:
    parameters:
      - $ref: "#/components/parameters/Id"
    get:
      tags: [Scan]
      operationId: getScan
      summary: Get a scan job
      responses:
        "200":
          description: Scan job status and, once done, the findings payload.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiResponse_ScanJob"
        default:
          $ref: "#/components/responses/ApiError"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  parameters:
    Id:
      name: id
      in: path
      required: true
      schema:
        type: string
    Slug:
      name: slug
      in: path
      required: true
      schema:
        type: string
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 500
  responses:
    ApiError:
      description: API error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
  schemas:
    ApiError:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
            message:
              type: string
            details: true
    Pagination:
      type: object
      required: [has_more, next_cursor]
      properties:
        has_more:
          type: boolean
        next_cursor:
          type: [string, "null"]
    Success:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
    PostCreateResult:
      type: object
      required: [id]
      properties:
        id:
          type: string
    Post:
      type: object
      required:
        - id
        - type
        - title
        - content
        - status
        - channel_ids
        - variant_count
        - published_count
        - failed_count
        - scheduled_at
        - published_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
        type:
          $ref: "#/components/schemas/PostType"
        title:
          type: [string, "null"]
        content:
          type: string
        status:
          type: string
        channel_ids:
          type: array
          items:
            type: string
        variant_count:
          type: integer
        published_count:
          type: integer
        failed_count:
          type: integer
        scheduled_at:
          type: [string, "null"]
          format: date-time
        published_at:
          type: [string, "null"]
          format: date-time
        created_at:
          type: [string, "null"]
          format: date-time
        updated_at:
          type: [string, "null"]
          format: date-time
    PostType:
      type: string
      enum: [social, newsletter, blog]
    PostVariant:
      type: object
      required:
        - id
        - channel_id
        - content
        - status
        - platform
        - channel_display_name
        - scheduled_at
        - published_at
        - platform_post_id
        - permalink
        - error
      properties:
        id:
          type: string
        channel_id:
          type: string
        content:
          type: string
        status:
          type: string
        platform:
          type: [string, "null"]
        channel_display_name:
          type: [string, "null"]
        scheduled_at:
          type: [string, "null"]
          format: date-time
        published_at:
          type: [string, "null"]
          format: date-time
        platform_post_id:
          type: [string, "null"]
        permalink:
          type: [string, "null"]
          format: uri
        error:
          type: [string, "null"]
    PostDetail:
      allOf:
        - $ref: "#/components/schemas/Post"
        - type: object
          required: [variants]
          properties:
            variants:
              type: array
              items:
                $ref: "#/components/schemas/PostVariant"
    Channel:
      type: object
      required: [id, platform, display_name, platform_username, state, connected_at]
      properties:
        id:
          type: string
        platform:
          type: [string, "null"]
        display_name:
          type: [string, "null"]
        platform_username:
          type: [string, "null"]
        state:
          type: string
        connected_at:
          type: [string, "null"]
          format: date-time
    CreatePostInput:
      type: object
      required: [content, channel_ids]
      properties:
        type:
          $ref: "#/components/schemas/PostType"
        title:
          type: string
        content:
          type: string
        channel_ids:
          type: array
          items:
            type: string
    UpdatePostInput:
      type: object
      properties:
        title:
          type: string
        content:
          type: string
    SchedulePostInput:
      type: object
      required: [scheduled_at]
      properties:
        scheduled_at:
          oneOf:
            - type: integer
            - type: string
              format: date-time
    ScheduleResult:
      type: object
      required: [success, workflow_id]
      properties:
        success:
          type: boolean
        workflow_id:
          type: string
    PublishResult:
      type: object
      required: [success, workflow_id]
      properties:
        success:
          type: boolean
        workflow_id:
          type: string
    SendEmailInput:
      type: object
      required: [template_slug, to]
      properties:
        template_slug:
          type: string
        to:
          type: string
          format: email
        name:
          type: string
        locale:
          type: string
        fallback_locale:
          type: string
        variables:
          type: object
          additionalProperties:
            type: string
        contact_id:
          type: string
        idempotency_key:
          type: string
        copy_to:
          type: string
          format: email
        copy_reply_to:
          type: string
          format: email
    EmailSendResult:
      type: object
      required: [id, copy_id, contact_id, status]
      properties:
        id:
          type: [string, "null"]
          description: Email send id — poll GET /api/v1/emails/{id} with it.
        copy_id:
          type: [string, "null"]
          description: Send id of the copy_to copy, or null when no copy was requested.
        contact_id:
          type: [string, "null"]
        status:
          type: string
    EmailSend:
      type: object
      required:
        - id
        - status
        - recipient_email
        - recipient_name
        - subject
        - template_id
        - contact_id
        - queued_at
        - sent_at
        - delivered_at
        - opened_at
        - clicked_at
        - error_message
      properties:
        id:
          type: string
        status:
          type: string
        recipient_email:
          type: string
          format: email
        recipient_name:
          type: [string, "null"]
        subject:
          type: [string, "null"]
        template_id:
          type: [string, "null"]
        contact_id:
          type: [string, "null"]
        queued_at:
          type: [string, "null"]
          format: date-time
        sent_at:
          type: [string, "null"]
          format: date-time
        delivered_at:
          type: [string, "null"]
          format: date-time
        opened_at:
          type: [string, "null"]
          format: date-time
        clicked_at:
          type: [string, "null"]
          format: date-time
        error_message:
          type: [string, "null"]
    BatchSendInput:
      type: object
      required: [template_slug, recipients]
      properties:
        template_slug:
          type: string
        default_locale:
          type: string
        recipients:
          type: array
          maxItems: 100
          items:
            type: object
            required: [email]
            properties:
              email:
                type: string
                format: email
              name:
                type: string
              locale:
                type: string
              variables:
                type: object
                additionalProperties:
                  type: string
    BatchSendSummary:
      type: object
      required: [batch_id, total, queued, failed, results]
      properties:
        batch_id:
          type: string
        total:
          type: integer
        queued:
          type: integer
        failed:
          type: integer
        results:
          type: array
          description: Per-recipient outcome, in request order.
          items:
            $ref: "#/components/schemas/BatchSendRecipientResult"
    BatchSendRecipientResult:
      type: object
      required: [email, id, status, error]
      properties:
        email:
          type: string
          format: email
        id:
          type: [string, "null"]
          description: Email send id — poll GET /api/v1/emails/{id} with it. Null when not queued.
        status:
          type: string
          enum: [queued, failed]
        error:
          type: [string, "null"]
          description: Failure reason for recipients that were not queued.
    EmailTemplate:
      type: object
      required:
        - id
        - name
        - slug
        - type
        - subject
        - default_locale
        - available_locales
        - description
        - category
        - label_ids
        - is_active
        - is_archived
        - version
        - created_at
        - updated_at
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
        type:
          type: [string, "null"]
        subject:
          type: [string, "null"]
        default_locale:
          type: string
        available_locales:
          type: array
          items:
            type: string
        description:
          type: [string, "null"]
        category:
          type: [string, "null"]
        label_ids:
          type: array
          items:
            type: string
        is_active:
          type: boolean
        is_archived:
          type: boolean
        version:
          type: integer
        created_at:
          type: [string, "null"]
          format: date-time
        updated_at:
          type: [string, "null"]
          format: date-time
    EmailTemplateDetail:
      allOf:
        - $ref: "#/components/schemas/EmailTemplate"
        - type: object
          required:
            - html_content
            - text_content
            - preview_text
            - from_name
            - from_email
            - reply_to
            - requested_locale
            - resolved_locale
            - content_source
            - localizations
          properties:
            html_content:
              type: [string, "null"]
            text_content:
              type: [string, "null"]
            preview_text:
              type: [string, "null"]
            from_name:
              type: [string, "null"]
            from_email:
              type: [string, "null"]
              format: email
            reply_to:
              type: [string, "null"]
              format: email
            requested_locale:
              type: [string, "null"]
            resolved_locale:
              type: string
            content_source:
              type: string
            localizations:
              type: array
              items:
                type: object
                required: [locale, subject, html_content, text_content, preview_text]
                properties:
                  locale:
                    type: [string, "null"]
                  subject:
                    type: [string, "null"]
                  html_content:
                    type: [string, "null"]
                  text_content:
                    type: [string, "null"]
                  preview_text:
                    type: [string, "null"]
    Contact:
      type: object
      required:
        - id
        - email
        - first_name
        - last_name
        - phone
        - company
        - job_title
        - address
        - status
        - email_status
        - label_ids
        - source
        - custom_fields
        - created_at
        - updated_at
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        first_name:
          type: [string, "null"]
        last_name:
          type: [string, "null"]
        phone:
          type: [string, "null"]
        company:
          type: [string, "null"]
        job_title:
          type: [string, "null"]
        address:
          anyOf:
            - type: object
              additionalProperties:
                type: string
            - type: "null"
        status:
          $ref: "#/components/schemas/ContactStatus"
        email_status:
          $ref: "#/components/schemas/EmailStatus"
        label_ids:
          type: array
          items:
            type: string
        source:
          type: [string, "null"]
        custom_fields:
          anyOf:
            - type: object
              additionalProperties: true
            - type: "null"
        created_at:
          type: [string, "null"]
          format: date-time
        updated_at:
          type: [string, "null"]
          format: date-time
    ContactCreateResult:
      type: object
      required: [id]
      properties:
        id:
          type: string
    ContactUpdateResult:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          const: true
    ContactRemoveResult:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          const: true
    ContactNoteResult:
      type: object
      required: [id]
      properties:
        id:
          type: string
    ContactStatus:
      type: string
      enum: [lead, prospect, customer, churned, archived]
    EmailStatus:
      type: string
      enum: [subscribed, unsubscribed, bounced, complained]
    CreateContactInput:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        first_name:
          type: string
        last_name:
          type: string
        phone:
          type: string
        company:
          type: string
        job_title:
          type: string
        address:
          type: object
          additionalProperties:
            type: string
        status:
          $ref: "#/components/schemas/ContactStatus"
        email_status:
          $ref: "#/components/schemas/EmailStatus"
        label_ids:
          type: array
          items:
            type: string
        labels:
          type: array
          items:
            type: string
        custom_fields:
          type: object
          additionalProperties: true
        notes:
          oneOf:
            - type: string
            - $ref: "#/components/schemas/ContactNoteInput"
    UpdateContactInput:
      type: object
      properties:
        email:
          type: string
          format: email
        first_name:
          type: string
        last_name:
          type: string
        phone:
          type: string
        company:
          type: string
        job_title:
          type: string
        status:
          $ref: "#/components/schemas/ContactStatus"
        email_status:
          $ref: "#/components/schemas/EmailStatus"
        label_ids:
          type: array
          items:
            type: string
        labels:
          type: array
          items:
            type: string
        custom_fields:
          type: object
          additionalProperties: true
    ContactNoteInput:
      type: object
      required: [content]
      properties:
        content:
          type: string
        attachments:
          type: array
          items:
            type: object
            required: [url, name]
            properties:
              url:
                type: string
                format: uri
              name:
                type: string
              type:
                type: string
              size:
                type: number
    ImportContactInput:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        first_name:
          type: string
        last_name:
          type: string
        phone:
          type: string
        company:
          type: string
        job_title:
          type: string
        label_ids:
          type: array
          items:
            type: string
        status:
          type: string
    ImportContactsResult:
      type: object
      required: [added, skipped, total]
      properties:
        added:
          type: integer
        skipped:
          type: integer
        total:
          type: integer
    Activity:
      type: object
      required:
        - id
        - type
        - title
        - content
        - actor_name
        - actor_type
        - metadata
        - created_at
      properties:
        id:
          type: string
        type:
          type: string
        title:
          type: [string, "null"]
        content:
          type: [string, "null"]
        actor_name:
          type: [string, "null"]
        actor_type:
          type: [string, "null"]
        metadata: true
        created_at:
          type: [string, "null"]
          format: date-time
    AddNoteInput:
      type: object
      required: [content]
      properties:
        content:
          type: string
    Deal:
      type: object
      required:
        - id
        - title
        - description
        - value
        - currency
        - status
        - brand_name
        - brand_website
        - contact_id
        - contact_name
        - contact_email
        - start_date
        - end_date
        - notes
        - created_at
        - updated_at
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: [string, "null"]
        value:
          type: [number, "null"]
        currency:
          type: [string, "null"]
        status:
          $ref: "#/components/schemas/DealStatus"
        brand_name:
          type: [string, "null"]
        brand_website:
          type: [string, "null"]
          format: uri
        contact_id:
          type: [string, "null"]
        contact_name:
          type: [string, "null"]
        contact_email:
          type: [string, "null"]
          format: email
        start_date:
          type: [string, "null"]
          format: date
        end_date:
          type: [string, "null"]
          format: date
        notes:
          type: [string, "null"]
        created_at:
          type: [string, "null"]
          format: date-time
        updated_at:
          type: [string, "null"]
          format: date-time
    DealCreateResult:
      type: object
      required: [id]
      properties:
        id:
          type: string
    DealUpdateResult:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          const: true
    DealRemoveResult:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          const: true
    DealStatus:
      type: string
      enum: [draft, open, won, lost, negotiating, proposal_sent, on_hold, churned]
    CreateDealInput:
      type: object
      required: [title]
      properties:
        title:
          type: string
        description:
          type: string
        value:
          type: number
        currency:
          type: string
        brand_name:
          type: string
        brand_website:
          type: string
          format: uri
        contact_id:
          type: string
        contact_name:
          type: string
        contact_email:
          type: string
          format: email
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
        notes:
          type: string
    UpdateDealInput:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        value:
          type: number
        currency:
          type: string
        status:
          $ref: "#/components/schemas/DealStatus"
        brand_name:
          type: string
        brand_website:
          type: string
          format: uri
        contact_id:
          type: [string, "null"]
        contact_name:
          type: string
        contact_email:
          type: string
          format: email
        start_date:
          type: string
          format: date
        end_date:
          type: string
          format: date
        notes:
          type: string
    GdprExport:
      type: object
      required: [id, request_type, status, submitted_at, completed_at]
      properties:
        id:
          type: string
        request_type:
          type: string
        status:
          type: string
        submitted_at:
          type: [string, "null"]
          format: date-time
        completed_at:
          type: [string, "null"]
          format: date-time
        due_date:
          type: [string, "null"]
          format: date-time
        download_url:
          type: [string, "null"]
          format: uri
        expires_at:
          type: [string, "null"]
          format: date-time
    GdprExportRequest:
      type: object
      required: [request_id, status]
      properties:
        request_id:
          type: string
        status:
          type: string
    ConsentType:
      type: string
      enum: [marketing_email, analytics_tracking, third_party_sharing]
    RecordConsentInput:
      type: object
      required: [email, consent_type, granted]
      properties:
        email:
          type: string
          format: email
        consent_type:
          $ref: "#/components/schemas/ConsentType"
        granted:
          type: boolean
        source:
          type: string
        ip_address:
          type: string
        consent_text:
          type: string
        version:
          type: string
    ConsentRecord:
      type: object
      required: [id, email, consent_type, granted, granted_at, revoked_at]
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        consent_type:
          $ref: "#/components/schemas/ConsentType"
        granted:
          type: boolean
        granted_at:
          type: [string, "null"]
          format: date-time
        revoked_at:
          type: [string, "null"]
          format: date-time
        source:
          type: string
        version:
          type: string
    ConsentResult:
      type: object
      required: [id]
      properties:
        id:
          type: string
    CookieConsentInput:
      type: object
      required: [domain, consentStatus, consentTimestamp, cookiePreferences]
      properties:
        domain:
          type: string
        consentStatus:
          type: string
        consentTimestamp:
          type: string
          format: date-time
        ipAddress:
          type: string
        userAgent:
          type: string
        cookiePreferences:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/CookieCategoryConsent"
    CookieCategoryConsent:
      type: object
      required: [allowed]
      properties:
        allowed:
          type: boolean
        cookieRecords:
          type: array
          items:
            type: object
            required: [cookie, duration, description]
            properties:
              cookie:
                type: string
              duration:
                type: string
              description:
                type: string
    CookieConsentResult:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
        logId:
          type: string
    Workspace:
      type: object
      required: [id, name, slug]
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
      additionalProperties: true
    HelpdeskConversationStatus:
      type: string
      enum: [open, snoozed, closed]
    HelpdeskMessageAuthorType:
      type: string
      enum: [visitor, operator, ai, system]
    HelpdeskMessageType:
      type: string
      enum: [chat, email, note]
      description: >-
        `note` is operator-internal and never delivered to the customer.
    HelpdeskConversation:
      type: object
      required:
        - id
        - channel
        - channel_connection_id
        - status
        - subject
        - assignee_user_id
        - contact_id
        - visitor_name
        - visitor_email
        - external_conversation_id
        - channel_account_id
        - message_count
        - unread_for_operator
        - last_message_at
        - last_message_preview
        - last_message_author_type
        - created_at
        - updated_at
      properties:
        id:
          type: string
        channel:
          type: string
          description: >-
            Channel type, e.g. `widget`, `instagram`, `messenger`, `whatsapp`,
            `email`.
        channel_connection_id:
          type: [string, "null"]
        status:
          $ref: "#/components/schemas/HelpdeskConversationStatus"
        subject:
          type: [string, "null"]
        assignee_user_id:
          type: [string, "null"]
        contact_id:
          type: [string, "null"]
        visitor_name:
          type: [string, "null"]
        visitor_email:
          type: [string, "null"]
        external_conversation_id:
          type: [string, "null"]
        channel_account_id:
          type: [string, "null"]
        message_count:
          type: integer
        unread_for_operator:
          type: integer
        last_message_at:
          type: integer
          description: Unix timestamp in milliseconds.
        last_message_preview:
          type: [string, "null"]
        last_message_author_type:
          anyOf:
            - $ref: "#/components/schemas/HelpdeskMessageAuthorType"
            - type: "null"
        created_at:
          type: integer
          description: Unix timestamp in milliseconds.
        updated_at:
          type: integer
          description: Unix timestamp in milliseconds.
    HelpdeskMessage:
      type: object
      required:
        - id
        - conversation_id
        - author_type
        - message_type
        - author_user_id
        - author_name
        - body
        - delivery_status
        - delivery_error
        - created_at
      properties:
        id:
          type: string
        conversation_id:
          type: string
        author_type:
          $ref: "#/components/schemas/HelpdeskMessageAuthorType"
        message_type:
          $ref: "#/components/schemas/HelpdeskMessageType"
        author_user_id:
          type: [string, "null"]
        author_name:
          type: [string, "null"]
        body:
          type: string
        delivery_status:
          oneOf:
            - $ref: "#/components/schemas/HelpdeskMessageDeliveryStatus"
            - type: "null"
          description: >-
            Outbound delivery state, or `null` for inbound messages and
            internal notes. A `201` from `POST /api/v1/helpdesk/replies` means
            the reply was accepted, NOT delivered — the channel hand-off is
            asynchronous, so poll this field (or subscribe to
            `helpdesk.message_delivery_updated`) to learn the outcome.
        delivery_error:
          type: [string, "null"]
          description: >-
            Last send error for a `failed` outbound message, otherwise `null`.
        created_at:
          type: integer
          description: Unix timestamp in milliseconds.
    HelpdeskMessageDeliveryStatus:
      type: string
      enum: [pending, sent, delivered, failed]
    UpdateHelpdeskConversationInput:
      type: object
      minProperties: 1
      description: At least one field is required.
      properties:
        status:
          $ref: "#/components/schemas/HelpdeskConversationStatus"
        assignee_user_id:
          type: [string, "null"]
          description: User ID to assign, or `null` to unassign.
    HelpdeskConversationUpdateResult:
      type: object
      required: [id, status, assignee_user_id]
      properties:
        id:
          type: string
        status:
          $ref: "#/components/schemas/HelpdeskConversationStatus"
        assignee_user_id:
          type: [string, "null"]
    CreateHelpdeskReplyInput:
      type: object
      required: [conversation_id, body]
      properties:
        conversation_id:
          type: string
        body:
          type: string
          maxLength: 20000
        message_type:
          type: string
          enum: [chat, note]
          description: >-
            `note` = operator-internal note (not delivered to the customer).
            Default `chat`.
        author_name:
          type: string
          description: >-
            Agent display name for bridged replies (shown in widget + inbox).
    HelpdeskReplyCreateResult:
      type: object
      required: [id, conversation_id, status]
      properties:
        id:
          type: string
        conversation_id:
          type: string
        status:
          type: string
          const: created
    WebhookEndpoint:
      type: object
      required:
        - id
        - name
        - url
        - enabled
        - event_types
        - channels
        - channel_connection_ids
        - secret_last4
        - consecutive_failures
        - last_delivery_at
        - last_success_at
        - last_error_at
        - last_error
        - created_at
        - updated_at
      properties:
        id:
          type: string
        name:
          type: string
        url:
          type: string
          format: uri
          description: Destination URL (must be https).
        enabled:
          type: boolean
        event_types:
          type: array
          items:
            type: string
          description: Subscribed event types. Empty array = all events.
        channels:
          type: [array, "null"]
          items:
            type: string
          description: >-
            Channel-type filter (e.g. `['widget', 'whatsapp']`), or `null` for
            all channels.
        channel_connection_ids:
          type: [array, "null"]
          items:
            type: string
          description: Channel-connection filter, or `null` for all connections.
        secret_last4:
          type: string
          description: Last 4 characters of the signing secret, for identification.
        consecutive_failures:
          type: integer
        last_delivery_at:
          type: [integer, "null"]
          description: Unix timestamp in milliseconds, or `null` if never.
        last_success_at:
          type: [integer, "null"]
          description: Unix timestamp in milliseconds, or `null` if never.
        last_error_at:
          type: [integer, "null"]
          description: Unix timestamp in milliseconds, or `null` if never.
        last_error:
          type: [string, "null"]
        created_at:
          type: integer
          description: Unix timestamp in milliseconds.
        updated_at:
          type: integer
          description: Unix timestamp in milliseconds.
        secret:
          type: string
          description: >-
            Full signing secret (`whsec_…`) — present ONLY in the create
            response, exactly once. It can never be retrieved again; store it
            securely immediately. An idempotent replay of the create request
            omits it.
    CreateWebhookInput:
      type: object
      required: [name, url, event_types]
      properties:
        name:
          type: string
          maxLength: 100
        url:
          type: string
          format: uri
          description: Destination URL — must be https.
        event_types:
          type: array
          items:
            type: string
          description: >-
            Event types to subscribe to (e.g. `helpdesk.message_received`).
            Empty = all.
        channels:
          type: array
          items:
            type: string
          description: Restrict to these channel types (e.g. `['widget', 'whatsapp']`).
        channel_connection_ids:
          type: array
          items:
            type: string
          description: Restrict to these channel connection IDs.
    UpdateWebhookInput:
      type: object
      description: Only provided fields change.
      properties:
        name:
          type: string
          maxLength: 100
        url:
          type: string
          format: uri
        event_types:
          type: array
          items:
            type: string
        channels:
          type: [array, "null"]
          items:
            type: string
          description: New channel-type filter, or `null` to clear the filter.
        channel_connection_ids:
          type: [array, "null"]
          items:
            type: string
          description: New channel-connection filter, or `null` to clear the filter.
        enabled:
          type: boolean
    WebhookDeleteResult:
      type: object
      required: [id, status]
      properties:
        id:
          type: string
        status:
          type: string
          const: deleted
    WebhookDelivery:
      type: object
      description: >-
        A delivery attempt record. `id` is the same value sent as the
        `X-Medal-Delivery-Id` and `Idempotency-Key` headers on the outbound
        request. Deliveries never carry payload bodies (payloads can contain
        customer PII). The correlation fields are derived from the stored event
        and fail closed to `null` when no canonical event exists — e.g.
        `test.ping` deliveries or events that have aged out of retention.
      required:
        - id
        - event_type
        - resource_id
        - conversation_id
        - message_id
        - connection_ref
        - channel
        - channel_connection_id
        - status
        - attempt_count
        - next_attempt_at
        - response_status
        - duration_ms
        - last_error
        - delivered_at
        - created_at
      properties:
        id:
          type: string
        event_type:
          type: string
        resource_id:
          type: [string, "null"]
          description: Primary subject id of the announced event, or `null`.
        conversation_id:
          type: [string, "null"]
        message_id:
          type: [string, "null"]
        connection_ref:
          type: [string, "null"]
        channel:
          type: [string, "null"]
        channel_connection_id:
          type: [string, "null"]
        status:
          type: string
          enum: [pending, delivered, dead_letter]
        attempt_count:
          type: integer
        next_attempt_at:
          type: [integer, "null"]
          description: Unix timestamp in milliseconds of the next retry, or `null`.
        response_status:
          type: [integer, "null"]
        duration_ms:
          type: [integer, "null"]
        last_error:
          type: [string, "null"]
        delivered_at:
          type: [integer, "null"]
          description: Unix timestamp in milliseconds, or `null` if not delivered.
        created_at:
          type: integer
          description: Unix timestamp in milliseconds.
    WebhookTestResult:
      type: object
      required: [delivery_id, status]
      properties:
        delivery_id:
          type: string
        status:
          type: string
          const: queued
    ConnectLinkStatus:
      type: string
      enum: [pending, consumed, expired, revoked]
    ChannelConnectionState:
      type: string
      enum: [connecting, active, disconnected, disabled]
    CreateConnectLinkInput:
      type: object
      required: [channel_type]
      properties:
        channel_type:
          type: string
          maxLength: 64
          description: Channel type to connect (e.g. `telegram_inbox`).
        label:
          type: string
          maxLength: 100
          description: Display label shown on the hosted connect page.
        redirect_url:
          type: string
          format: uri
          maxLength: 2000
          description: >-
            URL the hosted page redirects to after a successful connect — must
            be https.
    ConnectLinkCreateResult:
      type: object
      required: [id, channel_type, label, status, expires_at]
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
          description: >-
            Single-use hosted connect URL containing the one-time link token —
            present ONLY in the live create response. An idempotent replay of
            the create request omits it; the token can never be retrieved
            again.
        channel_type:
          type: string
        label:
          type: [string, "null"]
        status:
          $ref: "#/components/schemas/ConnectLinkStatus"
        expires_at:
          type: integer
          description: Unix timestamp in milliseconds.
    ConnectLink:
      type: object
      required:
        - id
        - channel_type
        - label
        - status
        - consumed_connection_ref
        - expires_at
        - created_at
      properties:
        id:
          type: string
        channel_type:
          type: string
        label:
          type: [string, "null"]
        status:
          $ref: "#/components/schemas/ConnectLinkStatus"
        consumed_connection_ref:
          type: [string, "null"]
          description: >-
            Stable ref of the connection created by consuming this link, or
            `null`.
        expires_at:
          type: integer
          description: Unix timestamp in milliseconds.
        created_at:
          type: integer
          description: Unix timestamp in milliseconds.
    ConnectLinkRevokeResult:
      type: object
      required: [id, status]
      properties:
        id:
          type: string
        status:
          type: string
          const: revoked
    ChannelConnection:
      type: object
      required:
        - id
        - channel_type
        - label
        - state
        - masked_identity
        - last_activity_at
        - helpdesk_connection_id
      properties:
        id:
          type: string
        channel_type:
          type: string
        label:
          type: [string, "null"]
        state:
          $ref: "#/components/schemas/ChannelConnectionState"
        masked_identity:
          type: string
          description: Privacy-preserving identity handle (e.g. a masked phone number).
        last_activity_at:
          type: [integer, "null"]
          description: Unix timestamp in milliseconds, or `null` if never active.
        helpdesk_connection_id:
          type: [string, "null"]
          description: Linked helpdesk channel connection ID, or `null`.
    ChannelConnectionDisconnectResult:
      type: object
      required: [id, state]
      properties:
        id:
          type: string
        state:
          type: string
          const: disconnected
    ApiResponse_PostCreateResult:
      $ref: "#/components/schemas/Envelope_PostCreateResult"
    ApiResponse_PostDetail:
      $ref: "#/components/schemas/Envelope_PostDetail"
    ApiResponse_Success:
      $ref: "#/components/schemas/Envelope_Success"
    ApiResponse_ScheduleResult:
      $ref: "#/components/schemas/Envelope_ScheduleResult"
    ApiResponse_PublishResult:
      $ref: "#/components/schemas/Envelope_PublishResult"
    ApiResponse_ChannelArray:
      $ref: "#/components/schemas/Envelope_ChannelArray"
    ApiResponse_EmailSendResult:
      $ref: "#/components/schemas/Envelope_EmailSendResult"
    ApiResponse_EmailSend:
      $ref: "#/components/schemas/Envelope_EmailSend"
    ApiResponse_BatchSendSummary:
      $ref: "#/components/schemas/Envelope_BatchSendSummary"
    ApiResponse_EmailTemplateArray:
      $ref: "#/components/schemas/Envelope_EmailTemplateArray"
    ApiResponse_EmailTemplateDetail:
      $ref: "#/components/schemas/Envelope_EmailTemplateDetail"
    ApiResponse_Contact:
      $ref: "#/components/schemas/Envelope_Contact"
    ApiResponse_ContactCreateResult:
      $ref: "#/components/schemas/Envelope_ContactCreateResult"
    ApiResponse_ContactUpdateResult:
      $ref: "#/components/schemas/Envelope_ContactUpdateResult"
    ApiResponse_ContactRemoveResult:
      $ref: "#/components/schemas/Envelope_ContactRemoveResult"
    ApiResponse_ContactNoteResult:
      $ref: "#/components/schemas/Envelope_ContactNoteResult"
    ApiResponse_ImportContactsResult:
      $ref: "#/components/schemas/Envelope_ImportContactsResult"
    ApiResponse_Deal:
      $ref: "#/components/schemas/Envelope_Deal"
    ApiResponse_DealCreateResult:
      $ref: "#/components/schemas/Envelope_DealCreateResult"
    ApiResponse_DealUpdateResult:
      $ref: "#/components/schemas/Envelope_DealUpdateResult"
    ApiResponse_DealRemoveResult:
      $ref: "#/components/schemas/Envelope_DealRemoveResult"
    ApiResponse_GdprExportRequest:
      $ref: "#/components/schemas/Envelope_GdprExportRequest"
    ApiResponse_GdprExportArray:
      $ref: "#/components/schemas/Envelope_GdprExportArray"
    ApiResponse_GdprExport:
      $ref: "#/components/schemas/Envelope_GdprExport"
    ApiResponse_ScanCreateResult:
      $ref: "#/components/schemas/Envelope_ScanCreateResult"
    ApiResponse_ScanCompanyArray:
      $ref: "#/components/schemas/Envelope_ScanCompanyArray"
    ApiResponse_ScanJob:
      $ref: "#/components/schemas/Envelope_ScanJob"
    ApiResponse_ConsentResult:
      $ref: "#/components/schemas/Envelope_ConsentResult"
    ApiResponse_ConsentRecordArray:
      $ref: "#/components/schemas/Envelope_ConsentRecordArray"
    ApiResponse_WorkspaceArray:
      $ref: "#/components/schemas/Envelope_WorkspaceArray"
    ApiResponse_HelpdeskConversation:
      $ref: "#/components/schemas/Envelope_HelpdeskConversation"
    ApiResponse_HelpdeskConversationUpdateResult:
      $ref: "#/components/schemas/Envelope_HelpdeskConversationUpdateResult"
    ApiResponse_HelpdeskReplyCreateResult:
      $ref: "#/components/schemas/Envelope_HelpdeskReplyCreateResult"
    ApiResponse_WebhookEndpoint:
      $ref: "#/components/schemas/Envelope_WebhookEndpoint"
    ApiResponse_WebhookEndpointArray:
      $ref: "#/components/schemas/Envelope_WebhookEndpointArray"
    ApiResponse_WebhookDeleteResult:
      $ref: "#/components/schemas/Envelope_WebhookDeleteResult"
    ApiResponse_WebhookDeliveryArray:
      $ref: "#/components/schemas/Envelope_WebhookDeliveryArray"
    ApiResponse_WebhookTestResult:
      $ref: "#/components/schemas/Envelope_WebhookTestResult"
    ApiResponse_ConnectLinkCreateResult:
      $ref: "#/components/schemas/Envelope_ConnectLinkCreateResult"
    # Deprecated: the channels listings are cursor-paginated and now respond
    # with PaginatedResponse_ConnectLink / PaginatedResponse_ChannelConnection.
    # These unpaginated aliases are retained (unreferenced by any operation)
    # so consumers importing the generated contract via
    # `@medalsocial/sdk/openapi-types` or `OpenApiComponents` keep compiling.
    ApiResponse_ConnectLinkArray:
      $ref: "#/components/schemas/Envelope_ConnectLinkArray"
    ApiResponse_ConnectLinkRevokeResult:
      $ref: "#/components/schemas/Envelope_ConnectLinkRevokeResult"
    ApiResponse_ChannelConnectionArray:
      $ref: "#/components/schemas/Envelope_ChannelConnectionArray"
    ApiResponse_ChannelConnectionDisconnectResult:
      $ref: "#/components/schemas/Envelope_ChannelConnectionDisconnectResult"
    PaginatedResponse_Post:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Post"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_Contact:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Contact"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_Activity:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Activity"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_Deal:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Deal"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_HelpdeskConversation:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/HelpdeskConversation"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_HelpdeskMessage:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/HelpdeskMessage"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_ConnectLink:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ConnectLink"
        pagination:
          $ref: "#/components/schemas/Pagination"
    PaginatedResponse_ChannelConnection:
      type: object
      required: [data, pagination]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ChannelConnection"
        pagination:
          $ref: "#/components/schemas/Pagination"
    Envelope_PostCreateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/PostCreateResult"
    Envelope_PostDetail:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/PostDetail"
    Envelope_Success:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Success"
    Envelope_ScheduleResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ScheduleResult"
    Envelope_PublishResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/PublishResult"
    Envelope_ChannelArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Channel"
    Envelope_EmailSendResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/EmailSendResult"
    Envelope_EmailSend:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/EmailSend"
    Envelope_BatchSendSummary:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/BatchSendSummary"
    Envelope_EmailTemplateArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/EmailTemplate"
    Envelope_EmailTemplateDetail:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/EmailTemplateDetail"
    Envelope_Contact:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Contact"
    Envelope_ContactCreateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ContactCreateResult"
    Envelope_ContactUpdateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ContactUpdateResult"
    Envelope_ContactRemoveResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ContactRemoveResult"
    Envelope_ContactNoteResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ContactNoteResult"
    Envelope_ImportContactsResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ImportContactsResult"
    Envelope_Deal:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Deal"
    Envelope_DealCreateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/DealCreateResult"
    Envelope_DealUpdateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/DealUpdateResult"
    Envelope_DealRemoveResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/DealRemoveResult"
    ScanCreateInput:
      type: object
      description: Provide exactly one of `url`, `orgnr`, or `name`.
      oneOf:
        - required: [url]
          not:
            anyOf: [{ required: [orgnr] }, { required: [name] }]
        - required: [orgnr]
          not:
            anyOf: [{ required: [url] }, { required: [name] }]
        - required: [name]
          not:
            anyOf: [{ required: [url] }, { required: [orgnr] }]
      properties:
        url:
          type: string
          minLength: 4
          maxLength: 500
        orgnr:
          type: string
          pattern: "^\\d{9}$"
        name:
          type: string
          minLength: 2
          maxLength: 120
    ScanCreateResult:
      type: object
      required: [id, status]
      properties:
        id:
          type: string
        status:
          type: string
        message:
          type: string
    ScanCompany:
      type: object
      required: [orgnr, name, org_form, industry, city, website]
      properties:
        orgnr:
          type: string
        name:
          type: string
        org_form:
          type: [string, "null"]
        industry:
          type: [string, "null"]
        city:
          type: [string, "null"]
        website:
          type: [string, "null"]
    ScanJob:
      type: object
      required: [id, status, input, resolved, result, error, created_at, finished_at]
      properties:
        id:
          type: string
        status:
          type: string
          description: pending | running | done | failed
        input:
          $ref: "#/components/schemas/ScanCreateInput"
        resolved:
          type: [object, "null"]
          properties:
            orgnr:
              type: string
            companyName:
              type: string
            websiteUrl:
              type: string
        result:
          type: [object, "null"]
          description: >-
            Versioned findings payload (see the SDK `ScanResultPayload` type);
            additive per `version`. The stable core is documented below;
            further sections (registry, signals, pagespeed, seo, ai, gdpr,
            mailAuth, httpsOk) evolve additively.
          required: [version, nettskaar, subScores]
          properties:
            version:
              type: integer
            nettskaar:
              type: [integer, "null"]
              description: Weighted composite score (0-100); null when unmeasurable.
            subScores:
              type: object
              description: Per-axis 0-100 scores; null axes were unmeasurable.
              required: [fart, google, ai, trygghet, omdomme]
              properties:
                fart: { type: [integer, "null"] }
                google: { type: [integer, "null"] }
                ai: { type: [integer, "null"] }
                trygghet: { type: [integer, "null"] }
                omdomme: { type: [integer, "null"] }
          additionalProperties: true
        error:
          type: [string, "null"]
          description: Public-safe failure code (company_not_found, no_website, …).
        created_at:
          type: [string, "null"]
          format: date-time
        finished_at:
          type: [string, "null"]
          format: date-time
    Envelope_ScanCreateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ScanCreateResult"
    Envelope_ScanCompanyArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ScanCompany"
    Envelope_ScanJob:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ScanJob"
    Envelope_GdprExportRequest:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/GdprExportRequest"
    Envelope_GdprExportArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/GdprExport"
    Envelope_GdprExport:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/GdprExport"
    Envelope_ConsentResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ConsentResult"
    Envelope_ConsentRecordArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ConsentRecord"
    Envelope_WorkspaceArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Workspace"
    Envelope_HelpdeskConversation:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/HelpdeskConversation"
    Envelope_HelpdeskConversationUpdateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/HelpdeskConversationUpdateResult"
    Envelope_HelpdeskReplyCreateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/HelpdeskReplyCreateResult"
    Envelope_WebhookEndpoint:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/WebhookEndpoint"
    Envelope_WebhookEndpointArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEndpoint"
    Envelope_WebhookDeleteResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/WebhookDeleteResult"
    IssueCapabilityConfirmationInput:
      type: object
      required: [capability_id, preview_summary, user_approved]
      properties:
        capability_id:
          type: string
          minLength: 1
          maxLength: 160
          description: Confirmable capability to mint a token for.
        api_path:
          type: string
          minLength: 1
          maxLength: 512
          description: >-
            Concrete `/api/v1/...` path to bind the token to. Optional when the
            capability has exactly one API target; required when it has several.
        path_params:
          type: object
          additionalProperties:
            type: [string, number, boolean]
          description: Values for the capability path template's parameters.
        idempotency_key:
          type: string
          minLength: 1
          description: >-
            The exact `Idempotency-Key` the confirmed write will send. The token
            is bound to it.
        preview_summary:
          type: string
          minLength: 1
          maxLength: 4000
          description: >-
            Human-readable description of the approved action, retained for
            audit.
        user_approved:
          const: true
          description: >-
            Asserts that a human on the caller's side approved this specific
            action.
    CapabilityConfirmation:
      type: object
      required:
        - confirmation_token
        - token_type
        - capability_id
        - method
        - path
        - required_scopes
        - idempotency_key
        - expires_in
        - expires_at
        - preview_summary
      properties:
        confirmation_token:
          type: string
          description: Send as the `X-Capability-Confirmation` header on the write.
        token_type:
          type: string
          const: medal_capability_confirmation
        capability_id:
          type: string
        method:
          type: string
        path:
          type: string
        required_scopes:
          type: array
          items:
            type: string
        idempotency_key:
          type: [string, "null"]
        expires_in:
          type: integer
          minimum: 60
          maximum: 900
        expires_at:
          type: string
          format: date-time
        preview_summary:
          type: string
    ApiResponse_CapabilityConfirmation:
      $ref: "#/components/schemas/Envelope_CapabilityConfirmation"
    Envelope_CapabilityConfirmation:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/CapabilityConfirmation"
    Envelope_WebhookDeliveryArray:
      type: object
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/WebhookDelivery"
    Envelope_WebhookTestResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/WebhookTestResult"
    Envelope_ConnectLinkCreateResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ConnectLinkCreateResult"
    Envelope_ConnectLinkArray:
      type: object
      deprecated: true
      description: >-
        Deprecated — `listChannelConnectLinks` responds with
        `PaginatedResponse_ConnectLink`. Retained for backwards compatibility
        with consumers of the generated contract types.
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ConnectLink"
    Envelope_ConnectLinkRevokeResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ConnectLinkRevokeResult"
    Envelope_ChannelConnectionArray:
      type: object
      deprecated: true
      description: >-
        Deprecated — `listChannelConnections` responds with
        `PaginatedResponse_ChannelConnection`. Retained for backwards
        compatibility with consumers of the generated contract types.
      required: [data]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ChannelConnection"
    Envelope_ChannelConnectionDisconnectResult:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/ChannelConnectionDisconnectResult"
