{
  "openapi": "3.1.0",
  "info": {
    "title": "HL Hunt API",
    "version": "2026-08-31",
    "description": "Public V1 API for HL Hunt: debtors, cases, conversations, payment links, payments, arrangements, and webhook endpoints. Monetary fields ending in _cents are strings of integer cents. Errors are RFC 7807 application/problem+json. Write endpoints require an Idempotency-Key header."
  },
  "servers": [{ "url": "https://debt.hlhunt.org/api/v1" }],
  "security": [{ "apiKey": [] }],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key created in Developers → API Keys. hlh_live_… operates on your live workspace, hlh_test_… on your sandbox tenant."
      }
    },
    "parameters": {
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "format": "uuid" }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 }
      },
      "starting_after": {
        "name": "starting_after",
        "in": "query",
        "description": "Opaque cursor from a previous page's next_cursor.",
        "schema": { "type": "string" }
      },
      "idempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Unique key per logical operation. Same key + same body replays the original response; same key + different body is rejected with 422.",
        "schema": { "type": "string", "maxLength": 255 }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 7807 problem document.",
        "properties": {
          "type": { "type": "string" },
          "title": { "type": "string" },
          "status": { "type": "integer" },
          "detail": { "type": "string" }
        }
      },
      "List": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "const": "list" },
          "data": { "type": "array", "items": { "type": "object" } },
          "has_more": { "type": "boolean" },
          "next_cursor": { "type": ["string", "null"] }
        }
      },
      "Debtor": {
        "type": "object",
        "description": "object=debtor. Contact and identity fields for one debtor."
      },
      "Case": {
        "type": "object",
        "description": "object=case. principal_cents / balance_cents are strings of integer cents."
      },
      "Conversation": {
        "type": "object",
        "description": "object=conversation. AI conversation metadata."
      },
      "Message": { "type": "object", "description": "object=message. One conversation message." },
      "PaymentLink": {
        "type": "object",
        "description": "object=payment_link. id is the checkout session id (cs_…)."
      },
      "Payment": {
        "type": "object",
        "description": "object=payment. amount_cents is a string of integer cents."
      },
      "Arrangement": {
        "type": "object",
        "description": "object=arrangement. Installment plan with schedule."
      },
      "WebhookEndpoint": {
        "type": "object",
        "description": "object=webhook_endpoint. secret appears only on create/rotate responses; otherwise secret_last4."
      }
    },
    "responses": {
      "Problem": {
        "description": "Error (RFC 7807)",
        "content": {
          "application/problem+json": { "schema": { "$ref": "#/components/schemas/Problem" } }
        }
      }
    }
  },
  "paths": {
    "/debtors": {
      "get": {
        "summary": "List debtors",
        "description": "Requires scope accounts:read.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/starting_after" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/List" } } }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "post": {
        "summary": "Create a debtor",
        "description": "Requires scope accounts:write and an Idempotency-Key.",
        "parameters": [{ "$ref": "#/components/parameters/idempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Debtor" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/debtors/{id}": {
      "get": {
        "summary": "Retrieve a debtor",
        "description": "Requires scope accounts:read.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Debtor" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "patch": {
        "summary": "Update a debtor",
        "description": "Requires scope accounts:write and an Idempotency-Key.",
        "parameters": [
          { "$ref": "#/components/parameters/id" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Debtor" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/cases": {
      "get": {
        "summary": "List cases",
        "description": "Requires scope collections:read.",
        "parameters": [
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/starting_after" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/List" } } }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "post": {
        "summary": "Create a case",
        "description": "Requires scope collections:write and an Idempotency-Key. Runs full intake validation; AI outreach may begin per workspace settings.",
        "parameters": [{ "$ref": "#/components/parameters/idempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Case" } } }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/cases/{id}": {
      "get": {
        "summary": "Retrieve a case",
        "description": "Requires scope collections:read.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Case" } } }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/conversations/{id}": {
      "get": {
        "summary": "Retrieve a conversation",
        "description": "Requires scope conversations:read.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Conversation" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/conversations/{id}/messages": {
      "get": {
        "summary": "List messages in a conversation",
        "description": "Requires scope conversations:read.",
        "parameters": [
          { "$ref": "#/components/parameters/id" },
          { "$ref": "#/components/parameters/limit" },
          { "$ref": "#/components/parameters/starting_after" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/List" } } }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "post": {
        "summary": "Send a message into a conversation",
        "description": "Requires scope conversations:write and an Idempotency-Key. The send passes the full compliance pipeline (DNC, quiet hours, contact caps) and may be blocked with an RFC 7807 problem.",
        "parameters": [
          { "$ref": "#/components/parameters/id" },
          { "$ref": "#/components/parameters/idempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "201": {
            "description": "Queued",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Message" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/payment-links": {
      "post": {
        "summary": "Create a payment link",
        "description": "Requires scope payments:write and an Idempotency-Key. Same-key retries converge on one provider checkout session; uncertain provider outcomes hold the key (409) instead of minting a second payable link.",
        "parameters": [{ "$ref": "#/components/parameters/idempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/PaymentLink" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/payment-links/{id}": {
      "get": {
        "summary": "Retrieve a payment link",
        "description": "Requires scope payments:read. id is the checkout session id (cs_…).",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/PaymentLink" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/payments/{id}": {
      "get": {
        "summary": "Retrieve a payment",
        "description": "Requires scope payments:read.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Payment" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/arrangements": {
      "post": {
        "summary": "Create an arrangement",
        "description": "Requires scope payments:write and an Idempotency-Key. Uncertain completion holds the key rather than risking a duplicate installment plan.",
        "parameters": [{ "$ref": "#/components/parameters/idempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Arrangement" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/arrangements/{id}": {
      "get": {
        "summary": "Retrieve an arrangement",
        "description": "Requires scope payments:read.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/Arrangement" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/webhook-endpoints": {
      "get": {
        "summary": "List webhook endpoints",
        "description": "Requires scope webhooks:manage. Returns secret_last4 only.",
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/List" } } }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "post": {
        "summary": "Create a webhook endpoint",
        "description": "Requires scope webhooks:manage. HTTPS on port 443 to public hosts only. The whsec_ signing secret appears exactly once in this response. Empty event_types subscribes to all events.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEndpoint" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/webhook-endpoints/{id}": {
      "get": {
        "summary": "Retrieve a webhook endpoint",
        "description": "Requires scope webhooks:manage.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEndpoint" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "patch": {
        "summary": "Update a webhook endpoint",
        "description": "Requires scope webhooks:manage. Updatable: url, description, event_types, status (enabled/disabled).",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object" } } }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEndpoint" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      },
      "delete": {
        "summary": "Delete a webhook endpoint",
        "description": "Requires scope webhooks:manage. Soft-deletes the endpoint and cancels its pending deliveries.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEndpoint" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    },
    "/webhook-endpoints/{id}/rotate-secret": {
      "post": {
        "summary": "Rotate a webhook endpoint's signing secret",
        "description": "Requires scope webhooks:manage. The new whsec_ secret appears exactly once in this response; the previous secret continues to verify for 24 hours.",
        "parameters": [{ "$ref": "#/components/parameters/id" }],
        "responses": {
          "200": {
            "description": "Rotated",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEndpoint" } }
            }
          },
          "default": { "$ref": "#/components/responses/Problem" }
        }
      }
    }
  }
}
