Numbers Online Outbound API

Outbound pre-call checks (scrub + call-provenance) and number enrollment for spoofing-defense

Operations 4

POST /api/v1/outbound/lookup Outbound pre-call check (scrub + provenance) #
POST /api/v1/outbound/enroll Enroll a number for call-provenance #
POST /api/v1/precall/lookup Outbound pre-call check — original spelling (permanent alias) #
POST /api/v1/precall/enroll Enroll a number — original spelling (permanent alias) #

Documentation

Specifications

Other Resources

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/numbers-online:numbers-online-outbound-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

numbers-online-outbound-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Numbers Online Phone Intelligence Outbound API
  description: Phone number parsing, validation, and inbound caller-intelligence as a supplementary signal.
  version: 1.0.0
  contact:
    name: Phone Numbers Online
    url: https://numbers.online
servers:
- url: https://numbers.online
  description: Production server
- url: http://localhost:3000
  description: Development server
tags:
- name: Outbound
  description: Outbound pre-call checks (scrub + call-provenance) and number enrollment for spoofing-defense
paths:
  /api/v1/outbound/lookup:
    post:
      tags:
      - Outbound
      summary: Outbound pre-call check (scrub + provenance)
      description: 'An outbound integration''s pre-call check on a destination. Returns the M1 pre-call scrub on `to` (SUPPRESS | NO_MATCH | UNKNOWN) plus three more supplementary signals: a compliance signal; an outbound `dial_risk` read — a low-confidence estimate of the cost/abuse risk of DIALING `to` (premium / satellite / international / IRSF-cover structural prior, plus any dynamic fraud observations); and a `cost_estimate` — the indicative RETAIL cost to reach `to`, priced for BOTH channels (voice per minute, SMS per message) from aggregated provider list-price decks, with a per-provider breakdown. All are supplementary signals, NEVER a consent grant or a block verdict: NO_MATCH is not permission to call and a dial_risk level is not a block — you remain responsible for your own lawful basis and dialing decision. For an ENROLLED caller (a verified business number you enrolled via /api/v1/outbound/enroll), it also records a hashed, short-TTL (from -> to) call-provenance edge: a self-incriminating record of who you actually dialled, used at report time to hold your number accountable for calls it DID place and to shield it from reports about calls it did NOT place (spoofing). Both ends are hashed, server-only, and never surfaced. Non-enrolled callers still get the scrub; no edge is written. Requires an API key with the `precall` use case. Free (bundled); not metered. Supplementary signal only — no per-call verdict. This is the canonical spelling — the outbound mirror of /api/v1/inbound/lookup; the original /api/v1/precall/lookup stays a permanent alias served by the same handler.'
      operationId: precallLookup
      security:
      - ApiKeyAuth: []
      - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - to
              properties:
                to:
                  type: string
                  description: The destination number being called.
                  example: '+14155551212'
                from:
                  type: string
                  description: Your own calling number. A provenance edge is recorded only when this is a number you have enrolled.
                  example: '+442071838750'
                context:
                  type: string
                  enum:
                  - outbound_voice
                  - outbound_sms
                  description: 'The channel you are about to use. Drives which suppression list is scrubbed: outbound_sms scrubs SMS preferences, anything else scrubs voice. Echoed back as `dnc_channel`.'
      responses:
        '200':
          description: Pre-call scrub (+ provenance edge when enrolled)
          content:
            application/json:
              schema:
                type: object
                properties:
                  schema_version:
                    type: string
                    example: '2026-06-23'
                  to:
                    type: string
                  dnc:
                    type: string
                    enum:
                    - SUPPRESS
                    - NO_MATCH
                    - UNKNOWN
                  dnc_channel:
                    type: string
                    enum:
                    - voice
                    - sms
                    description: Which suppression list the `dnc` answer was scrubbed against (derived from `context`).
                  dnc_note:
                    type: string
                  compliance:
                    type:
                    - object
                    - 'null'
                    properties:
                      dnc_status:
                        type: string
                      reassigned_status:
                        type: string
                  dial_risk:
                    type:
                    - object
                    - 'null'
                    description: 'Outbound dial-risk signal: the cost/abuse risk of DIALING `to` (structural prior plus any dynamic fraud observations). Supplementary signal, not a block verdict. Null if scoring failed (fail-open).'
                    properties:
                      risk:
                        type: integer
                        description: Risk score 0–100.
                        example: 6
                      level:
                        type: string
                        enum:
                        - low
                        - medium
                        - high
                        description: 'Banded risk: low (<40), medium (40–69), high (≥70).'
                      model:
                        type: string
                        enum:
                        - dial_structural
                        description: 'Scoring model (§ Risk models): the outbound structural model — NOT the inbound reputation blend.'
                      structural_type:
                        type:
                        - string
                        - 'null'
                        enum:
                        - premium_prs
                        - satellite
                        - intl_network
                        - intl_premium
                        - freephone
                        - unallocated_dialable
                        - ordinary
                        description: The structural class the destination resolved to (null if no range matched).
                      reason_codes:
                        type: array
                        items:
                          type: string
                        description: Legible drivers, e.g. `structural:intl_premium` or `dynamic:<source_class>`.
                        example:
                        - structural:intl_premium
                      note:
                        type: string
                  cost_estimate:
                    type:
                    - object
                    - 'null'
                    description: Indicative RETAIL cost to reach `to`, from aggregated provider price-list decks (longest-prefix match, filtered by the destination's parsed line type). Returns BOTH channels regardless of `context` — `voice` priced per minute, `sms` per message — each null when no deck covers that channel; the whole object is null when neither does (fail-open). All amounts are decimal USD strings. Supplementary signal, never wholesale interconnect cost and never a quote.
                    properties:
                      currency:
                        type: string
                        enum:
                        - USD
                      note:
                        type: string
                      voice:
                        allOf:
                        - $ref: '#/components/schemas/ChannelCost'
                        description: Per-minute voice rate, or null if no voice deck covers the destination.
                      sms:
                        allOf:
                        - $ref: '#/components/schemas/ChannelCost'
                        description: Per-message SMS rate, or null if no SMS deck covers the destination.
                  enrolled:
                    type: boolean
                  provenance_recorded:
                    type: boolean
                  provenance_note:
                    type: string
                  edge_id:
                    type: string
                  ttl_seconds:
                    type: integer
                    example: 604800
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/outbound/enroll:
    post:
      tags:
      - Outbound
      summary: Enroll a number for call-provenance
      description: 'Owner-only control that enrolls (or, with `enrolled: false`, revokes) a verified business number you own for call-provenance. Enrollment is your attestation that this number only places calls after a Numbers Online outbound pre-call lookup, so the absence of a matching pre-call edge becomes a supplementary signal that a complaint may concern a spoofed call rather than one you placed. Only numbers bound to your own account can be enrolled; revocable and abuse-monitored. Requires an account-level API key with the `precall` use case (not a tenant sub-key). Supplementary signal only — not a compliance determination. This is the canonical spelling; the original /api/v1/precall/enroll stays a permanent alias served by the same handler. The same toggle is also available resource-shaped: GET /api/v1/account/phones lists your numbers, PATCH /api/v1/account/phones/{e164} flips enrollment.'
      operationId: precallEnroll
      security:
      - ApiKeyAuth: []
      - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - number
              properties:
                number:
                  type: string
                  example: '+442071838750'
                enrolled:
                  type: boolean
                  default: true
                  description: Set false to revoke enrollment.
      responses:
        '200':
          description: Enrollment updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  schema_version:
                    type: string
                    example: '2026-06-14'
                  ok:
                    type: boolean
                  number:
                    type: string
                  enrolled:
                    type: boolean
                  attestation:
                    type: string
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Tenant sub-keys cannot manage enrollment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Number not bound to your account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/v1/precall/lookup:
    post:
      tags:
      - Outbound
      summary: Outbound pre-call check — original spelling (permanent alias)
      description: Permanent alias of POST /api/v1/outbound/lookup — the same handler under the original path, kept forever (it is baked into published guides and agent prompts already in the field). Request, response, auth, billing, and limits are identical; see the canonical entry. Prefer /api/v1/outbound/lookup in new integrations.
      operationId: precallLookupAlias
      deprecated: true
      security:
      - ApiKeyAuth: []
      - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - to
              properties:
                to:
                  type: string
                  example: '+14155551212'
                from:
                  type: string
                  example: '+442071838750'
                context:
                  type: string
                  enum:
                  - outbound_voice
                  - outbound_sms
      responses:
        '200':
          description: Identical to POST /api/v1/outbound/lookup (same handler).
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/precall/enroll:
    post:
      tags:
      - Outbound
      summary: Enroll a number — original spelling (permanent alias)
      description: Permanent alias of POST /api/v1/outbound/enroll — the same handler under the original path, kept forever. Request, response, auth, and limits are identical; see the canonical entry. Prefer /api/v1/outbound/enroll (or the resource form PATCH /api/v1/account/phones/{e164}) in new integrations.
      operationId: precallEnrollAlias
      deprecated: true
      security:
      - ApiKeyAuth: []
      - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - number
              properties:
                number:
                  type: string
                  example: '+442071838750'
                enrolled:
                  type: boolean
                  default: true
      responses:
        '200':
          description: Identical to POST /api/v1/outbound/enroll (same handler).
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Tenant sub-keys cannot manage enrollment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Number not bound to your account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    ChannelCost:
      type: object
      description: 'Indicative RETAIL cost to reach a destination on ONE channel, aggregated across provider price-list decks. All amounts are decimal USD strings (money is integer micro-USD internally). `unit` says whether the figures are per minute (voice) or per message (sms). The channel-relevant lane fields (voice: `international`/`local`; sms: `person`/`application`) appear at the aggregate level and per provider; `breakdown` lists only publicly-displayable providers (cheapest avg first), with anonymous sources folded into the aggregate numbers only.'
      properties:
        unit:
          type: string
          enum:
          - per_minute
          - per_message
        min_usd:
          type: string
          example: '0.0072'
        avg_usd:
          type: string
          example: '0.0146'
        max_usd:
          type: string
          example: '0.32'
        network_type:
          type:
          - string
          - 'null'
          description: The line-type filter that was applied (e.g. `mobile`, `premium`), or null when unfiltered.
          example: mobile
        providers:
          type: integer
          description: Distinct providers behind the aggregate (named + anonymous).
          example: 3
        rate_count:
          type: integer
          description: Underlying rate entries aggregated.
          example: 5
        international:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'Voice only: cross-provider international-lane aggregate.'
        local:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'Voice only: cross-provider in-country-lane aggregate.'
        person:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'SMS only: cross-provider P2P aggregate.'
        application:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'SMS only: cross-provider A2P aggregate.'
        breakdown:
          type: array
          items:
            $ref: '#/components/schemas/ProviderCost'
    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
          description: Legacy field emitted ONLY by the pre-v1 parse family (/api/parse, /api/parse/bulk, /api/countries). /v1 routes return only `error` — do not depend on `success` there.
        error:
          type: string
          description: Human-readable error message (prose — switch on `code`, not on this string).
        code:
          type: string
          enum:
          - missing_key
          - invalid_key
          - use_case_forbidden
          - rate_limited_key
          - rate_limited_pool
          - rate_limited_ip
          - signature_invalid
          - insufficient_balance
          - account_suspended
          - paid_verification_required
          - receipt_invalid
          description: 'Stable machine-readable error code (added 2026-06-12, additive — older errors may omit it). See the "Error codes" section in the API description for the full table. A valid key on the wrong use case returns 403 use_case_forbidden (not 401): re-authing will not fix a permissions problem.'
        retry_after_seconds:
          type: integer
          description: 'Present on 429s: seconds until the window resets (mirrors the Retry-After header).'
      required:
      - error
    ProviderCost:
      type: object
      description: 'One named, publicly-displayable provider''s slice of the aggregate. Anonymous sources never appear here — they stay inside the channel-level numbers only. The lane fields present depend on the channel: a voice cost carries `international`/`local`, an SMS cost carries `person`/`application`; each is null when that provider''s decks do not split that way.'
      properties:
        name:
          type: string
          example: DIDWW
        domain:
          type:
          - string
          - 'null'
          example: didww.com
        min_usd:
          type: string
          example: '0.0072'
        avg_usd:
          type: string
          example: '0.0146'
        max_usd:
          type: string
          example: '0.32'
        international:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'Voice: international lanes (default + origin-based).'
        local:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'Voice: in-country lanes.'
        person:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'SMS: person-originated (P2P) decks.'
        application:
          allOf:
          - $ref: '#/components/schemas/LaneUsd'
          description: 'SMS: application-originated (A2P) decks.'
    LaneUsd:
      type: object
      description: A min/avg/max spread for one pricing lane, as decimal USD strings.
      properties:
        min_usd:
          type: string
          example: '0.0072'
        avg_usd:
          type: string
          example: '0.0146'
        max_usd:
          type: string
          example: '0.32'
  responses:
    RateLimited:
      description: Per-key rate limit exceeded. Retry after the number of seconds in the Retry-After header.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for authentication
    BearerAuth:
      type: http
      scheme: bearer
      description: Bearer token authentication
    CidQueryKeyAuth:
      type: apiKey
      in: query
      name: key
      description: API key in the `?key=` query param. Accepted by the header-less PBX endpoint GET /api/v1/cid/{number} and by the webhook adapters POST /api/v1/integrations/retell/inbound, POST /api/v1/integrations/vapi/tool, and POST /api/v1/sbc/redirect, whose upstream platforms set only a static webhook URL and cannot send an Authorization/X-API-Key header. The key can leak into access logs — use a dedicated, rotated key, and prefer header auth wherever the client supports it.