{
  "openapi": "3.1.0",
  "info": {
    "title": "OOLP Contracts API",
    "version": "1.1.0",
    "description": "Public API used by partner platforms (e.g. YAARD) to issue NBA-sealed contracts through Obi Okonkwo Legal Practitioners (OOLP).\n\n## Authentication\n\nEvery request carries three headers:\n\n| Header | Purpose |\n| --- | --- |\n| `x-oolp-api-key` | The API key shown once when the partner record was created. Starts with `oolp_`. |\n| `x-oolp-timestamp` | Unix epoch seconds. Requests older than 300s are rejected (replay protection). |\n| `x-oolp-signature` | `HMAC-SHA256(hmac_secret, \"{timestamp}.{raw_body}\")` hex-encoded. |\n\n### Signing example (Node.js)\n\n```js\nimport { createHmac } from 'crypto';\nconst BASE_URL = process.env.OOLP_BASE_URL || 'https://your-oolp-domain.example';\nconst body = JSON.stringify(payload);\nconst ts   = Math.floor(Date.now() / 1000).toString();\nconst sig  = createHmac('sha256', HMAC_SECRET).update(`${ts}.${body}`).digest('hex');\nfetch(`${BASE_URL}/api/public/v1/requests`, {\n  method: 'POST',\n  headers: {\n    'content-type': 'application/json',\n    'x-oolp-api-key': API_KEY,\n    'x-oolp-timestamp': ts,\n    'x-oolp-signature': sig,\n  },\n  body,\n});\n```\n\n### Signing example (Python)\n\n```py\nimport hmac, hashlib, json, time, requests\nOOLP_BASE_URL = 'https://your-oolp-domain.example'\nbody = json.dumps(payload, separators=(',', ':'))\nts   = str(int(time.time()))\nsig  = hmac.new(HMAC_SECRET.encode(), f\"{ts}.{body}\".encode(), hashlib.sha256).hexdigest()\nrequests.post(f'{OOLP_BASE_URL}/api/public/v1/requests', data=body, headers={\n  'content-type': 'application/json',\n  'x-oolp-api-key': API_KEY,\n  'x-oolp-timestamp': ts,\n  'x-oolp-signature': sig,\n})\n```\n\n## Idempotency\n\nPOST `/v1/requests` accepts an optional `Idempotency-Key` header. Retrying with the same key returns the original response.\n\n## Money & dates\n\nAll monetary fields are integer **Nigerian Naira (NGN)** — no kobo, no decimals. Dates are ISO 8601.\n\n## Errors\n\nUse `application/problem+json` with `status`, `title`, `code`, and optional `detail` and `errors[]`.",
    "contact": {
      "email": "engineering@obiokonkwo.com"
    }
  },
  "servers": [
    {
      "url": "/",
      "description": "Current deployment (automatically uses this domain)"
    }
  ],
  "security": [
    {
      "PartnerKey": [],
      "PartnerSignature": [],
      "PartnerTimestamp": []
    }
  ],
  "tags": [
    {
      "name": "Templates",
      "description": "Read-only catalogue of contract templates and the data they need."
    },
    {
      "name": "Requests",
      "description": "Create, poll, and confirm payment on contract requests."
    },
    {
      "name": "Webhooks",
      "description": "Events OOLP delivers to your endpoint."
    }
  ],
  "paths": {
    "/api/public/v1/templates": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "List active templates",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TemplateSummary"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/v1/templates/{slug}": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Get a template's detail and required partner-data keys",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/public/v1/requests": {
      "post": {
        "tags": [
          "Requests"
        ],
        "summary": "Create a contract request",
        "description": "Submits a partner-originated request. Returns an `intake_url` to share with the end client.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "description": "Optional. Retries with the same key return the original response."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "example": {
                "template_slug": "residential-tenancy",
                "partner_reference": "YAARD-LEASE-2026-00042",
                "contract_value_ngn": 2400000,
                "requester": {
                  "name": "Adaeze Ibe",
                  "email": "adaeze@yaard.ng"
                },
                "partner_data": {
                  "landlord_name": "Chidinma Okeke",
                  "landlord_email": "chidinma@example.com",
                  "tenant_name": "Tunde Bakare",
                  "tenant_email": "tunde@example.com",
                  "property_address": "12 Akin Adesola, Victoria Island, Lagos",
                  "rent_ngn": 2400000,
                  "start_date": "2026-08-01",
                  "end_date": "2027-07-31"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedRequest"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "401": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "409": {
            "$ref": "#/components/responses/Problem"
          },
          "422": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/public/v1/requests/{id}": {
      "get": {
        "tags": [
          "Requests"
        ],
        "summary": "Get request status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestStatus"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/public/v1/requests/{id}/confirm-payment": {
      "post": {
        "tags": [
          "Requests"
        ],
        "summary": "Confirm a payment for a request",
        "description": "Use when the partner collected payment from the client and wants to unblock signing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payment_reference"
                ],
                "properties": {
                  "payment_reference": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "paid_amount_ngn": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/public/v1/widget.js": {
      "get": {
        "tags": [
          "Templates"
        ],
        "summary": "Drop-in widget JS",
        "description": "Embed on your site as `<script src='.../v1/widget.js' data-partner-key='...' data-template='residential-tenancy'></script>`. Opens the intake flow in a modal. The widget never exposes your HMAC secret; only the publishable partner key.",
        "responses": {
          "200": {
            "description": "application/javascript"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PartnerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-oolp-api-key"
      },
      "PartnerSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "x-oolp-signature",
        "description": "HMAC-SHA-256 of `\"{timestamp}.{raw_body}\"` using hmac_secret, hex-encoded."
      },
      "PartnerTimestamp": {
        "type": "apiKey",
        "in": "header",
        "name": "x-oolp-timestamp",
        "description": "Unix epoch seconds. Rejected if more than 300s of clock skew."
      }
    },
    "responses": {
      "Problem": {
        "description": "Error",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "properties": {
          "status": {
            "type": "integer",
            "example": 422
          },
          "title": {
            "type": "string",
            "example": "Validation failed"
          },
          "code": {
            "type": "string",
            "example": "validation_failed"
          },
          "detail": {
            "type": "string",
            "example": "Field `partner_data.rent_ngn` is required for template `residential-tenancy`."
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "status",
          "title"
        ]
      },
      "TemplateSummary": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "example": "residential-tenancy"
          },
          "name": {
            "type": "string",
            "example": "Residential Tenancy Agreement"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "jurisdiction": {
            "type": "string",
            "nullable": true,
            "example": "Lagos State"
          },
          "fee_mode": {
            "type": "string",
            "enum": [
              "fixed",
              "percent_of_value"
            ]
          },
          "fee_ngn": {
            "type": "integer",
            "description": "Fixed fee. Ignored when fee_mode='percent_of_value'."
          },
          "fee_percent_bps": {
            "type": "integer",
            "nullable": true,
            "description": "Basis points (10000 = 100%). fee = contract_value_ngn * bps / 10000, clamped by fee_min_ngn/fee_max_ngn."
          },
          "fee_min_ngn": {
            "type": "integer",
            "nullable": true
          },
          "fee_max_ngn": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "TemplateDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TemplateSummary"
          },
          {
            "type": "object",
            "properties": {
              "required_field_keys": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Keys from the global partner-data registry that this template requires."
              },
              "field_definitions": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PartnerDataField"
                }
              },
              "principal_role": {
                "type": "string",
                "nullable": true,
                "description": "Which party (by role) becomes the client. E.g. 'landlord' on a tenancy."
              }
            }
          }
        ]
      },
      "PartnerDataField": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "example": "rent_ngn"
          },
          "label": {
            "type": "string",
            "example": "Annual rent (NGN)"
          },
          "type": {
            "type": "string",
            "enum": [
              "string",
              "number",
              "money",
              "email",
              "date",
              "boolean",
              "enum",
              "phone",
              "address"
            ]
          },
          "category": {
            "type": "string",
            "example": "money"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "enum_options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          }
        }
      },
      "CreateRequest": {
        "type": "object",
        "required": [
          "template_slug",
          "partner_reference"
        ],
        "properties": {
          "template_slug": {
            "type": "string"
          },
          "partner_reference": {
            "type": "string",
            "maxLength": 120,
            "description": "Your internal id for this transaction. Echoed in every webhook."
          },
          "contract_value_ngn": {
            "type": "integer",
            "minimum": 0,
            "description": "Required when template.fee_mode='percent_of_value'."
          },
          "requester": {
            "type": "object",
            "description": "The person on your platform who initiated this request (NOT the contract parties).",
            "properties": {
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "partner_data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Key/value pairs matching the template's required_field_keys. See GET /v1/templates/{slug}. Any extra keys are stored verbatim and accessible to lawyers, but never used in the contract body unless a clause references them."
          }
        }
      },
      "CreatedRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "example": "awaiting_payment"
          },
          "fee_ngn": {
            "type": "integer"
          },
          "currency": {
            "type": "string",
            "example": "NGN"
          },
          "intake_url": {
            "type": "string",
            "format": "uri",
            "description": "Tokenised URL the partner forwards to the end client. Do NOT log or share publicly."
          }
        }
      },
      "RequestStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "awaiting_payment",
              "paid",
              "intake",
              "lawyer_review",
              "sealed",
              "sent",
              "signed",
              "completed",
              "cancelled"
            ]
          },
          "file_number": {
            "type": "string",
            "nullable": true
          },
          "fee_ngn": {
            "type": "integer"
          },
          "paid_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "sealed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "verify_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Public verification URL for the sealed PDF. Present once status='completed'."
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "OOLP POSTs this body to YOUR `webhook_url` (set in Partner Settings on the OOLP dashboard). Verify the `x-oolp-signature` header.\n\n**Signature:** `HMAC-SHA256(hmac_secret, \"{timestamp}.{raw_body}\")` hex. Headers: `x-oolp-timestamp`, `x-oolp-signature`, `x-oolp-event-id`.\n\n**Retries:** exponential backoff (1m, 5m, 30m, 2h, 12h). Max 5 attempts. Return HTTP 2xx within 10s to acknowledge.\n\n**Replay protection:** keep a 24h cache of `x-oolp-event-id` and ignore duplicates.",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "request.created",
              "payment.required",
              "payment.confirmed",
              "intake.submitted",
              "contract.sealed",
              "contract.sent",
              "contract.signed",
              "contract.completed"
            ]
          },
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "partner_reference": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "event",
          "event_id",
          "request_id",
          "occurred_at"
        ]
      }
    }
  }
}
