Skip to content

Add CNAM to REST APIs #634

Description

@khadijagardezi

references https://github.com/signalwire/cloud-product/issues/20618

Summary

Customers can now set, read, and clear the outbound caller ID name (CNAM) on a phone number over the REST API. Previously CNAM could only be set from the dashboard (Phone Numbers → a number → Caller ID Name).

A CNAM is not applied instantly. POST queues the requested name for a compliance review, and the response comes back with status: "pending". Background processing then moves the request to approved (the name is set at the carrier and on the number),
in_review (a human needs to look at it), rejected, or failed. Clients poll GET to see where a request landed.

Note

  • CNAM is only available on numbers whose carrier supports it (currently 10DLC numbers). Any request against another number is rejected with 422.
  • GET returns the most recent CNAM request for the number, not the name currently live at the carrier. The live value is the cnam attribute on the phone number object (GET /api/relay/rest/phone_numbers/{id}). These differ while a request is pending or after one is rejected.
  • This is unrelated to the existing CNAM lookup endpoint (GET /api/relay/rest/lookup/phone_number/{e164}), which reads a third party's caller ID name rather than setting your own.

Authentication & scope

  • Auth: HTTP Basic auth: Project ID as the username, API token as the password.
  • Required scope: numbers. A token without it receives 401 Unauthorized (plain-text body Unauthorized, not a JSON error envelope).
  • Base URL: https://<your-space>.signalwire.com

Scoped to the caller's project: phone_number_id values belonging to another project return 404 Not Found. An unverified company also receives 401 with{"message": "Please validate a phone number to access your account."}.

Endpoints

Method Path Description
GET /api/relay/rest/phone_numbers/{phone_number_id}/cnam Fetch the most recent CNAM request for the number
POST /api/relay/rest/phone_numbers/{phone_number_id}/cnam Request a caller ID name for the number
DELETE /api/relay/rest/phone_numbers/{phone_number_id}/cnam Clear the caller ID name on the number

Limitations

  • CNAM is only supported on numbers from carriers that offer it. Other numbers get 422 with Caller ID name isn't available for this number.
  • The name is capped at 15 characters after normalization → 422.
  • Allowed characters after normalization: A–Z, 0–9, space, and & ' . , -. The first character must be a letter or digit → 422.
  • Names are normalized before validation and storage: control characters removed, runs of whitespace collapsed, then upper-cased. " acme plumbing " is stored and returned as "ACME PLUMBING". Validate against the normalized form, not the input.
  • POST is not instant. 201 Created means queued for review, not applied. Poll GET until status is approved, rejected, or failed.
  • One live request per number: a new POST supersedes any earlier pending orin_review request for that number. Re-posting a name that is already approved returns the existing approved request rather than re-running review.
  • DELETE requires something to clear — an applied name or a request awaiting a decision. Otherwise 422 with There is no caller ID name to clear for this number. Like POST, it is asynchronous: 204 means the clear was queued.
  • GET returns 404 for a number that has never had a CNAM requested.
  • reason is only populated for in_review, rejected, and failed requests; it is null for pending and approved.

Fields

CNAM object (returned by GET and POST)

Field Type Notes
type string Always "cnam".
id string (UUID) ID of the CNAM request.
phone_number_id string (UUID) The phone number this request belongs to.
name string The normalized caller ID name (upper-cased, max 15 chars).
status string One of pending, approved, in_review, rejected, failed. See below.
reason string | null Machine-readable reason code when the request was not approved.
required_action string | null Human-readable next step, when review produced one (e.g. what to verify or document).
created_at string (ISO 8601)
updated_at string (ISO 8601)

status values

Value Meaning
pending Queued for compliance review.
approved Approved and set at the carrier. This is the name callers see.
in_review Automated review could not approve it; awaiting a manual decision.
rejected Not allowed. Submit a different name.
failed Processing failed after retries. Safe to submit again.

reason codes

Code Meaning
offensive_language Contains language that can't be displayed on calls.
impersonation Appears to impersonate another person or organization.
unverified_brand Can't yet confirm the name belongs to the business.
implied_trusted_institution Implies a bank, government agency, or similar institution.
scam_wording Wording commonly associated with scam calls.
deceptive Misleading about who is calling.
unsupported_personal_name A personal name not supported by verified business details.
too_generic Too generic to identify the caller.
invalid_format Characters or formatting that can't be displayed.
unrelated_to_business Doesn't appear to relate to the verified business.
needs_documentation Documentation of authorization to use the name is required.
other_compliance_concern Didn't pass compliance review (unspecified).
processing_failed Accompanies status: "failed" — processing errored, retry.

Writable fields (POST request body)

Field Type Notes
name string Required. Desired caller ID name. Normalized to upper case; max 15 characters after normalization; A–Z 0–9, space, and & ' . , - only, starting with a letter or digit.

Request / response examples

Fetch the current CNAM request: GET /api/relay/rest/phone_numbers/{phone_number_id}/cnam

Request

curl -u "$PROJECT_ID:$API_TOKEN" \
  "https://example.signalwire.com/api/relay/rest/phone_numbers/1cf0a2a0-8a6b-4a7c-9d63-3d1b6f0f2f11/cnam"

Response 200 OK

{
  "type": "cnam",
  "id": "b6a4f0c2-3f1e-4c9a-9c1b-7f2e5a1d8c40",
  "phone_number_id": "1cf0a2a0-8a6b-4a7c-9d63-3d1b6f0f2f11",
  "name": "ACME PLUMBING",
  "status": "approved",
  "reason": null,
  "required_action": null,
  "created_at": "2026-06-24T14:02:11Z",
  "updated_at": "2026-06-24T14:04:53Z"
}

Response 200 OK (held for manual review)

{
  "type": "cnam",
  "id": "c81b25de-9f4a-4d2f-8e5c-0a6b1c2d3e4f",
  "phone_number_id": "1cf0a2a0-8a6b-4a7c-9d63-3d1b6f0f2f11",
  "name": "ACME BANK",
  "status": "in_review",
  "reason": "implied_trusted_institution",
  "required_action": "Provide documentation showing you are authorized to use this name.",
  "created_at": "2026-06-24T15:10:02Z",
  "updated_at": "2026-06-24T15:10:19Z"
}

Request a caller ID name: POST /api/relay/rest/phone_numbers/{phone_number_id}/cnam

Request

{
  "name": "ACME PLUMBING"
}

Response 201 Created

{
  "type": "cnam",
  "id": "b6a4f0c2-3f1e-4c9a-9c1b-7f2e5a1d8c40",
  "phone_number_id": "1cf0a2a0-8a6b-4a7c-9d63-3d1b6f0f2f11",
  "name": "ACME PLUMBING",
  "status": "pending",
  "reason": null,
  "required_action": null,
  "created_at": "2026-06-24T14:02:11Z",
  "updated_at": "2026-06-24T14:02:11Z"
}

Clear the caller ID name: DELETE /api/relay/rest/phone_numbers/{phone_number_id}/cnam

Request

curl -X DELETE -u "$PROJECT_ID:$API_TOKEN" \
  "https://example.signalwire.com/api/relay/rest/phone_numbers/1cf0a2a0-8a6b-4a7c-9d63-3d1b6f0f2f11/cnam"

Response 204 No Content (no body; the clear is queued)

Error responses

Validation failures (422) return the standard Relay REST error envelope — an errors
array of objects with detail, status, title, and code:

{
  "errors": [
    {
      "detail": "Name is too long (maximum is 15 characters)",
      "status": "422",
      "title": "Invalid Attribute",
      "code": "422"
    }
  ]
}

Auth and lookup failures are plain-text, matching the rest of Relay REST:
401 returns Unauthorized, 404 returns Not Found.

Error codes to document

HTTP Code When it happens
401 Missing or invalid Basic credentials, or a token without the numbers scope. Plain-text body Unauthorized.
401 Company is not verified: {"message": "Please validate a phone number to access your account."}
404 phone_number_id doesn't exist or isn't in the caller's project (all three endpoints), or — on GET — the number has never had a caller ID name requested.
422 422 POST / DELETE: the number's carrier doesn't support CNAM — Caller ID name isn't available for this number.
422 422 POST: name missing or empty — Name can't be blank
422 422 POST: normalized name exceeds 15 characters — Name is too long (maximum is 15 characters)
422 422 POST: normalized name has disallowed characters, or doesn't start with a letter or digit — Name can only contain letters, numbers, spaces, and & ' . , -
422 422 POST: the request could not be recorded, safe to retry — Could not queue the caller ID name for review
422 422 DELETE: no applied name and no request awaiting a decision — There is no caller ID name to clear for this number.

All of these reuse the generic 422 code, so no new entries are needed in the public
REST error-code reference
. The reason codes in the CNAM object are a separate
enumeration and should be documented on this page (table above), not in the error-code
reference.

OpenAPI Spec

openapi: 3.0.3
info:
  title: SignalWire Relay REST — Caller ID Name (CNAM)
  version: "1.0.0"
servers:
  - url: https://{space}.signalwire.com/api/relay/rest
    variables:
      space:
        default: example
paths:
  /phone_numbers/{phone_number_id}/cnam:
    parameters:
      - name: phone_number_id
        in: path
        required: true
        description: ID of the phone number.
        schema:
          type: string
          format: uuid
    get:
      summary: Get the caller ID name request for a phone number
      description: >
        Returns the most recent CNAM request for the number. This is not necessarily the
        name live at the carrier — read `cnam` on the phone number object for that.
      operationId: getPhoneNumberCnam
      tags: [Caller ID Name]
      security:
        - basicAuth: []
      responses:
        "200":
          description: The most recent CNAM request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Cnam"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      summary: Request a caller ID name for a phone number
      description: >
        Queues the name for compliance review. A `201` means the request was accepted for
        review, not that the name is live. Poll `GET` until `status` is `approved`,
        `rejected`, or `failed`. Supersedes any earlier request still awaiting a decision.
      operationId: createPhoneNumberCnam
      tags: [Caller ID Name]
      security:
        - basicAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CnamRequestBody"
      responses:
        "201":
          description: The name was queued for review.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Cnam"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      summary: Clear the caller ID name on a phone number
      description: >
        Queues removal of the caller ID name at the carrier and cancels any request still
        awaiting a decision.
      operationId: deletePhoneNumberCnam
      tags: [Caller ID Name]
      security:
        - basicAuth: []
      responses:
        "204":
          description: The clear was queued.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: Project ID as the username, API token as the password. Requires the `numbers` scope.
  schemas:
    Cnam:
      type: object
      required: [type, id, phone_number_id, name, status, created_at, updated_at]
      properties:
        type:
          type: string
          enum: [cnam]
        id:
          type: string
          format: uuid
        phone_number_id:
          type: string
          format: uuid
        name:
          type: string
          maxLength: 15
          example: ACME PLUMBING
        status:
          type: string
          enum: [pending, approved, in_review, rejected, failed]
        reason:
          type: string
          nullable: true
          enum:
            - offensive_language
            - impersonation
            - unverified_brand
            - implied_trusted_institution
            - scam_wording
            - deceptive
            - unsupported_personal_name
            - too_generic
            - invalid_format
            - unrelated_to_business
            - needs_documentation
            - other_compliance_concern
            - processing_failed
        required_action:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    CnamRequestBody:
      type: object
      required: [name]
      properties:
        name:
          type: string
          description: >
            Desired caller ID name. Normalized before validation — control characters
            removed, whitespace collapsed, upper-cased.
          maxLength: 15
          pattern: "^[A-Z0-9][A-Z0-9 &'.,\\-]*$"
          example: ACME PLUMBING
    Error:
      type: object
      properties:
        errors:
          type: array
          items:
            type: object
            properties:
              detail:
                type: string
              status:
                type: string
                example: "422"
              title:
                type: string
                example: Invalid Attribute
              code:
                type: string
                example: "422"
  responses:
    Unauthorized:
      description: Missing or invalid credentials, missing `numbers` scope, or unverified company.
      content:
        text/plain:
          schema:
            type: string
            example: Unauthorized
    NotFound:
      description: The phone number is not in this project, or has no CNAM request.
      content:
        text/plain:
          schema:
            type: string
            example: Not Found
    ValidationError:
      description: The request could not be processed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions