> ## Documentation Index
> Fetch the complete documentation index at: https://docs.norbelys.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect an app

> Starts the authorization for one app and returns the URL to send the user to (popup or redirect), plus the `pending` connection row. The credential is held by the authorization provider — it never reaches Norbelys or your client. Poll the integration until its status turns `active`.



## OpenAPI

````yaml /openapi.json post /integrations/connect
openapi: 3.1.1
info:
  description: >-
    The **Norbelys API** is a single, predictable REST surface for cold email
    and

    outreach — people, senders, programs, and sending all live behind the five

    patterns below. Developer-first and AI-first: every name is either already

    invented (Schema.org) or obvious.


    ## Authentication


    Every request authenticates with an **org-scoped API key**. Create one in

    **Settings → API keys** and send it as a bearer token:


    ```http

    GET https://api.norbelys.com/v1/people

    Authorization: Bearer ak_live_…

    ```


    Interactive agents may instead use OAuth 2.1 (see `/auth.md` and the

    `/.well-known/oauth-protected-resource` metadata).


    ## Conventions


    - **Base URL** — `https://api.norbelys.com/v1`.

    - **JSON in, JSON out.** Timestamps are ISO-8601 in UTC.

    - **Cursor pagination.** List endpoints take `limit` + `cursor` and return
      `{ data, hasMore, nextCursor }` (offset-paged tables add `page` + `total`).
    - **Expansions.** Detail GETs take an `expand[]` query param to inline
    related
      data (e.g. `GET /people/{id}?expand[]=timeline`) instead of extra calls.
    - **Soft deletes.** Anything that has been used is archived, never
    hard-deleted —
      `DELETE` archives the resource and returns it.

    ## Errors


    Failures return the same envelope on every 4xx/5xx, with the matching HTTP
    status:


    ```json

    { "error": { "type": "invalid_request", "code": "invalid_param",
                "message": "…", "hint": "…", "doc_url": "…" } }
    ```


    `type` is a broad, machine-routable category derived from the status; `code`
    is the

    stable machine contract you branch on (never the human `message`). See the
    `ApiError`

    schema.


    ## Idempotency


    Every `POST` accepts an optional **`Idempotency-Key`** header. Reuse the
    same key to

    replay the original result for 24h instead of re-executing — so a retried
    create can

    never double-charge or duplicate a record.


    ## Rate limits & versioning


    Abuse control is enforced at the edge; responses advertise the policy via
    the

    `RateLimit-Policy` header, and a `429` carries `Retry-After`. The API is
    versioned in

    the URL path (`/v1`). Breaking changes ship under a new version; a retiring
    surface is

    announced with `Deprecation` + `Sunset` response headers at least 90 days
    ahead.
  title: Norbelys API
  version: 0.0.1
  x-api-lifecycle:
    currentVersion: v1
    deprecationPolicyUrl: https://docs.norbelys.com/conventions#versioning
    deprecationSignals:
      - Deprecation header
      - Sunset header
    minNoticeDays: 90
    versioning: url-path
servers:
  - description: Production
    url: https://api.norbelys.com/v1
security:
  - bearerAuth: []
tags:
  - description: Your workspace — profile and onboarding state.
    name: Organization
  - description: >-
      The people you reach out to: create, import, segment, and read a person's
      timeline.
    name: People
  - description: >-
      Custom person fields — the tenant-defined attributes that ride on every
      person under `customFields`.
    name: Fields
  - description: >-
      Static lists of people — hand-curated audiences you add to and remove
      from.
    name: Groups
  - description: >-
      Saved audience filters — dynamic rule-trees evaluated live against your
      people.
    name: Segments
  - description: >-
      Connected mailboxes that send your email — the sending identity behind
      each program.
    name: Senders
  - description: >-
      Every domain concern in one place: sending domains (verification, DNS
      records, daily caps) and DMARC monitoring (collection addresses, ingested
      reports).
    name: Domains
  - description: >-
      Campaigns and sequences — with their steps, variants and enrollments
      nested underneath.
    name: Programs
  - description: >-
      Send email through the unified send door, read the unified sent/received
      log, and fetch a message's exact sent source.
    name: Messages
  - description: >-
      The one analytics door: named catalog queries (funnels, feeds, health,
      A/B, DMARC) over the event store.
    name: Analytics
  - description: Addresses excluded from sending — unsubscribes, bounces, and complaints.
    name: Suppressions
  - description: >-
      Connected tools (Slack, CRMs): mint a Connect session, configure
      notifications, disconnect.
    name: Integrations
  - description: >-
      Synchronous email verification — deliverability, reachability, and
      identity signals for an address.
    name: Verify
  - name: Files
paths:
  /integrations/connect:
    post:
      tags:
        - Integrations
      summary: Connect an app
      description: >-
        Starts the authorization for one app and returns the URL to send the
        user to (popup or redirect), plus the `pending` connection row. The
        credential is held by the authorization provider — it never reaches
        Norbelys or your client. Poll the integration until its status turns
        `active`.
      operationId: integrations.connect
      parameters:
        - description: >-
            Opt-in idempotency for a safely retried write: reuse the same key to
            replay the original result for 24h instead of re-executing.
            Recommended on every POST.
          in: header
          name: Idempotency-Key
          required: false
          schema:
            maxLength: 255
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IntegrationConnectParams'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationConnectResult'
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: The request was malformed or failed validation.
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: Missing or invalid credentials.
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: Authenticated, but not permitted.
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: No such resource (or it has been archived).
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: The request conflicts with the resource's current state.
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: Well-formed but semantically invalid.
        '429':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: Rate limit exceeded — retry after the `Retry-After` interval.
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
          description: An unexpected error on our side.
      x-codeSamples:
        - label: TypeScript / JavaScript
          lang: typescript
          source: >-
            import { createClient } from "@norbelys/sdk";


            const norbelys = createClient({ apiKey: process.env.NORBELYS_API_KEY
            });


            const { data, error } = await norbelys.integrations.connect({
            provider: "slack" });

            if (error) {
              throw new Error(error.error.message);
            }

            console.log(data);
        - label: Python
          lang: python
          source: >-
            import norbelys


            config = norbelys.Configuration(host="https://api.norbelys.com/v1",
            access_token="ak_live_…")

            with norbelys.ApiClient(config) as client:
                api = norbelys.IntegrationsApi(client)
                result = api.integrations_connect({ "provider": "slack" })
                print(result)
        - label: Go
          lang: go
          source: >-
            cfg := norbelys.NewConfiguration()

            cfg.Servers = norbelys.ServerConfigurations{{URL:
            "https://api.norbelys.com/v1"}}

            client := norbelys.NewAPIClient(cfg)

            ctx := context.WithValue(context.Background(),
            norbelys.ContextAccessToken, "ak_live_…")


            result, _, err :=
            client.IntegrationsAPI.IntegrationsConnect(ctx).Execute()
        - label: Ruby
          lang: ruby
          source: |-
            require "norbelys"

            Norbelys.configure { |c| c.access_token = ENV["NORBELYS_API_KEY"] }
            api = Norbelys::IntegrationsApi.new
            result = api.integrations_connect({ provider: "slack" })
            puts result
        - label: CLI
          lang: bash
          source: 'norbelys integrations connect --data ''{ "provider": "slack" }'''
        - label: curl
          lang: bash
          source: |-
            curl -X POST "https://api.norbelys.com/v1/integrations/connect" \
              -H "Authorization: Bearer $NORBELYS_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{ "provider": "slack" }'
components:
  schemas:
    IntegrationConnectParams:
      type: object
      properties:
        provider:
          $ref: '#/components/schemas/IntegrationProvider'
          description: The app to connect.
        returnUrl:
          type: string
          format: uri
          description: >-
            Where to send the user once they finish authorizing. Defaults to the
            workspace's integrations settings.
          examples:
            - https://app.norbelys.com/settings/integrations
      required:
        - provider
      title: IntegrationConnectParams
    IntegrationConnectResult:
      type: object
      properties:
        authorizationUrl:
          type: string
          format: uri
          description: >-
            Send the user here (popup or redirect) to authorize the app. The
            link expires.
          examples:
            - https://backend.composio.dev/link/abc123
        expiresAt:
          description: When the authorization link expires.
        integration:
          $ref: '#/components/schemas/Integration'
          description: >-
            The connection row, created `pending` — poll it (or list
            integrations) until it turns `active`.
      required:
        - authorizationUrl
        - integration
      title: IntegrationConnectResult
    ApiError:
      properties:
        error:
          properties:
            code:
              description: >-
                Stable machine code — branch on this, never on the human
                `message`.
              enum:
                - invalid_param
                - missing_param
                - invalid_expand
                - duplicate_email
                - already_exists
                - unauthorized
                - forbidden
                - mailbox_already_connected
                - suppression_protected
                - program_not_launchable
                - database_unavailable
                - rate_limited
                - idempotency_key_reuse
                - idempotency_in_progress
              type: string
            doc_url:
              description: Optional link to the relevant documentation.
              format: uri
              type: string
            hint:
              description: Optional one-sentence remediation.
              type: string
            message:
              description: Human-readable explanation. Never a contract.
              type: string
            type:
              description: Broad, machine-routable category derived from the HTTP status.
              examples:
                - invalid_request
                - authentication_error
                - rate_limit
              type: string
          required:
            - type
            - code
            - message
          title: ApiErrorDetail
          type: object
      required:
        - error
      title: ApiError
      type: object
    IntegrationProvider:
      type: string
      minLength: 1
      title: IntegrationProvider
      description: The connected app for this integration.
      examples:
        - slack
    Integration:
      type: object
      properties:
        config:
          anyOf:
            - $ref: '#/components/schemas/IntegrationConfig'
            - type: 'null'
          description: >-
            Per-connection settings: notification toggles, the Slack channel,
            and the CRM sync-direction settings.
        createdAt:
          description: Connect instant (RFC 3339).
        id:
          type: string
          description: The integration id.
          examples:
            - intc_f7g3h9j5k1l7m3n9p5q1r7s3
        lastError:
          anyOf:
            - type: string
            - type: 'null'
          description: The last import or auth failure message, or null.
          examples:
            - 'invalid_grant: refresh token revoked'
        lastSyncAt:
          description: When the last import ran (RFC 3339), or null before the first.
        object:
          const: integration
          default: integration
          description: Always "integration".
        provider:
          $ref: '#/components/schemas/IntegrationProvider'
        status:
          $ref: '#/components/schemas/IntegrationStatus'
        updatedAt:
          description: Last-update instant (RFC 3339).
      required:
        - config
        - id
        - lastError
        - provider
        - status
      title: Integration
    IntegrationConfig:
      type: object
      properties:
        notify:
          $ref: '#/components/schemas/IntegrationNotifyConfig'
        pushOut:
          type: boolean
          description: >-
            Whether to push outcomes (replies) back to this app's timeline; on
            unless set to false.
        slackChannelId:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Slack only: the channel id notifications post to; required before
            any notification sends.
          examples:
            - C0123ABC456
        slackChannelName:
          anyOf:
            - type: string
            - type: 'null'
          description: 'Slack only: the channel''s display name.'
          examples:
            - '#wins'
        syncIn:
          type: boolean
          description: >-
            Whether to pull leads/deals (CRM) or meetings (scheduling tools) in
            from this app; on unless set to false.
      title: IntegrationConfig
    IntegrationStatus:
      enum:
        - pending
        - active
        - error
        - revoked
      type: string
      title: IntegrationStatus
      description: >-
        Connection status: `pending` (authorization not finished yet), `active`,
        `error` (the credential needs a reconnect), or `revoked`.
      examples:
        - active
    IntegrationNotifyConfig:
      type: object
      properties:
        campaignAutoPaused:
          type: boolean
          description: >-
            Notify when a campaign auto-pauses (e.g. its bounce-rate gate
            trips); on by default.
        meetingBooked:
          type: boolean
          description: Notify when a meeting is booked; on by default.
        positiveReply:
          type: boolean
          description: >-
            Notify when an incoming reply is classified as positive; on by
            default.
        reply:
          type: boolean
          description: Notify on every incoming reply; off by default.
      title: IntegrationNotifyConfig
      description: Per-outcome notification toggles.
  securitySchemes:
    bearerAuth:
      description: >-
        Org-scoped Norbelys API key. Create one in Settings → API keys and send
        it as `Authorization: Bearer ak_…`.
      scheme: bearer
      type: http

````