{
  "openapi": "3.1.0",
  "info": {
    "title": "valueverde Partner API",
    "description": "Programmatic access to the valueverde platform for integration partners. Browse the published cooperative catalogue, provision the investors you act on behalf of, place share-purchase orders, and read the resulting portfolio. Authenticated via OAuth2 client_credentials; errors follow RFC 9457 Problem Details with stable `code` and `trace_id` fields.",
    "contact": {
      "name": "valueverde Partner Support",
      "email": "partners@valueverde.de"
    },
    "license": {
      "name": "Proprietary"
    },
    "version": "1.6.0"
  },
  "servers": [
    {
      "url": "https://api.valueverde.de",
      "description": "Production — the live API."
    },
    {
      "url": "https://api.staging.valueverde.de",
      "description": "Staging — the same contract with no real money. Build and test here."
    }
  ],
  "security": [
    {
      "oauth2": []
    }
  ],
  "tags": [
    {
      "name": "Cooperatives (partner)",
      "description": "Versioned partner catalogue of published cooperatives. Narrower than the internal /cooperatives contract by design; responses are conditional-GET friendly."
    },
    {
      "name": "Client",
      "description": "Identity and granted scopes of the OAuth2 client the request is authenticated as."
    },
    {
      "name": "Managed Investors",
      "description": "B2B surface for partners to provision and manage the investors they act on behalf of."
    },
    {
      "name": "Investments",
      "description": "B2B surface for partners to place and read share purchases and portfolio holdings for the investors they act on behalf of."
    },
    {
      "name": "Webhooks",
      "description": "Signed notifications valueverde POSTs to an endpoint you register, when a share purchase you placed changes state. Your endpoint implements these; they are not routes on this API."
    }
  ],
  "paths": {
    "/v1/investors/{investorId}/profile": {
      "put": {
        "tags": [
          "Managed Investors"
        ],
        "summary": "Complete a managed investor's profile",
        "description": "PUT semantics — the profile fully replaces the stored profile state; domain value objects validate (IBAN checksum, BIC, birth date) and map failures to 422. The `address`, `bank_account` and `company` sections are each all-or-nothing: omit one entirely to leave it unset, or send it whole. Inside `bank_account` only `iban` is required — BIC and institution are derived from it and the account holder defaults to the profile name. `consents` is REQUIRED on every write: omitting it, or sending a consent as not-given, revokes that consent and erases its recorded capture instant, so re-send each consent as given to leave it untouched. 404 if the investor is not owned by the partner.",
        "operationId": "completeInvestorProfile",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteManagedInvestorProfileRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerInvestorProfileResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. The `errors` map carries per-field detail.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "investors:write"
            ]
          }
        ]
      }
    },
    "/v1/investors": {
      "get": {
        "tags": [
          "Managed Investors"
        ],
        "summary": "Find a managed investor by your own customer reference",
        "description": "Resolves the partner_customer_ref you provisioned the investor with back to the investor. Use it when you hold your own key but not ours — previously the only way to recover the mapping was to re-POST /v1/investors and read the replay, which is a write used as a lookup. 404 if no investor of yours carries that ref.",
        "operationId": "findInvestorByCustomerRef",
        "parameters": [
          {
            "name": "partner_customer_ref",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ManagedInvestorResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — e.g. an unknown sort, dividend_type, or affiliation token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "investors:read"
            ]
          }
        ]
      },
      "post": {
        "tags": [
          "Managed Investors"
        ],
        "summary": "Provision a managed investor",
        "description": "Creates a party identity (investor account + profile) the partner owns. Supplying the optional `profile` block completes the investor in the same call, which is the normal case. Idempotent on partner_customer_ref: re-provisioning the same ref with the same details replays the existing investor (200) instead of creating a second one (201); re-provisioning it with a different inline profile is a 409 — use PUT /v1/investors/{id}/profile to change a profile. The investor has no login user and cannot authenticate directly.",
        "operationId": "provisionInvestor",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProvisionInvestorRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Existing investor resolved — idempotent replay of the same partner_customer_ref.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ManagedInvestorResponse"
                }
              }
            }
          },
          "201": {
            "description": "Investor provisioned.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ManagedInvestorResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — e.g. an unknown sort, dividend_type, or affiliation token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. The `errors` map carries per-field detail.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "investors:write"
            ]
          }
        ]
      }
    },
    "/v1/investors/{investorId}/share-purchases": {
      "get": {
        "tags": [
          "Investments"
        ],
        "summary": "List a managed investor's share purchases",
        "description": "Returns a page of the share-purchase orders the partner has placed for this investor, newest first, as a paged envelope: `items` plus `page`, `size`, `total_items`, `total_pages`, `has_next`, `has_previous`. `page` is 1-based (default 1) and `size` defaults to 20 and is capped at 100 — a larger `size` is silently clamped to the cap rather than rejected. 404 if the investor does not belong to the calling partner (no existence oracle).",
        "operationId": "listSharePurchases",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResponsePartnerSharePurchaseResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "applications:read"
            ]
          }
        ]
      },
      "post": {
        "tags": [
          "Investments"
        ],
        "summary": "Place a share purchase for a managed investor",
        "description": "Drafts and submits a subscription order in one step, returning 201 with the SUBMITTED order. Commercial terms are computed from the cooperative's current pricing and frozen server-side — the request carries no price fields. Requires a complete investor profile (409 INVESTOR_PROFILE_INCOMPLETE otherwise). 404 if the investor is not owned by the calling partner. Only `cooperative_id`, `share_count`, `sepa_mandate.signed_at` and `statute_consent_given_at` are required: `applicant_type` follows the investor's company affiliation, `sepa_mandate.debtor_iban` defaults to the profile IBAN, and `sepa_mandate.reference` is issued server-side when omitted — each may still be sent explicitly to override. Terms and privacy consent are taken from the investor's profile and are no longer accepted here. Supply an optional Idempotency-Key header to make a retried create safe: a repeat with the same body replays the original order, a repeat with a changed body is rejected (409). Note that because `applicant_type` may be derived, editing the investor's company affiliation between two retries makes them different orders and so a 409. A key is honoured for 24 hours; after that it is forgotten, and reusing it places a NEW order rather than replaying the original.",
        "operationId": "placeSharePurchase",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlaceSharePurchaseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Order drafted and submitted. Commercial terms are frozen server-side.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerSharePurchaseResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — e.g. an unknown sort, dividend_type, or affiliation token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — the order’s current state forbids this transition (e.g. already settled), the investor’s profile is incomplete or unattested, or an Idempotency-Key was replayed with a different body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. The `errors` map carries per-field detail.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "applications:write"
            ]
          }
        ]
      }
    },
    "/v1/investors/{investorId}/share-purchases/{purchaseId}/cancel": {
      "post": {
        "tags": [
          "Investments"
        ],
        "summary": "Cancel a managed investor's share purchase",
        "description": "Cancels a draft or submitted order; an already-settled or decided order yields 409. 404 for an unknown purchase, one belonging to a different investor, or an investor the calling partner does not own.",
        "operationId": "cancelSharePurchase",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "purchaseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerSharePurchaseResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — the order’s current state forbids this transition (e.g. already settled), the investor’s profile is incomplete or unattested, or an Idempotency-Key was replayed with a different body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "applications:write"
            ]
          }
        ]
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Client"
        ],
        "summary": "Describe the calling OAuth2 client",
        "description": "Returns the client_id, the organisation the client acts for, the scopes carried by the presented token, and the full set the client was granted — the set you may request at the token endpoint. Requires no scope beyond a valid partner token. `token_scopes` is a subset of `granted_scopes`; they differ only when the token request asked for less than everything.",
        "operationId": "describeClient",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerClientInfoResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": []
          }
        ]
      }
    },
    "/v1/investors/{investorId}": {
      "get": {
        "tags": [
          "Managed Investors"
        ],
        "summary": "Get a managed investor",
        "description": "Returns the investor's status, profile completeness and full profile. 404 if the investor does not belong to the calling partner (no existence oracle).",
        "operationId": "getManagedInvestor",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ManagedInvestorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "investors:read"
            ]
          }
        ]
      }
    },
    "/v1/investors/{investorId}/share-purchases/{purchaseId}": {
      "get": {
        "tags": [
          "Investments"
        ],
        "summary": "Get one of a managed investor's share purchases",
        "description": "404 for an unknown purchase id, one that belongs to a different investor, or an investor the calling partner does not own.",
        "operationId": "getSharePurchase",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "purchaseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerSharePurchaseResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "applications:read"
            ]
          }
        ]
      }
    },
    "/v1/investors/{investorId}/holdings": {
      "get": {
        "tags": [
          "Investments"
        ],
        "summary": "List a managed investor's holdings",
        "description": "Returns a page of the investor's portfolio positions — one row per cooperative with the current share count, oldest position first — as a paged envelope: `items` plus `page`, `size`, `total_items`, `total_pages`, `has_next`, `has_previous`. `page` is 1-based (default 1) and `size` defaults to 20 and is capped at 100 — a larger `size` is silently clamped to the cap rather than rejected. 404 if the investor is not owned by the partner.",
        "operationId": "listHoldings",
        "parameters": [
          {
            "name": "investorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResponsePartnerHoldingResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "portfolio:read"
            ]
          }
        ]
      }
    },
    "/v1/cooperatives": {
      "get": {
        "tags": [
          "Cooperatives (partner)"
        ],
        "summary": "List published cooperatives (partner)",
        "description": "Paginated, filtered, sorted catalogue card. page is one-based; size is capped at 100. Sort tokens: created_at_desc (default), created_at_asc, name_asc, name_desc, share_price_asc, share_price_desc. Min/max share-price filters are interpreted as EUR. Pass include_coordinates=true to add latitude/longitude for map rendering. Send the returned ETag back as If-None-Match to get a 304.",
        "operationId": "browseCooperatives",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 1
            }
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 20
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "city",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bafa_funded",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "dividend_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_share_price",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "max_share_price",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "affiliation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "near_lat",
            "in": "query",
            "description": "Latitude of a geo search. Send with near_lng; sorts by distance and applies no other filter.",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 90,
              "minimum": -90
            }
          },
          {
            "name": "near_lng",
            "in": "query",
            "description": "Longitude of a geo search. Send with near_lat.",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 180,
              "minimum": -180
            }
          },
          {
            "name": "radius_km",
            "in": "query",
            "description": "Search radius in kilometres. Only valid with near_lat/near_lng.",
            "required": false,
            "schema": {
              "type": "number",
              "format": "double",
              "maximum": 20000,
              "minimum": 0
            }
          },
          {
            "name": "include_coordinates",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResponsePartnerCooperativeCardResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — e.g. an unknown sort, dividend_type, or affiliation token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "cooperatives:read"
            ]
          }
        ]
      }
    },
    "/v1/cooperatives/{id}": {
      "get": {
        "tags": [
          "Cooperatives (partner)"
        ],
        "summary": "Get a single published cooperative (partner)",
        "description": "Returns the partner record, with projects rolled up into projects_by_type. Pass expand=projects to additionally receive the individual projects. Unpublished cooperatives return 404 — publication state is not observable here. Send the returned ETag back as If-None-Match to get a 304.",
        "operationId": "getCooperative",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "expand",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              }
            },
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/PartnerCooperativeDetailResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — e.g. an unknown sort, dividend_type, or affiliation token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid access token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks a scope this endpoint requires. The body carries `code: INSUFFICIENT_SCOPE` and a `required_scopes` array naming what is missing; the response also carries `WWW-Authenticate: Bearer error=\"insufficient_scope\", scope=\"…\"`. A `403` with `code: FORBIDDEN` is a different failure and adding scopes will not fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "404": {
            "description": "Not found — the resource does not exist, or does not belong to the calling partner. Acting on an investor another partner owns also returns 404 (no cross-tenant existence oracle).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded — 600 requests a minute per API client. Unlike every other 4xx here, the request itself is fine: sleep for the `Retry-After` seconds given and resend it unchanged. See https://docs.valueverde.de/docs/rate-limits.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetail"
                }
              }
            },
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted per minute for the authenticated API client.",
                "schema": {
                  "type": "integer",
                  "example": 600
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window. The bucket refills continuously, so this rises as you go rather than resetting on a boundary.",
                "schema": {
                  "type": "integer",
                  "example": 587
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the budget is back to its full limit. Not the wait for the next single request — that is `Retry-After` on a 429.",
                "schema": {
                  "type": "integer",
                  "example": 42
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before resending. Always at least 1.",
                "schema": {
                  "type": "integer",
                  "example": 3
                }
              }
            }
          }
        },
        "security": [
          {
            "oauth2": [
              "cooperatives:read",
              "projects:read"
            ]
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "CompleteManagedInvestorProfileRequest": {
        "type": "object",
        "properties": {
          "profile": {
            "$ref": "#/components/schemas/PartnerInvestorProfileRequest"
          }
        },
        "required": [
          "profile"
        ]
      },
      "PartnerAddressRequest": {
        "type": "object",
        "properties": {
          "street": {
            "type": "string",
            "description": "Street name, without the house number.",
            "example": "Hauptstrasse",
            "maxLength": 255,
            "minLength": 0
          },
          "house_number": {
            "type": "string",
            "description": "House number, including any suffix.",
            "example": "1",
            "maxLength": 40,
            "minLength": 0
          },
          "postal_code": {
            "type": "string",
            "description": "Postal code, 3-20 characters.",
            "example": "10115",
            "maxLength": 20,
            "minLength": 3,
            "pattern": "^.{3,20}$"
          },
          "city": {
            "type": "string",
            "description": "City or town.",
            "example": "Berlin",
            "maxLength": 120,
            "minLength": 0
          }
        },
        "required": [
          "city",
          "house_number",
          "postal_code",
          "street"
        ]
      },
      "PartnerBankAccountRequest": {
        "type": "object",
        "properties": {
          "iban": {
            "type": "string",
            "description": "Checksum-validated at write time. The only component you must send — bic and institution are derived from it when omitted.",
            "example": "DE89370400440532013000",
            "maxLength": 34,
            "minLength": 15,
            "pattern": "^[A-Za-z0-9 \\-]{15,34}$"
          },
          "bic": {
            "type": "string",
            "description": "Optional — derived from the IBAN when omitted. Send it to override a lookup you hold better data than. A supplied BIC is validated strictly.",
            "example": "COBADEFFXXX",
            "maxLength": 11,
            "minLength": 0,
            "pattern": "^([A-Za-z0-9]{8}([A-Za-z0-9]{3})?)?$"
          },
          "institution": {
            "type": "string",
            "description": "Optional — the bank's name, derived from the IBAN when omitted.",
            "example": "Commerzbank",
            "maxLength": 255,
            "minLength": 0
          },
          "account_holder": {
            "type": "string",
            "description": "Optional — defaults to the profile name, or the company name on a company profile. Send it when the debtor account is held by a third party.",
            "example": "Maria Santos",
            "maxLength": 255,
            "minLength": 0
          }
        },
        "required": [
          "iban"
        ]
      },
      "PartnerCompanyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Registered company name.",
            "example": "Solar Nord GmbH",
            "maxLength": 255,
            "minLength": 0
          },
          "legal_form": {
            "type": "string",
            "description": "Legal form, as registered.",
            "example": "GmbH",
            "maxLength": 64,
            "minLength": 0
          },
          "tax_id": {
            "type": "string",
            "description": "The COMPANY's tax identifier — not the investor's, which stays at the top level as tax_id.",
            "example": "DE99999999999",
            "maxLength": 32,
            "minLength": 4,
            "pattern": "^[A-Za-z0-9 ]{4,32}$"
          }
        },
        "required": [
          "legal_form",
          "name",
          "tax_id"
        ]
      },
      "PartnerConsentRequest": {
        "type": "object",
        "properties": {
          "given": {
            "type": "boolean",
            "description": "Whether the investor gave this consent.",
            "example": true
          },
          "given_at": {
            "type": "string",
            "format": "date-time",
            "description": "Optional — when you captured it, if you know. Omit and the platform stamps the consent on receipt. Must not be in the future, and must be omitted when given is false.",
            "example": "2026-05-20T10:00:00Z"
          }
        },
        "required": [
          "given"
        ]
      },
      "PartnerConsentsRequest": {
        "type": "object",
        "properties": {
          "terms": {
            "$ref": "#/components/schemas/PartnerConsentRequest",
            "description": "Acceptance of the platform terms. Required, and must be given."
          },
          "privacy": {
            "$ref": "#/components/schemas/PartnerConsentRequest",
            "description": "Acceptance of the privacy notice. Required, and must be given."
          },
          "data_sharing": {
            "$ref": "#/components/schemas/PartnerConsentRequest",
            "description": "Agreement that the investor's data may be shared with the cooperative they subscribe to. Required, and must be given."
          },
          "representation_authorization": {
            "$ref": "#/components/schemas/PartnerConsentRequest",
            "description": "Business applicants only — the signatory's authority to represent the company. Omit it, or send it not-given, for a private individual: a given value without a company block is rejected with 422 REPRESENTATION_CONSENT_REQUIRES_COMPANY."
          }
        },
        "required": [
          "data_sharing",
          "privacy",
          "terms"
        ]
      },
      "PartnerInvestorProfileRequest": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "description": "Given name. Required together with last_name — the name is stored as one value, so half of it is rejected rather than dropped.",
            "example": "Maria",
            "maxLength": 120,
            "minLength": 0
          },
          "last_name": {
            "type": "string",
            "description": "Family name. Required together with first_name.",
            "example": "Santos",
            "maxLength": 120,
            "minLength": 0
          },
          "birth_date": {
            "type": "string",
            "format": "date",
            "description": "ISO date.",
            "example": "1985-03-15"
          },
          "tax_id": {
            "type": "string",
            "description": "The INVESTOR's tax identifier, alphanumeric — a company's own is company.tax_id. Inner whitespace is stripped, so the grouped form is accepted.",
            "example": "DE12345678901",
            "maxLength": 32,
            "minLength": 0,
            "pattern": "^([A-Za-z0-9 ]{4,32})?$"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Correspondence address for this investor.",
            "example": "maria@example.org",
            "maxLength": 254,
            "minLength": 0
          },
          "phone": {
            "type": "string",
            "description": "Digits, spaces, brackets and hyphens, with an optional leading +. Not required to hold shares.",
            "example": "+49 30 1234567",
            "maxLength": 32,
            "minLength": 0,
            "pattern": "^(\\+?[0-9 ()\\-]{6,32})?$"
          },
          "address": {
            "$ref": "#/components/schemas/PartnerAddressRequest",
            "description": "Postal address. Omit the whole object to leave it unset; send it and every member is required."
          },
          "bank_account": {
            "$ref": "#/components/schemas/PartnerBankAccountRequest",
            "description": "SEPA debtor account. Omit the whole object to leave it unset; send it and only `iban` is required — the rest is derived."
          },
          "company": {
            "$ref": "#/components/schemas/PartnerCompanyRequest",
            "description": "Company affiliation. Omit it entirely for a private individual; send it and every member is required."
          },
          "consents": {
            "$ref": "#/components/schemas/PartnerConsentsRequest",
            "description": "The investor's consents. Required — omitting it revokes every stored consent and erases the recorded capture instants."
          }
        },
        "required": [
          "consents"
        ]
      },
      "PartnerAddressResponse": {
        "type": "object",
        "properties": {
          "street": {
            "type": "string"
          },
          "house_number": {
            "type": "string"
          },
          "postal_code": {
            "type": "string"
          },
          "city": {
            "type": "string"
          }
        }
      },
      "PartnerBankAccountResponse": {
        "type": "object",
        "properties": {
          "iban_masked": {
            "type": "string"
          },
          "bic": {
            "type": "string"
          },
          "institution": {
            "type": "string"
          },
          "account_holder": {
            "type": "string"
          }
        }
      },
      "PartnerCompanyResponse": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "legal_form": {
            "type": "string"
          },
          "tax_id": {
            "type": "string"
          }
        }
      },
      "PartnerConsentResponse": {
        "type": "object",
        "properties": {
          "given": {
            "type": "boolean"
          },
          "given_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PartnerInvestorProfileResponse": {
        "type": "object",
        "properties": {
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "completeness": {
            "type": "string"
          },
          "is_complete": {
            "type": "boolean"
          },
          "missing_profile_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "represents_company": {
            "type": "boolean"
          },
          "investor_type": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "birth_date": {
            "type": "string",
            "format": "date"
          },
          "tax_id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "address": {
            "$ref": "#/components/schemas/PartnerAddressResponse"
          },
          "bank_account": {
            "$ref": "#/components/schemas/PartnerBankAccountResponse"
          },
          "company": {
            "$ref": "#/components/schemas/PartnerCompanyResponse"
          },
          "consents": {
            "$ref": "#/components/schemas/PartnerProfileConsentsResponse"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PartnerProfileConsentsResponse": {
        "type": "object",
        "properties": {
          "terms": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          },
          "privacy": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          },
          "data_sharing": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          },
          "representation_authorization": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          }
        }
      },
      "EnergyMixEntryResponse": {
        "type": "object",
        "properties": {
          "energy_type": {
            "type": "string"
          },
          "energy_category": {
            "type": "string",
            "enum": [
              "solar",
              "wind",
              "heat",
              "chp",
              "hydro",
              "biomass",
              "other"
            ]
          },
          "percentage": {
            "type": "number"
          }
        }
      },
      "ProvisionInvestorRequest": {
        "type": "object",
        "properties": {
          "partner_customer_ref": {
            "type": "string",
            "description": "Your own identifier for this end-user. Unique per partner, and the idempotency key: re-posting the same ref replays the existing investor.",
            "example": "nb-cust-001",
            "maxLength": 200,
            "minLength": 0
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Correspondence address for the investor. Seeds the profile; the profile block must not restate it.",
            "example": "maria@example.org",
            "maxLength": 254,
            "minLength": 0
          },
          "profile": {
            "$ref": "#/components/schemas/PartnerInvestorProfileRequest",
            "description": "Optional — supplying it provisions and completes the investor in one call, which is the normal case."
          }
        },
        "required": [
          "email",
          "partner_customer_ref"
        ]
      },
      "ManagedInvestorResponse": {
        "type": "object",
        "properties": {
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "partner_customer_ref": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "profile_completeness": {
            "type": "string"
          },
          "is_profile_complete": {
            "type": "boolean"
          },
          "missing_profile_fields": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "profile": {
            "$ref": "#/components/schemas/PartnerInvestorProfileResponse"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PartnerSepaMandateRequest": {
        "type": "object",
        "properties": {
          "debtor_iban": {
            "type": "string",
            "description": "Optional — defaults to the profile IBAN. Send it when the debtor account differs from the account on the investor's profile.",
            "example": "DE89370400440532013000",
            "maxLength": 34,
            "minLength": 0,
            "pattern": "^([A-Za-z0-9 \\-]{15,34})?$"
          },
          "reference": {
            "type": "string",
            "description": "Optional — issued server-side when omitted. Send one only if you run your own mandate management.",
            "example": "UMR-1",
            "maxLength": 64,
            "minLength": 0
          },
          "signed_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the investor signed the mandate, in your UI. Must not be in the future.",
            "example": "2026-05-20T10:00:00Z"
          }
        },
        "required": [
          "signed_at"
        ]
      },
      "PlaceSharePurchaseRequest": {
        "type": "object",
        "properties": {
          "cooperative_id": {
            "type": "string",
            "format": "uuid",
            "description": "The cooperative to subscribe to, as returned by GET /v1/cooperatives. Must be a published cooperative.",
            "example": "0192f0c8-6f7a-7b31-9a1e-2c4d5e6f7a8b"
          },
          "share_count": {
            "type": "integer",
            "format": "int32",
            "description": "Number of shares. Commercial terms are computed server-side from the cooperative's current pricing.",
            "example": 2,
            "maximum": 100000,
            "minimum": 1
          },
          "applicant_type": {
            "type": "string",
            "description": "Optional — defaults to the investor's company affiliation: affiliated yields company, otherwise private.",
            "enum": [
              "private",
              "company"
            ],
            "example": "private",
            "maxLength": 20,
            "minLength": 0,
            "pattern": "^(private|company)?$"
          },
          "sepa_mandate": {
            "$ref": "#/components/schemas/PartnerSepaMandateRequest",
            "description": "The SEPA mandate the investor signed. Required, but only its `signed_at` is — the debtor account and the mandate reference both default."
          },
          "statute_consent_given_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the investor consented to THIS cooperative's statute (Satzung). Presented per purchase, so it cannot come from the profile.",
            "example": "2026-05-20T10:00:00Z"
          }
        },
        "required": [
          "cooperative_id",
          "sepa_mandate",
          "share_count",
          "statute_consent_given_at"
        ]
      },
      "PartnerPurchaseConsentsResponse": {
        "type": "object",
        "properties": {
          "terms": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          },
          "privacy": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          },
          "statute": {
            "$ref": "#/components/schemas/PartnerConsentResponse"
          }
        }
      },
      "PartnerSepaMandateResponse": {
        "type": "object",
        "properties": {
          "debtor_iban_masked": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "signed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PartnerSharePurchaseResponse": {
        "type": "object",
        "properties": {
          "share_purchase_id": {
            "type": "string",
            "format": "uuid"
          },
          "cooperative_id": {
            "type": "string",
            "format": "uuid"
          },
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "applicant_type": {
            "type": "string"
          },
          "share_count": {
            "type": "integer",
            "format": "int32"
          },
          "price_per_share": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "price_per_share_currency": {
            "type": "string"
          },
          "entry_fee": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "entry_fee_currency": {
            "type": "string"
          },
          "total": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "total_currency": {
            "type": "string"
          },
          "sepa_mandate": {
            "$ref": "#/components/schemas/PartnerSepaMandateResponse"
          },
          "consents": {
            "$ref": "#/components/schemas/PartnerPurchaseConsentsResponse"
          },
          "document_url": {
            "type": "string"
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time"
          },
          "decided_at": {
            "type": "string",
            "format": "date-time"
          },
          "settled_at": {
            "type": "string",
            "format": "date-time"
          },
          "rejection_reason": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PartnerClientInfoResponse": {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "description": "The partner organisation this client acts on behalf of. Mirrors the token's org_id claim."
          },
          "token_scopes": {
            "type": "array",
            "description": "Scopes carried by the token used for this request.",
            "items": {
              "type": "string"
            },
            "uniqueItems": true
          },
          "granted_scopes": {
            "type": "array",
            "description": "Every scope the client may request at the token endpoint. Requesting anything outside this set fails the whole token request with 400 invalid_scope; omitting scope yields exactly this set.",
            "items": {
              "type": "string"
            },
            "uniqueItems": true
          }
        }
      },
      "PagedResponsePartnerSharePurchaseResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSharePurchaseResponse"
            }
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "size": {
            "type": "integer",
            "format": "int32"
          },
          "total_items": {
            "type": "integer",
            "format": "int64"
          },
          "total_pages": {
            "type": "integer",
            "format": "int32"
          },
          "has_next": {
            "type": "boolean"
          },
          "has_previous": {
            "type": "boolean"
          },
          "sort": {
            "type": "string"
          }
        }
      },
      "PagedResponsePartnerHoldingResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerHoldingResponse"
            }
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "size": {
            "type": "integer",
            "format": "int32"
          },
          "total_items": {
            "type": "integer",
            "format": "int64"
          },
          "total_pages": {
            "type": "integer",
            "format": "int32"
          },
          "has_next": {
            "type": "boolean"
          },
          "has_previous": {
            "type": "boolean"
          },
          "sort": {
            "type": "string"
          }
        }
      },
      "PartnerHoldingResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "cooperative_id": {
            "type": "string",
            "format": "uuid"
          },
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "shares": {
            "type": "integer",
            "format": "int32"
          },
          "pending_shares": {
            "type": "integer",
            "format": "int32"
          },
          "first_acquired_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PagedResponsePartnerCooperativeCardResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerCooperativeCardResponse"
            }
          },
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "size": {
            "type": "integer",
            "format": "int32"
          },
          "total_items": {
            "type": "integer",
            "format": "int64"
          },
          "total_pages": {
            "type": "integer",
            "format": "int32"
          },
          "has_next": {
            "type": "boolean"
          },
          "has_previous": {
            "type": "boolean"
          },
          "sort": {
            "type": "string"
          }
        }
      },
      "PartnerCooperativeCardResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "investment_status": {
            "type": "string",
            "example": "open"
          },
          "operational_city": {
            "type": "string",
            "deprecated": true
          },
          "operating_areas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "share_price": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "share_price_currency": {
            "type": "string"
          },
          "minimum_shares": {
            "type": "integer",
            "format": "int32"
          },
          "entry_fee": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "entry_fee_currency": {
            "type": "string"
          },
          "entry_fee_type": {
            "type": "string",
            "enum": [
              "one_time",
              "per_share",
              "percent"
            ]
          },
          "last_dividend_percent": {
            "type": "number"
          },
          "dividend_type": {
            "type": "string",
            "enum": [
              "distributing",
              "accumulating",
              "share_value"
            ]
          },
          "bafa_enabled": {
            "type": "boolean"
          },
          "is_buergerwerke_member": {
            "type": "boolean"
          },
          "is_buendnis_buergerenergie_member": {
            "type": "boolean"
          },
          "co2_savings_tons": {
            "type": "number"
          },
          "co2_per_share_tons": {
            "type": "number"
          },
          "card_image_url": {
            "type": "string"
          },
          "logo_image_url": {
            "type": "string"
          },
          "application_url": {
            "type": "string"
          },
          "latitude": {
            "type": "number"
          },
          "longitude": {
            "type": "number"
          },
          "distance_km": {
            "type": "number"
          }
        }
      },
      "BafaFundingResponse": {
        "type": "object",
        "properties": {
          "min_investment_amount": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "min_investment_currency": {
            "type": "string"
          },
          "refund_percentage": {
            "type": "number"
          },
          "description": {
            "type": "string"
          },
          "info_link": {
            "type": "string"
          }
        }
      },
      "Co2BreakdownResponse": {
        "type": "object",
        "properties": {
          "project_type": {
            "type": "string"
          },
          "tons": {
            "type": "number"
          }
        }
      },
      "DividendTimelineYearResponse": {
        "type": "object",
        "properties": {
          "year": {
            "type": "integer",
            "format": "int32"
          },
          "paid": {
            "type": "boolean"
          }
        }
      },
      "DividendYearResponse": {
        "type": "object",
        "properties": {
          "year": {
            "type": "integer",
            "format": "int32"
          },
          "percentage": {
            "type": "number"
          },
          "share_value": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          }
        }
      },
      "MemberYearResponse": {
        "type": "object",
        "properties": {
          "year": {
            "type": "integer",
            "format": "int32"
          },
          "member_count": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "PartnerCo2PerShare": {
        "type": "object",
        "properties": {
          "value": {
            "type": "number"
          },
          "method": {
            "type": "string"
          },
          "capital_year": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "PartnerCo2Savings": {
        "type": "object",
        "properties": {
          "tons_per_year": {
            "type": "number"
          },
          "source": {
            "type": "string",
            "enum": [
              "projects",
              "project_types",
              "manual"
            ]
          },
          "breakdown": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Co2BreakdownResponse"
            }
          },
          "tree_equivalent": {
            "type": "number"
          }
        }
      },
      "PartnerCooperativeDetailResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "investment_status": {
            "type": "string",
            "example": "open"
          },
          "share_price": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "share_price_currency": {
            "type": "string"
          },
          "minimum_shares": {
            "type": "integer",
            "format": "int32"
          },
          "maximum_shares": {
            "type": "integer",
            "format": "int32"
          },
          "max_shares_reason": {
            "type": "string"
          },
          "entry_fee": {
            "type": "string",
            "format": "decimal",
            "example": "100.00"
          },
          "entry_fee_currency": {
            "type": "string"
          },
          "entry_fee_type": {
            "type": "string",
            "enum": [
              "one_time",
              "per_share",
              "percent"
            ]
          },
          "description": {
            "type": "string"
          },
          "profile_highlight": {
            "type": "string"
          },
          "profile_description": {
            "type": "string"
          },
          "founding_year": {
            "type": "integer",
            "format": "int32"
          },
          "operational_city": {
            "type": "string",
            "deprecated": true
          },
          "operational_postal_code": {
            "type": "string",
            "deprecated": true
          },
          "operating_areas": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "last_dividend_percent": {
            "type": "number"
          },
          "dividend_type": {
            "type": "string",
            "enum": [
              "distributing",
              "accumulating",
              "share_value"
            ]
          },
          "bafa_enabled": {
            "type": "boolean"
          },
          "bafa_funding_details": {
            "$ref": "#/components/schemas/BafaFundingResponse"
          },
          "is_buergerwerke_member": {
            "type": "boolean"
          },
          "is_buendnis_buergerenergie_member": {
            "type": "boolean"
          },
          "hero_image_url": {
            "type": "string"
          },
          "card_image_url": {
            "type": "string"
          },
          "logo_image_url": {
            "type": "string"
          },
          "application_url": {
            "type": "string"
          },
          "statute_link": {
            "type": "string"
          },
          "cancellation_period": {
            "type": "string"
          },
          "notice_period_years": {
            "type": "integer",
            "format": "int32"
          },
          "notice_effective": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "projects_by_type": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerProjectTypeRollupResponse"
            }
          },
          "projects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerProjectResponse"
            }
          },
          "dividends": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DividendYearResponse"
            }
          },
          "dividend_statistics": {
            "$ref": "#/components/schemas/PartnerDividendStatistics"
          },
          "energy_mix": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EnergyMixEntryResponse"
            }
          },
          "co2_savings": {
            "$ref": "#/components/schemas/PartnerCo2Savings"
          },
          "co2_per_share": {
            "$ref": "#/components/schemas/PartnerCo2PerShare"
          },
          "sdgs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerSdg"
            }
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MemberYearResponse"
            }
          },
          "member_statistics": {
            "$ref": "#/components/schemas/PartnerMemberStatistics"
          }
        }
      },
      "PartnerCustomMetric": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "PartnerDividendStatistics": {
        "type": "object",
        "properties": {
          "average_percent": {
            "type": "number"
          },
          "paid_years": {
            "type": "integer",
            "format": "int32"
          },
          "total_years": {
            "type": "integer",
            "format": "int32"
          },
          "timeline": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DividendTimelineYearResponse"
            }
          }
        }
      },
      "PartnerMemberStatistics": {
        "type": "object",
        "properties": {
          "current_year": {
            "type": "integer",
            "format": "int32"
          },
          "current_count": {
            "type": "integer",
            "format": "int32"
          },
          "previous_year": {
            "type": "integer",
            "format": "int32"
          },
          "previous_count": {
            "type": "integer",
            "format": "int32"
          },
          "growth_percent": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "PartnerProjectResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "project_type": {
            "type": "string"
          },
          "project_type_key": {
            "type": "string"
          },
          "is_planned": {
            "type": "boolean"
          },
          "commissioning_year": {
            "type": "integer",
            "format": "int32"
          },
          "capacity_mw": {
            "type": "number"
          },
          "kwh_per_year": {
            "type": "number"
          },
          "households_supplied": {
            "type": "integer",
            "format": "int32"
          },
          "co2_savings_tons": {
            "type": "number"
          },
          "location": {
            "type": "string"
          },
          "image_url": {
            "type": "string"
          },
          "custom_metrics": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartnerCustomMetric"
            }
          }
        }
      },
      "PartnerProjectTypeRollupResponse": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "type_key": {
            "type": "string"
          },
          "active_project_count": {
            "type": "integer",
            "format": "int32"
          },
          "planned_project_count": {
            "type": "integer",
            "format": "int32"
          },
          "total_capacity_mw": {
            "type": "number"
          },
          "co2_savings_tons_per_year": {
            "type": "number"
          },
          "first_commissioning_year": {
            "type": "integer",
            "format": "int32"
          },
          "last_commissioning_year": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "PartnerSdg": {
        "type": "object",
        "properties": {
          "number": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "image_url": {
            "type": "string"
          }
        }
      },
      "ProblemDetail": {
        "type": "object",
        "description": "RFC 9457 Problem Details envelope returned with `Content-Type: application/problem+json` for every non-2xx response. Branch on `code` (machine-readable, stable) rather than `title` (human-readable, may change). `errors` is only present on 422 VALIDATION_ERROR responses.",
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Stable URI identifying the problem class. Format `https://api.valueverde.de/errors/<slug>`.",
            "example": "https://api.valueverde.de/errors/managed-investor-not-found"
          },
          "title": {
            "type": "string",
            "example": "Managed investor not found"
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "example": 404
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation specific to this occurrence."
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "description": "URI of the request that produced the error."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code.",
            "example": "MANAGED_INVESTOR_NOT_FOUND"
          },
          "trace_id": {
            "type": "string",
            "description": "Correlation id; mirrors the `X-Request-Id` response header."
          },
          "errors": {
            "type": "object",
            "additionalProperties": true,
            "description": "Per-field validation errors. Only present on 422 VALIDATION_ERROR responses."
          },
          "required_scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scopes the endpoint required and the token did not carry. Only present on 403 INSUFFICIENT_SCOPE responses, where they are also echoed in the `WWW-Authenticate` header.",
            "examples": [
              [
                "investors:write"
              ]
            ]
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Envelope common to every delivery. Field order in the raw body is fixed (`id`, `type`, `created_at`, `data`), which matters only if you are debugging a signature mismatch — verify the raw bytes, never a re-serialisation.",
        "required": [
          "id",
          "type",
          "created_at",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Event id, stable per (share purchase, transition). Every retry and redelivery of the same event carries the same value — store it and treat a repeat as a no-op."
          },
          "type": {
            "type": "string",
            "enum": [
              "share_purchase.submitted",
              "share_purchase.approved",
              "share_purchase.rejected",
              "share_purchase.cancelled",
              "share_purchase.settled"
            ],
            "description": "Event type. New types may be added; ignore ones you do not handle rather than failing the delivery."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the transition occurred (ISO-8601 with offset), not when the delivery was attempted."
          },
          "data": {
            "type": "object",
            "description": "Type-specific body."
          }
        }
      },
      "SharePurchaseSubmittedEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEvent"
          }
        ],
        "type": "object",
        "description": "An order you placed has been submitted and accepted into the queue.",
        "properties": {
          "type": {
            "type": "string",
            "const": "share_purchase.submitted"
          },
          "data": {
            "$ref": "#/components/schemas/SharePurchaseSubmittedData"
          }
        }
      },
      "SharePurchaseSettledEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEvent"
          }
        ],
        "type": "object",
        "description": "The order has settled. This is the point the investor’s holdings change.",
        "properties": {
          "type": {
            "type": "string",
            "const": "share_purchase.settled"
          },
          "data": {
            "$ref": "#/components/schemas/SharePurchaseSettledData"
          }
        }
      },
      "SharePurchaseSubmittedData": {
        "type": "object",
        "required": [
          "share_purchase_id",
          "investor_id",
          "cooperative_id",
          "share_count",
          "status",
          "terminal",
          "total",
          "total_currency"
        ],
        "properties": {
          "share_purchase_id": {
            "type": "string",
            "format": "uuid",
            "description": "The order. Matches `share_purchase_id` on the share-purchase endpoints."
          },
          "investor_id": {
            "type": "string",
            "format": "uuid",
            "description": "The managed investor the order belongs to — the `investorId` path segment on the investing endpoints."
          },
          "cooperative_id": {
            "type": "string",
            "format": "uuid"
          },
          "share_count": {
            "type": "integer",
            "format": "int32",
            "minimum": 1
          },
          "status": {
            "type": "string",
            "const": "submitted"
          },
          "terminal": {
            "type": "boolean",
            "const": false
          },
          "total": {
            "type": "string",
            "description": "Total consideration as a decimal **string**, never a float — parse it as a decimal type. Server-computed and frozen at submission.",
            "examples": [
              "500.00"
            ]
          },
          "total_currency": {
            "type": "string",
            "description": "ISO-4217 code.",
            "examples": [
              "EUR"
            ]
          }
        }
      },
      "SharePurchaseSettledData": {
        "type": "object",
        "description": "Carries no `total`: the settlement transition does not restate the amount. Take it from the submitted event or from the order.",
        "required": [
          "share_purchase_id",
          "investor_id",
          "cooperative_id",
          "share_count",
          "status",
          "terminal"
        ],
        "properties": {
          "share_purchase_id": {
            "type": "string",
            "format": "uuid"
          },
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "cooperative_id": {
            "type": "string",
            "format": "uuid"
          },
          "share_count": {
            "type": "integer",
            "format": "int32",
            "minimum": 1
          },
          "status": {
            "type": "string",
            "const": "settled"
          },
          "terminal": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "SharePurchaseApprovedEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEvent"
          }
        ],
        "type": "object",
        "description": "The order passed review. Progress, not an ending — settlement still follows.",
        "properties": {
          "type": {
            "type": "string",
            "const": "share_purchase.approved"
          },
          "data": {
            "$ref": "#/components/schemas/SharePurchaseLifecycleData"
          }
        }
      },
      "SharePurchaseRejectedEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEvent"
          }
        ],
        "type": "object",
        "description": "The order was refused. Final — nothing further arrives for it.",
        "properties": {
          "type": {
            "type": "string",
            "const": "share_purchase.rejected"
          },
          "data": {
            "$ref": "#/components/schemas/SharePurchaseRejectedData"
          }
        }
      },
      "SharePurchaseCancelledEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEvent"
          }
        ],
        "type": "object",
        "description": "The order was cancelled. Final — nothing further arrives for it.",
        "properties": {
          "type": {
            "type": "string",
            "const": "share_purchase.cancelled"
          },
          "data": {
            "$ref": "#/components/schemas/SharePurchaseLifecycleData"
          }
        }
      },
      "SharePurchaseLifecycleData": {
        "type": "object",
        "required": [
          "share_purchase_id",
          "investor_id",
          "cooperative_id",
          "status",
          "terminal"
        ],
        "properties": {
          "share_purchase_id": {
            "type": "string",
            "format": "uuid"
          },
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "cooperative_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "approved",
              "cancelled"
            ],
            "description": "The status the order is now in."
          },
          "terminal": {
            "type": "boolean",
            "description": "Whether any further event will arrive for this order. Branch on this rather than hard-coding which types are final."
          }
        }
      },
      "SharePurchaseRejectedData": {
        "type": "object",
        "required": [
          "share_purchase_id",
          "investor_id",
          "cooperative_id",
          "status",
          "terminal"
        ],
        "properties": {
          "share_purchase_id": {
            "type": "string",
            "format": "uuid"
          },
          "investor_id": {
            "type": "string",
            "format": "uuid"
          },
          "cooperative_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "const": "rejected"
          },
          "terminal": {
            "type": "boolean",
            "const": true
          },
          "reason": {
            "type": "string",
            "description": "Why the order was refused, in our words. Present when one was recorded. Surface it to your customer or map it — do not assume it is stable enough to branch on."
          }
        }
      }
    },
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth2 client_credentials flow for partner server-to-server integrations. Obtain client_id and client_secret from your platform administrator.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://api.valueverde.de/oauth2/token",
            "scopes": {
              "cooperatives:read": "Browse and inspect the published cooperative catalogue.",
              "projects:read": "Read a cooperative’s project detail — required alongside cooperatives:read to fetch a single cooperative, and for its expand=projects payload.",
              "investors:read": "Read the managed investors the partner owns.",
              "investors:write": "Provision managed investors and complete their profile attestation.",
              "applications:read": "Read the share-purchase orders the partner has placed.",
              "applications:write": "Place and cancel share-purchase orders on behalf of a managed investor.",
              "portfolio:read": "Read a managed investor’s confirmed portfolio holdings."
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "share_purchase.submitted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "share_purchase.submitted",
        "description": "Delivered when an order you placed has been submitted and accepted into the queue. Implemented by your endpoint, POSTed by valueverde.",
        "operationId": "onSharePurchaseSubmitted",
        "security": [],
        "parameters": [
          {
            "name": "X-VV-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over `\"{t}.{raw_body}\"`, formatted `t=<unix-seconds>,v1=<hex>`. Verify against the raw bytes, in constant time, and reject a `t` outside your tolerance window.",
            "schema": {
              "type": "string",
              "examples": [
                "t=1755212062,v1=6c1f…"
              ]
            }
          },
          {
            "name": "X-VV-Webhook-Id",
            "in": "header",
            "required": true,
            "description": "Delivery-attempt id. Changes on every retry — dedupe on the body’s `id`, not this.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-VV-Webhook-Event",
            "in": "header",
            "required": true,
            "description": "Event type, mirroring `type` in the body. Lets you route before parsing.",
            "schema": {
              "type": "string",
              "enum": [
                "share_purchase.submitted",
                "share_purchase.settled"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharePurchaseSubmittedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Any 2xx counts. Acknowledge as soon as you have persisted the event and process asynchronously — the read timeout is 5 seconds and a slow response is indistinguishable from a broken one."
          },
          "default": {
            "description": "Delivery failed and will be retried with exponential backoff from 30s, up to 6 attempts, after which it is dead-lettered and lost. Return a non-2xx only when you genuinely want the delivery again."
          }
        }
      }
    },
    "share_purchase.approved": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "share_purchase.approved",
        "description": "Delivered when the order passes review. Progress, not an ending — settlement still follows. Implemented by your endpoint, POSTed by valueverde.",
        "operationId": "onSharePurchaseApproved",
        "security": [],
        "parameters": [
          {
            "name": "X-VV-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over `\"{t}.{raw_body}\"`, formatted `t=<unix-seconds>,v1=<hex>`. Verify against the raw bytes, in constant time, and reject a `t` outside your tolerance window.",
            "schema": {
              "type": "string",
              "examples": [
                "t=1755212062,v1=6c1f…"
              ]
            }
          },
          {
            "name": "X-VV-Webhook-Id",
            "in": "header",
            "required": true,
            "description": "Delivery-attempt id. Changes on every retry — dedupe on the body’s `id`, not this.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-VV-Webhook-Event",
            "in": "header",
            "required": true,
            "description": "Event type, mirroring `type` in the body. Lets you route before parsing.",
            "schema": {
              "type": "string",
              "enum": [
                "share_purchase.submitted",
                "share_purchase.settled"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharePurchaseApprovedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Any 2xx counts. Acknowledge as soon as you have persisted the event and process asynchronously — the read timeout is 5 seconds and a slow response is indistinguishable from a broken one."
          },
          "default": {
            "description": "Delivery failed and will be retried with exponential backoff from 30s, up to 6 attempts, after which it is dead-lettered and lost. Return a non-2xx only when you genuinely want the delivery again."
          }
        }
      }
    },
    "share_purchase.rejected": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "share_purchase.rejected",
        "description": "Delivered when the order is refused. Final — nothing further arrives for it, so this is the event that lets you stop polling and tell your customer. Implemented by your endpoint, POSTed by valueverde.",
        "operationId": "onSharePurchaseRejected",
        "security": [],
        "parameters": [
          {
            "name": "X-VV-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over `\"{t}.{raw_body}\"`, formatted `t=<unix-seconds>,v1=<hex>`. Verify against the raw bytes, in constant time, and reject a `t` outside your tolerance window.",
            "schema": {
              "type": "string",
              "examples": [
                "t=1755212062,v1=6c1f…"
              ]
            }
          },
          {
            "name": "X-VV-Webhook-Id",
            "in": "header",
            "required": true,
            "description": "Delivery-attempt id. Changes on every retry — dedupe on the body’s `id`, not this.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-VV-Webhook-Event",
            "in": "header",
            "required": true,
            "description": "Event type, mirroring `type` in the body. Lets you route before parsing.",
            "schema": {
              "type": "string",
              "enum": [
                "share_purchase.submitted",
                "share_purchase.settled"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharePurchaseRejectedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Any 2xx counts. Acknowledge as soon as you have persisted the event and process asynchronously — the read timeout is 5 seconds and a slow response is indistinguishable from a broken one."
          },
          "default": {
            "description": "Delivery failed and will be retried with exponential backoff from 30s, up to 6 attempts, after which it is dead-lettered and lost. Return a non-2xx only when you genuinely want the delivery again."
          }
        }
      }
    },
    "share_purchase.cancelled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "share_purchase.cancelled",
        "description": "Delivered when the order is cancelled, including by your own call to the cancel endpoint. Final. Implemented by your endpoint, POSTed by valueverde.",
        "operationId": "onSharePurchaseCancelled",
        "security": [],
        "parameters": [
          {
            "name": "X-VV-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over `\"{t}.{raw_body}\"`, formatted `t=<unix-seconds>,v1=<hex>`. Verify against the raw bytes, in constant time, and reject a `t` outside your tolerance window.",
            "schema": {
              "type": "string",
              "examples": [
                "t=1755212062,v1=6c1f…"
              ]
            }
          },
          {
            "name": "X-VV-Webhook-Id",
            "in": "header",
            "required": true,
            "description": "Delivery-attempt id. Changes on every retry — dedupe on the body’s `id`, not this.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-VV-Webhook-Event",
            "in": "header",
            "required": true,
            "description": "Event type, mirroring `type` in the body. Lets you route before parsing.",
            "schema": {
              "type": "string",
              "enum": [
                "share_purchase.submitted",
                "share_purchase.settled"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharePurchaseCancelledEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Any 2xx counts. Acknowledge as soon as you have persisted the event and process asynchronously — the read timeout is 5 seconds and a slow response is indistinguishable from a broken one."
          },
          "default": {
            "description": "Delivery failed and will be retried with exponential backoff from 30s, up to 6 attempts, after which it is dead-lettered and lost. Return a non-2xx only when you genuinely want the delivery again."
          }
        }
      }
    },
    "share_purchase.settled": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "share_purchase.settled",
        "description": "Delivered when the order settles — the point the investor’s holdings change. Implemented by your endpoint, POSTed by valueverde.",
        "operationId": "onSharePurchaseSettled",
        "security": [],
        "parameters": [
          {
            "name": "X-VV-Webhook-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 over `\"{t}.{raw_body}\"`, formatted `t=<unix-seconds>,v1=<hex>`. Verify against the raw bytes, in constant time, and reject a `t` outside your tolerance window.",
            "schema": {
              "type": "string",
              "examples": [
                "t=1755212062,v1=6c1f…"
              ]
            }
          },
          {
            "name": "X-VV-Webhook-Id",
            "in": "header",
            "required": true,
            "description": "Delivery-attempt id. Changes on every retry — dedupe on the body’s `id`, not this.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-VV-Webhook-Event",
            "in": "header",
            "required": true,
            "description": "Event type, mirroring `type` in the body. Lets you route before parsing.",
            "schema": {
              "type": "string",
              "enum": [
                "share_purchase.submitted",
                "share_purchase.settled"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SharePurchaseSettledEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Delivery accepted. Any 2xx counts. Acknowledge as soon as you have persisted the event and process asynchronously — the read timeout is 5 seconds and a slow response is indistinguishable from a broken one."
          },
          "default": {
            "description": "Delivery failed and will be retried with exponential backoff from 30s, up to 6 attempts, after which it is dead-lettered and lost. Return a non-2xx only when you genuinely want the delivery again."
          }
        }
      }
    }
  }
}
