{
  "openapi": "3.0.3",
  "info": {
    "title": "Data API",
    "version": "1.0.0",
    "description": "Reseller-fronted data enrichment, DNC, and mobile-suppression API. Upload a file, create a job, review priced match results, purchase, and download the output. Authenticate every request with a Bearer API key (dapi_live_...)."
  },
  "servers": [
    {
      "url": "https://dataapi.reachthepeople.ai/api"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Files",
      "description": "Upload source files for processing"
    },
    {
      "name": "Jobs",
      "description": "Create, inspect, purchase, and download jobs"
    },
    {
      "name": "Webhooks",
      "description": "Configure a URL to receive job lifecycle events. Deliveries are signed (X-Signature: sha256=<HMAC-SHA256 of the raw body keyed by your secret>) so you can verify authenticity."
    }
  ],
  "paths": {
    "/v1/files": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Request an upload URL for a source file",
        "operationId": "createFile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload URL issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateFileResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/jobs": {
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Create a job from an uploaded file",
        "operationId": "createJob",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateJobRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateJobResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "List jobs",
        "operationId": "listJobs",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated job list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get a single job",
        "operationId": "getJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Job",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/jobs/{id}/purchase": {
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Purchase the results of a completed job",
        "operationId": "purchaseJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PurchaseJobRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Purchase complete; billing snapshot returned",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseJobResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/jobs/{id}/output": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get a download URL for the purchased job output",
        "operationId": "getJobOutput",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Output ready",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobOutputReady"
                }
              }
            }
          },
          "202": {
            "description": "Output is still being staged; retry shortly",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobOutputStaging"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhook": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Get your webhook configuration",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current webhook config",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Set your webhook configuration",
        "description": "Register the HTTPS URL we POST job events to (job.completed / job.failed). If you enable webhooks without an existing or provided secret, one is generated and returned ONCE in `secret` — store it to verify the `X-Signature` header. Each delivery is a POST with header `X-Webhook-Event` and `X-Signature: sha256=<hex>`, where the signature is the HMAC-SHA256 of the raw JSON body keyed by your secret. The body matches the WebhookEvent schema.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookConfigUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfigSaved"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Issued API key, e.g. `Authorization: Bearer dapi_live_...`"
      }
    },
    "parameters": {
      "JobId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Job id",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "CreateFileRequest": {
        "type": "object",
        "required": [
          "filename"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "example": "contacts.csv"
          },
          "delimiter": {
            "type": "string",
            "default": ",",
            "example": ","
          },
          "hasRowHeader": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "CreateFileResponse": {
        "type": "object",
        "properties": {
          "uploadUrl": {
            "type": "string",
            "description": "Pre-signed URL to PUT the file to"
          },
          "fileRef": {
            "type": "string",
            "description": "Opaque reference passed to POST /v1/jobs"
          },
          "expiresInMinutes": {
            "type": "integer",
            "example": 30
          }
        }
      },
      "CreateJobRequest": {
        "type": "object",
        "required": [
          "type",
          "name",
          "fileRef",
          "filename",
          "columns"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "email_append"
            ],
            "description": "What to append to each matched record"
          },
          "name": {
            "type": "string",
            "example": "June email append"
          },
          "fileRef": {
            "type": "string",
            "description": "fileRef from POST /v1/files"
          },
          "filename": {
            "type": "string",
            "example": "contacts.csv"
          },
          "delimiter": {
            "type": "string",
            "enum": [
              "comma",
              "tab",
              "pipe",
              "semicolon"
            ],
            "default": "comma",
            "description": "Column delimiter of the uploaded file (the literal character is also accepted)"
          },
          "hasRowHeader": {
            "type": "boolean",
            "default": true
          },
          "columns": {
            "type": "object",
            "description": "Map of your file's columns to identity fields, as { fieldName: columnNumber } (1-based). Recognized fields: title, firstName, middleName, lastName, suffix, fullName, birthdate, address, addressLine2, city, state, zip, zip4, email, phone, mobile. Must include lastName, an address (address / fullAddress / houseNumber), and a zip (zip or zip4) so records can be matched.",
            "additionalProperties": {
              "type": "integer"
            },
            "example": {
              "firstName": 2,
              "lastName": 4,
              "address": 5,
              "city": 7,
              "state": 8,
              "zip": 9,
              "zip4": 10
            }
          },
          "emailValidation": {
            "type": "string",
            "enum": [
              "none",
              "blank",
              "indicator"
            ],
            "default": "none",
            "description": "email_append only: how appended emails are validated"
          },
          "emailHosts": {
            "description": "email_append only: \"all\" (default), { include: [\"gmail.com\", ...] }, or { exclude: [...] }",
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "all"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "include": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              {
                "type": "object",
                "properties": {
                  "exclude": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            ]
          },
          "notificationEmail": {
            "type": "string",
            "description": "Optional email address to notify when the job completes",
            "example": "ops@example.com"
          }
        }
      },
      "CreateJobResponse": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "Created"
          }
        }
      },
      "JobListResponse": {
        "type": "object",
        "properties": {
          "records": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Job"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "PurchaseJobRequest": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "matched",
              "unique"
            ],
            "description": "Which set of records to purchase"
          }
        }
      },
      "PurchaseJobResponse": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "Purchased"
          },
          "billing": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillingLine"
            }
          },
          "totalAmount": {
            "type": "number",
            "format": "float"
          },
          "invoiceNumber": {
            "type": "string"
          }
        }
      },
      "JobOutputReady": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Pre-signed download URL"
          },
          "expiresInMinutes": {
            "type": "integer",
            "example": 30
          }
        }
      },
      "JobOutputStaging": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "staging"
            ],
            "example": "staging"
          }
        }
      },
      "PricedResult": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string"
          },
          "matched": {
            "type": "integer"
          },
          "unitPrice": {
            "type": "number",
            "format": "float"
          },
          "estimatedAmount": {
            "type": "number",
            "format": "float"
          }
        }
      },
      "BillingLine": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string"
          },
          "matched": {
            "type": "integer"
          },
          "unitPrice": {
            "type": "number",
            "format": "float"
          },
          "amount": {
            "type": "number",
            "format": "float"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "email_append"
            ]
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "Completed"
          },
          "progress": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "pricedResults": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PricedResult"
            }
          },
          "billing": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillingLine"
            }
          },
          "totalAmount": {
            "type": "number",
            "format": "float"
          },
          "invoiceNumber": {
            "type": "string"
          },
          "paid": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "purchasedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookConfig": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "HTTPS URL events are POSTed to",
            "example": "https://example.com/hooks/reach"
          },
          "enabled": {
            "type": "boolean"
          },
          "hasSecret": {
            "type": "boolean",
            "description": "Whether a signing secret is set (the secret itself is never returned)"
          }
        }
      },
      "WebhookConfigUpdate": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Public HTTPS URL",
            "example": "https://example.com/hooks/reach"
          },
          "enabled": {
            "type": "boolean"
          },
          "secret": {
            "type": "string",
            "description": "Optional signing secret. If omitted when enabling and none exists, one is generated and returned once."
          }
        }
      },
      "WebhookConfigSaved": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "hasSecret": {
            "type": "boolean"
          },
          "secret": {
            "type": "string",
            "description": "Present ONLY when a secret was just generated — store it now; it is not retrievable later."
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "POST body delivered to your webhook URL. Sent with headers X-Webhook-Event (the event name) and X-Signature: sha256=<hex HMAC-SHA256 of the raw body keyed by your secret>.",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "job.completed",
              "job.failed"
            ],
            "example": "job.completed"
          },
          "data": {
            "type": "object",
            "properties": {
              "jobId": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "example": "Completed"
              },
              "progress": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              }
            }
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}