> ## 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.

# Audit a program before launch

> Inspect one saved campaign without sending anything. Expands the full cadence and mailboxes, checks launch completeness, Liquid in subject/preheader/body, tracking, unsubscribe, and visible link labels; then renders every arm against a bounded sample of real leads and reports merge-field coverage. Run this after edits and immediately before launch.



## OpenAPI

````yaml /openapi.json get /programs/{id}/audit
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:
  /programs/{id}/audit:
    get:
      tags:
        - Programs
      summary: Audit a program before launch
      description: >-
        Inspect one saved campaign without sending anything. Expands the full
        cadence and mailboxes, checks launch completeness, Liquid in
        subject/preheader/body, tracking, unsubscribe, and visible link labels;
        then renders every arm against a bounded sample of real leads and
        reports merge-field coverage. Run this after edits and immediately
        before launch.
      operationId: programs.audit
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            description: The program id to audit.
        - name: personId
          in: query
          schema:
            type: string
            description: >-
              Render every saved arm against this person instead of sampling
              enrolled leads.
          allowEmptyValue: true
          allowReserved: true
        - name: sampleSize
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 10
            description: >-
              Number of enrolled people to test-render, 1-10. Defaults to 3;
              ignored when personId is given.
            examples:
              - 3
          allowEmptyValue: true
          allowReserved: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProgramAuditResult'
        '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.programs.audit("prg_y3rgwmhkbgxx");

            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.ProgramsApi(client)
                result = api.programs_audit("prg_y3rgwmhkbgxx")
                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.ProgramsAPI.ProgramsAudit(ctx,
            "prg_y3rgwmhkbgxx").Execute()
        - label: Ruby
          lang: ruby
          source: |-
            require "norbelys"

            Norbelys.configure { |c| c.access_token = ENV["NORBELYS_API_KEY"] }
            api = Norbelys::ProgramsApi.new
            result = api.programs_audit("prg_y3rgwmhkbgxx")
            puts result
        - label: CLI
          lang: bash
          source: norbelys programs audit prg_y3rgwmhkbgxx
        - label: curl
          lang: bash
          source: |-
            curl -X GET "https://api.norbelys.com/v1/programs/{id}/audit" \
              -H "Authorization: Bearer $NORBELYS_API_KEY"
components:
  schemas:
    ProgramAuditResult:
      type: object
      properties:
        coverage:
          type: array
          items:
            $ref: '#/components/schemas/ProgramAuditFieldCoverage'
          description: Real fill rates for recipient merge tags over the sample.
        issues:
          type: array
          items:
            $ref: '#/components/schemas/ProgramAuditIssue'
          description: Blocking errors followed by advisory warnings and information.
        previews:
          type: array
          items:
            $ref: '#/components/schemas/ProgramAuditPreview'
          description: >-
            One successful exact render per arm, using real people and the
            send-time renderer.
        programId:
          type: string
          description: The audited program id.
        programName:
          type: string
          description: The campaign display name.
        ready:
          type: boolean
          description: True when the audit found no blocking errors.
        renderAttempts:
          type: integer
          minimum: 0
          description: Person × arm render attempts performed.
        renderErrors:
          type: integer
          minimum: 0
          description: Render attempts that failed.
        sampledPeople:
          type: integer
          minimum: 0
          description: Real people used for rendering and field coverage.
        status:
          enum:
            - draft
            - active
            - paused
            - done
          type: string
          description: Current campaign lifecycle status.
        summary:
          $ref: '#/components/schemas/ProgramAuditSummary'
      required:
        - coverage
        - issues
        - previews
        - programId
        - programName
        - ready
        - renderAttempts
        - renderErrors
        - sampledPeople
        - status
        - summary
      title: ProgramAuditResult
    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
    ProgramAuditFieldCoverage:
      type: object
      properties:
        code:
          type: string
          description: Recipient merge-tag code used by the saved campaign.
        percent:
          type: integer
          minimum: 0
          maximum: 100
          description: Percent of sampled people with a non-empty value.
        present:
          type: integer
          minimum: 0
          description: Sampled people with a value.
        total:
          type: integer
          minimum: 0
          description: People tested.
      required:
        - code
        - percent
        - present
        - total
      title: ProgramAuditFieldCoverage
    ProgramAuditIssue:
      type: object
      properties:
        code:
          enum:
            - no_steps
            - no_variants
            - missing_subject
            - missing_body
            - invalid_template
            - no_senders
            - no_leads
            - unsubscribe_disabled
            - tracking_disabled
            - missing_preheader
            - static_subject
            - static_preheader
            - visible_tracking_url
            - render_error
            - low_field_coverage
            - subject_too_long
            - preheader_too_long
          type: string
          description: Stable machine-readable finding code.
        field:
          enum:
            - program
            - subject
            - preheader
            - html
            - text
          type: string
          description: Content field involved, when the finding is field-specific.
        message:
          type: string
          description: Plain-language finding and suggested fix.
        severity:
          enum:
            - error
            - warning
            - info
          type: string
          description: Error blocks readiness; warning/info is advisory.
        step:
          type: integer
          minimum: 1
          description: 1-based cadence step, when applicable.
        variantId:
          type: string
          description: The saved arm involved, when applicable.
      required:
        - code
        - message
        - severity
      title: ProgramAuditIssue
    ProgramAuditPreview:
      type: object
      properties:
        email:
          anyOf:
            - type: string
            - type: 'null'
          description: The sampled person's email, or null.
        label:
          type: string
          description: The arm's display label.
        personId:
          type: string
          description: The person used for this exact render.
        preheader:
          type: string
          description: Rendered inbox preview text.
        step:
          type: integer
          minimum: 1
          description: 1-based cadence step.
        subject:
          type: string
          description: Rendered subject line.
        textPreview:
          type: string
          description: Short rendered body excerpt for a fast copy review.
        variantId:
          type: string
          description: The saved arm that was rendered.
      required:
        - email
        - label
        - personId
        - preheader
        - step
        - subject
        - textPreview
        - variantId
      title: ProgramAuditPreview
    ProgramAuditSummary:
      type: object
      properties:
        aiVariants:
          type: integer
          minimum: 0
          description: Saved arms with send-time AI enabled.
        leadCount:
          type: integer
          minimum: 0
          description: Enrolled leads.
        personalizedBodies:
          type: integer
          minimum: 0
          description: Arms whose body contains Liquid output or control tags.
        personalizedPreheaders:
          type: integer
          minimum: 0
          description: Arms whose preheader contains Liquid output or control tags.
        personalizedSubjects:
          type: integer
          minimum: 0
          description: Arms whose subject contains Liquid output or control tags.
        senderCount:
          type: integer
          minimum: 0
          description: Attached, non-paused mailboxes that can send.
        stepCount:
          type: integer
          minimum: 0
          description: Live cadence steps.
        trackClicks:
          type: boolean
          description: Whether click tracking is enabled.
        trackOpens:
          type: boolean
          description: Whether open tracking is enabled.
        unsubscribe:
          type: boolean
          description: Whether one-click unsubscribe is enabled.
        variantCount:
          type: integer
          minimum: 0
          description: Live saved arms.
        variantsByStep:
          type: array
          items:
            type: integer
            minimum: 0
          description: Arm count for each live cadence step, in order.
      required:
        - aiVariants
        - leadCount
        - personalizedBodies
        - personalizedPreheaders
        - personalizedSubjects
        - senderCount
        - stepCount
        - trackClicks
        - trackOpens
        - unsubscribe
        - variantCount
        - variantsByStep
      title: ProgramAuditSummary
      description: Compact campaign completeness and personalization rollup.
  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

````