{
  "openapi": "3.1.0",
  "info": {
    "title": "Boundstone API",
    "version": "0.1.0",
    "summary": "Trust-first phone, email and IP validation.",
    "description": "Every verification response lists checks.performed and checks.not_performed — what we verified, and what we didn't claim to. Capabilities we haven't built return explicit errors, never fabricated verdicts. Accuracy claims live in the public benchmarks, not in this document.",
    "contact": {
      "name": "Boundstone",
      "url": "https://boundstone.io"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://boundstone.io/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.boundstone.io",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Verify",
      "description": "Single-record verification (1 credit each)"
    },
    {
      "name": "Bulk",
      "description": "CSV batch jobs, processed asynchronously"
    },
    {
      "name": "Account",
      "description": "Balance, plan, and the credit ledger"
    },
    {
      "name": "System",
      "description": "Health and status, no auth"
    }
  ],
  "paths": {
    "/v1/verify/phone": {
      "post": {
        "tags": [
          "Verify"
        ],
        "summary": "Verify a phone number",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. A client-chosen key that makes retries safe: if a request with this key already produced a charged 200, the same response is replayed (with an Idempotent-Replayed: true header) and you are NOT charged again. Only successful responses are remembered; keys expire after 24h."
          }
        ],
        "description": "Default (1 credit): format, region and line-type from libphonenumber metadata (200+ countries); carrier, ported status and HLR liveness report as not_performed. Set hlr:true (5 credits, paid plans) to add a live HLR dip — carrier, ported status and reachability move into checks.performed and an hlr object is returned. An HLR dip the network can't determine abstains and is refunded (credits_charged: 0).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone"
                ],
                "properties": {
                  "phone": {
                    "type": "string",
                    "example": "+1 650 447 2983"
                  },
                  "country": {
                    "type": "string",
                    "example": "US",
                    "description": "Default region for national-format inputs"
                  },
                  "hlr": {
                    "type": "boolean",
                    "default": false,
                    "description": "Add a live HLR dip (carrier, ported status, reachability). Paid plans only; 5 credits."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per 10-second window on your plan"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees enough for one more request"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed phone — never charged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Out of credits, or hlr:true on a free plan (hlr_paid_only)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit hit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per 10-second window on your plan"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees enough for one more request"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            }
          },
          "501": {
            "description": "hlr:true requested but the HLR provider isn't configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/verify/email": {
      "post": {
        "tags": [
          "Verify"
        ],
        "summary": "Verify an email address",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. A client-chosen key that makes retries safe: if a request with this key already produced a charged 200, the same response is replayed (with an Idempotent-Replayed: true header) and you are NOT charged again. Only successful responses are remembered; keys expire after 24h."
          }
        ],
        "description": "Syntax, domain, live MX (over DNS-over-HTTPS), A-record fallback, disposable/role/free-provider flags. SMTP mailbox and catch-all probing report as not_performed — we don't guess deliverability we didn't test. 1 credit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "example": "name@company.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per 10-second window on your plan"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees enough for one more request"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed email — never charged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Out of credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit hit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per 10-second window on your plan"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees enough for one more request"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            }
          }
        }
      }
    },
    "/v1/verify/ip": {
      "post": {
        "tags": [
          "Verify"
        ],
        "summary": "Verify an IP address",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. A client-chosen key that makes retries safe: if a request with this key already produced a charged 200, the same response is replayed (with an Idempotent-Replayed: true header) and you are NOT charged again. Only successful responses are remembered; keys expire after 24h."
          }
        ],
        "description": "Syntax, version (4/6), and IANA range classification — public vs private/loopback/reserved/bogon. Geolocation, ASN, hosting/datacenter and proxy/VPN/Tor report as not_performed until licensed data ships. 1 credit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ip"
                ],
                "properties": {
                  "ip": {
                    "type": "string",
                    "example": "8.8.8.8"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verified",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per 10-second window on your plan"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees enough for one more request"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IpResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed ip — never charged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Out of credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit hit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per 10-second window on your plan"
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left in the current window"
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window frees enough for one more request"
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait before retrying"
              }
            }
          }
        }
      }
    },
    "/v1/bulk/phone": {
      "post": {
        "tags": [
          "Bulk"
        ],
        "summary": "Submit a bulk phone job (raw CSV body)",
        "description": "First CSV column is read; a header row is auto-detected. Credits (1/row) are reserved when the job is accepted and refunded for any row that fails to process. Free plan: 250 rows/job; paid: 10,000. Add ?hlr=true to run a live HLR dip per row (carrier, ported status, reachability): paid plans only, 5 credits/row, capped at 1,000 rows/job, with per-row abstain refunds. The results CSV then adds hlr_status,reachable,carrier,mccmnc,ported columns.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Two-letter default region for national-format numbers, e.g. US (phone only)"
          },
          {
            "name": "hlr",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Run a live HLR dip on every row. Paid plans only; 5 credits/row; max 1,000 rows/job."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. A client-chosen key that makes retries safe: if a request with this key already produced a charged 200, the same response is replayed (with an Idempotent-Replayed: true header) and you are NOT charged again. Only successful responses are remembered; keys expire after 24h."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "text/csv": {
              "schema": {
                "type": "string"
              },
              "example": "phone\n+1 650 447 2983\n+61 491 570 156"
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Empty CSV, row cap exceeded, or ?hlr=true on a non-phone job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits, or ?hlr=true on a free plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "CSV body over 1 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "?hlr=true but the HLR provider isn't configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bulk/email": {
      "post": {
        "tags": [
          "Bulk"
        ],
        "summary": "Submit a bulk email job (raw CSV body)",
        "description": "First CSV column is read; a header row is auto-detected. Credits (1/row) are reserved when the job is accepted and refunded for any row that fails to process. Free plan: 250 rows/job; paid: 10,000.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Two-letter default region for national-format numbers, e.g. US (phone only)"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. A client-chosen key that makes retries safe: if a request with this key already produced a charged 200, the same response is replayed (with an Idempotent-Replayed: true header) and you are NOT charged again. Only successful responses are remembered; keys expire after 24h."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "text/csv": {
              "schema": {
                "type": "string"
              },
              "example": "phone\n+1 650 447 2983\n+61 491 570 156"
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Empty CSV, row cap exceeded, or ?hlr=true on a non-phone job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits, or ?hlr=true on a free plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "CSV body over 1 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bulk/ip": {
      "post": {
        "tags": [
          "Bulk"
        ],
        "summary": "Submit a bulk ip job (raw CSV body)",
        "description": "First CSV column is read; a header row is auto-detected. Credits (1/row) are reserved when the job is accepted and refunded for any row that fails to process. Free plan: 250 rows/job; paid: 10,000.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Two-letter default region for national-format numbers, e.g. US (phone only)"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional. A client-chosen key that makes retries safe: if a request with this key already produced a charged 200, the same response is replayed (with an Idempotent-Replayed: true header) and you are NOT charged again. Only successful responses are remembered; keys expire after 24h."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "text/csv": {
              "schema": {
                "type": "string"
              },
              "example": "phone\n+1 650 447 2983\n+61 491 570 156"
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Empty CSV, row cap exceeded, or ?hlr=true on a non-phone job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits, or ?hlr=true on a free plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "CSV body over 1 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bulk/{id}": {
      "get": {
        "tags": [
          "Bulk"
        ],
        "summary": "Job status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such job on this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bulk/{id}/results.csv": {
      "get": {
        "tags": [
          "Bulk"
        ],
        "summary": "Job results as CSV (once done)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CSV results",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such job on this account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Job not done yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Results expired — kept 30 days after completion, then deleted (job metadata remains)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Account summary",
        "responses": {
          "200": {
            "description": "Account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account/ledger": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Credit ledger",
        "description": "Every grant, spend and refund, newest first (up to 100), with the current balance.",
        "responses": {
          "200": {
            "description": "Ledger",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ledger"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Liveness ping",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "ts": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Uptime feed",
        "description": "Same data as the public status page; CORS-open. state is operational | degraded | no_data.",
        "security": [],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "bs_live_…",
        "description": "Your API key as: Authorization: Bearer bs_live_…"
      }
    },
    "schemas": {
      "Checks": {
        "type": "object",
        "required": [
          "performed",
          "not_performed"
        ],
        "description": "The honesty contract, on every verification: what this result is based on, and what it is not. not_performed lists capabilities we did not run (licensed/unbuilt) rather than guessing a verdict.",
        "properties": {
          "performed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "format",
              "region",
              "line_type_metadata",
              "allocation"
            ]
          },
          "not_performed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "carrier_lookup",
              "ported_status",
              "hlr_liveness"
            ]
          }
        }
      },
      "Hlr": {
        "type": "object",
        "description": "Live HLR network dip result (opt-in). status 'undetermined' means the network couldn't answer — the dip was refunded (credits_charged: 0).",
        "required": [
          "status",
          "reachable",
          "current_carrier",
          "mccmnc",
          "ported",
          "roaming",
          "data_source"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "connected",
              "absent",
              "invalid",
              "undetermined"
            ],
            "example": "connected"
          },
          "reachable": {
            "type": "boolean",
            "nullable": true,
            "description": "true only when status is 'connected'; null when undetermined"
          },
          "current_carrier": {
            "type": "string",
            "nullable": true,
            "example": "T-Mobile US",
            "description": "Ported network when ported, else the original network"
          },
          "mccmnc": {
            "type": "string",
            "nullable": true,
            "example": "310260"
          },
          "ported": {
            "type": "boolean",
            "nullable": true,
            "example": true
          },
          "roaming": {
            "type": "boolean",
            "nullable": true,
            "example": false
          },
          "data_source": {
            "type": "string",
            "nullable": true,
            "example": "LIVE_HLR"
          }
        }
      },
      "PhoneResult": {
        "type": "object",
        "required": [
          "input",
          "valid",
          "line_type",
          "checks",
          "credits_charged"
        ],
        "properties": {
          "input": {
            "type": "string",
            "example": "+1 650 447 2983"
          },
          "valid": {
            "type": "boolean"
          },
          "e164": {
            "type": "string",
            "nullable": true,
            "example": "+16504472983"
          },
          "country": {
            "type": "string",
            "nullable": true,
            "example": "US"
          },
          "line_type": {
            "type": "string",
            "example": "mobile",
            "description": "libphonenumber metadata family; 'unknown' when invalid"
          },
          "national_format": {
            "type": "string",
            "nullable": true,
            "example": "(650) 447-2983"
          },
          "allocation": {
            "type": "object",
            "nullable": true,
            "description": "NANP only (null for other numbering plans, because we hold no allocation data for them). Tells a well-formed number from one that can exist: libphonenumber alone accepts unallocated NPA-NXX blocks and the reserved 555-01XX fictional range. When allocated is false, valid is false too.",
            "properties": {
              "allocated": {
                "type": "boolean",
                "example": true
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "unallocated_block",
                  "reserved_fictional",
                  null
                ],
                "description": "Why the number cannot exist. null when allocated."
              },
              "snapshot": {
                "type": "string",
                "example": "2026-07-30",
                "description": "The NANPA CO-code snapshot this verdict is true as of. Dated on purpose — allocation changes monthly."
              }
            }
          },
          "checks": {
            "type": "object",
            "required": [
              "performed",
              "not_performed"
            ],
            "description": "The honesty contract, on every verification: what this result is based on, and what it is not. not_performed lists capabilities we did not run (licensed/unbuilt) rather than guessing a verdict.",
            "properties": {
              "performed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "format",
                  "region",
                  "line_type_metadata",
                  "allocation"
                ]
              },
              "not_performed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "carrier_lookup",
                  "ported_status",
                  "hlr_liveness"
                ]
              }
            }
          },
          "hlr": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Hlr"
              }
            ],
            "nullable": true,
            "description": "Present only when the request set hlr:true. null on a metadata-only call."
          },
          "credits_charged": {
            "type": "integer",
            "example": 1,
            "description": "1 for a metadata call; 5 for an HLR dip; 0 when an HLR dip abstained and was refunded"
          }
        }
      },
      "EmailResult": {
        "type": "object",
        "required": [
          "input",
          "normalized",
          "valid_syntax",
          "checks",
          "credits_charged"
        ],
        "properties": {
          "input": {
            "type": "string",
            "example": "name@company.com"
          },
          "normalized": {
            "type": "string",
            "example": "name@company.com"
          },
          "valid_syntax": {
            "type": "boolean"
          },
          "domain": {
            "type": "string",
            "nullable": true,
            "example": "company.com"
          },
          "mx_found": {
            "type": "boolean"
          },
          "mx_records": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "a_fallback": {
            "type": "boolean",
            "description": "true when no MX but the domain has an A record"
          },
          "disposable": {
            "type": "boolean"
          },
          "role_account": {
            "type": "boolean",
            "description": "admin@, billing@, support@, …"
          },
          "free_provider": {
            "type": "boolean"
          },
          "checks": {
            "type": "object",
            "required": [
              "performed",
              "not_performed"
            ],
            "description": "The honesty contract, on every verification: what this result is based on, and what it is not. not_performed lists capabilities we did not run (licensed/unbuilt) rather than guessing a verdict.",
            "properties": {
              "performed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "format",
                  "region",
                  "line_type_metadata",
                  "allocation"
                ]
              },
              "not_performed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "carrier_lookup",
                  "ported_status",
                  "hlr_liveness"
                ]
              }
            }
          },
          "credits_charged": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "IpResult": {
        "type": "object",
        "required": [
          "input",
          "valid",
          "version",
          "classification",
          "is_public",
          "is_bogon",
          "checks",
          "credits_charged"
        ],
        "properties": {
          "input": {
            "type": "string",
            "example": "8.8.8.8"
          },
          "valid": {
            "type": "boolean"
          },
          "version": {
            "type": "integer",
            "nullable": true,
            "enum": [
              4,
              6,
              null
            ],
            "example": 4
          },
          "normalized": {
            "type": "string",
            "nullable": true,
            "example": "8.8.8.8",
            "description": "Canonical form (RFC 5952 for IPv6); null when invalid"
          },
          "classification": {
            "type": "string",
            "example": "public",
            "description": "public | private | loopback | link_local | multicast | reserved | unspecified | documentation | shared | benchmarking | broadcast | unique_local | ipv4_mapped | 6to4 | nat64 | discard | invalid"
          },
          "is_public": {
            "type": "boolean",
            "description": "true only for a normal globally-routable unicast address"
          },
          "is_bogon": {
            "type": "boolean",
            "description": "true for anything not publicly routable"
          },
          "checks": {
            "type": "object",
            "required": [
              "performed",
              "not_performed"
            ],
            "description": "The honesty contract, on every verification: what this result is based on, and what it is not. not_performed lists capabilities we did not run (licensed/unbuilt) rather than guessing a verdict.",
            "properties": {
              "performed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "format",
                  "region",
                  "line_type_metadata",
                  "allocation"
                ]
              },
              "not_performed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "carrier_lookup",
                  "ported_status",
                  "hlr_liveness"
                ]
              }
            }
          },
          "credits_charged": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "insufficient_credits"
          },
          "message": {
            "type": "string",
            "example": "Out of credits. The free tier refills on the 1st; paid credits never expire — we checked."
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "plan": {
            "type": "string",
            "example": "free"
          },
          "balance": {
            "type": "integer",
            "example": 250
          }
        }
      },
      "LedgerEntry": {
        "type": "object",
        "properties": {
          "delta": {
            "type": "integer",
            "example": -1,
            "description": "positive = credited, negative = spent"
          },
          "reason": {
            "type": "string",
            "example": "verify_phone"
          },
          "ref": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "integer",
            "description": "Unix seconds"
          }
        }
      },
      "Ledger": {
        "type": "object",
        "properties": {
          "balance": {
            "type": "integer",
            "example": 246
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LedgerEntry"
            }
          }
        }
      },
      "BulkAccepted": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "phone",
              "email",
              "ip"
            ]
          },
          "hlr": {
            "type": "boolean",
            "description": "true when the job runs a live HLR dip per row (phone + ?hlr=true)"
          },
          "rows": {
            "type": "integer",
            "example": 250
          },
          "credits_charged": {
            "type": "integer",
            "example": 250,
            "description": "rows × 1, or rows × 5 for an HLR job"
          },
          "status": {
            "type": "string",
            "example": "queued"
          },
          "poll": {
            "type": "string",
            "example": "/v1/bulk/{id}"
          },
          "results": {
            "type": "string",
            "example": "/v1/bulk/{id}/results.csv"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "phone",
              "email",
              "ip"
            ]
          },
          "hlr": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "done",
              "error"
            ]
          },
          "total": {
            "type": "integer"
          },
          "processed": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "integer"
          },
          "updated_at": {
            "type": "integer"
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "operational",
              "degraded",
              "no_data"
            ]
          },
          "probe": {
            "type": "string"
          },
          "uptime": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "recent": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "BulkCompletedEvent": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "bulk.completed"
            ]
          },
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "phone",
              "email"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "done",
              "error"
            ]
          },
          "total": {
            "type": "integer"
          },
          "processed": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "results_url": {
            "type": "string",
            "nullable": true
          },
          "occurred_at": {
            "type": "integer"
          }
        }
      }
    }
  },
  "webhooks": {
    "bulk.completed": {
      "post": {
        "summary": "Sent to your registered endpoint when a bulk job finishes",
        "description": "Signed with the boundstone-signature header (t=<ts>,v1=<hex>), where hex = HMAC-SHA256 of `<ts>.<raw body>` keyed by the endpoint's signing secret. Respond 2xx to acknowledge; non-2xx or timeout retries with exponential backoff (30s→1h, six attempts).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkCompletedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged"
          }
        }
      }
    }
  }
}