openapi: 3.0.3
info:
  title: DialCode API
  description: >
    Phone validation with scam intelligence. Offline validation (E.164, country,
    line type, area, carrier) plus a risk layer no numverify-clone has: country
    scam profiles, per-number risk signals, and FTC / FCC complaint history for
    US numbers. Free 1,000 lookups/month.
  version: "1.3"
  contact:
    url: https://dialcode.app/api
servers:
  - url: https://dialcode.app
security:
  - ApiKeyAuth: []
paths:
  /v1/phone/{number}:
    get:
      summary: Validate a phone number and get scam intelligence
      description: >
        `number` is any phone number in international format. A literal `+` in
        the path works as-is; digits without `+` are parsed as international.
        National-format input (8663982896, 866-398-2896) carries no country code
        and needs the `country` parameter to resolve.
      parameters:
        - name: number
          in: path
          required: true
          schema: { type: string }
          example: "+639171234567"
        - name: country
          in: query
          required: false
          description: >
            ISO 3166-1 alpha-2 code (case-insensitive) to read a national-format
            number against. An unknown code returns 400 invalid_country and does
            not spend quota.
          schema: { type: string, minLength: 2, maxLength: 2 }
          example: "US"
      responses:
        "200":
          description: "Lookup result (also returned for well-formed but invalid numbers, with valid: false)"
          headers:
            X-RateLimit-Limit: { schema: { type: integer }, description: Monthly quota }
            X-RateLimit-Remaining: { schema: { type: integer } }
            X-RateLimit-Reset: { schema: { type: integer }, description: Unix epoch of next monthly reset }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/LookupResult" }
        "400": { $ref: "#/components/responses/UnparseableNumber" }
        "401": { $ref: "#/components/responses/InvalidApiKey" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /v1/account/usage:
    get:
      summary: Quota state for the calling key (free, consumes no quota)
      responses:
        "200":
          description: Current usage
          content:
            application/json:
              schema:
                type: object
                properties:
                  key_prefix: { type: string, example: dc_1a2b3c4d }
                  monthly_quota: { type: integer, example: 1000 }
                  used: { type: integer, example: 250 }
                  remaining: { type: integer, example: 750 }
                  resets_on: { type: string, format: date, example: "2026-08-01" }
        "401": { $ref: "#/components/responses/InvalidApiKey" }
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
  responses:
    InvalidApiKey:
      description: Missing or invalid X-Api-Key header
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    UnparseableNumber:
      description: Input has fewer than 4 digits, or country is not a known ISO 3166-1 alpha-2 code (invalid_country)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    RateLimited:
      description: Monthly quota exhausted (quota_exceeded) or per-minute limit hit (rate_limited)
      headers:
        Retry-After: { schema: { type: integer }, description: Seconds until you may retry }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Error:
      type: object
      properties:
        error: { type: string, example: quota_exceeded }
        message: { type: string, example: "Monthly quota exhausted. It resets on the 1st (UTC) - see Retry-After." }
    LookupResult:
      type: object
      properties:
        valid: { type: boolean }
        e164: { type: string, nullable: true, example: "+639171234567" }
        hint:
          type: string
          description: Present only when valid is false - what would make the number resolve
          example: "Looks like a US national number - pass country=US, or send it in E.164 form (+18663982896)."
        country:
          type: object
          nullable: true
          properties:
            code: { type: string, example: PH }
            dial_code: { type: string, example: "+63" }
            name: { type: string, example: Philippines }
            timezone: { type: string, nullable: true, example: Asia/Manila }
        formats:
          type: object
          nullable: true
          properties:
            national: { type: string, example: "0917 123 4567" }
            international: { type: string, example: "+63 917 123 4567" }
        carrier:
          type: string
          nullable: true
          example: Globe
          description: Range-holder carrier (allocation-time, not current after porting). Null for US/CA and most landlines.
        line_type: { type: string, nullable: true, example: mobile }
        area:
          type: object
          nullable: true
          properties:
            code: { type: string, example: "917" }
            name: { type: string, example: Manila }
        risk:
          type: object
          nullable: true
          properties:
            scam_risk: { type: string, enum: [low, medium, high, unknown] }
            scam_types: { type: array, items: { type: string }, example: [wangiri, romance] }
            advice_url: { type: string, nullable: true }
            complaints:
              type: object
              nullable: true
              description: FTC Do Not Call complaint history (US numbers only)
              properties:
                total: { type: integer }
                robocalls: { type: integer }
                first_reported_on: { type: string, format: date }
                last_reported_on: { type: string, format: date }
                categories:
                  type: array
                  items:
                    type: object
                    properties:
                      code: { type: string, example: imposter }
                      count: { type: integer }
                geography:
                  type: object
                  nullable: true
                  properties:
                    states: { type: integer, description: Distinct US states that reported this number }
                    top:
                      type: array
                      items:
                        type: object
                        properties:
                          state: { type: string, example: Florida }
                          count: { type: integer }
                source: { type: string }
            fcc_complaints:
              type: object
              nullable: true
              description: FCC consumer complaints, independent second US source
              properties:
                total: { type: integer }
                first_reported_on: { type: string, format: date }
                last_reported_on: { type: string, format: date }
                source: { type: string }
            signals:
              type: array
              items:
                type: object
                properties:
                  code: { type: string, example: voip_line }
                  note: { type: string }
