{
  "openapi": "3.1.0",
  "info": {
    "title": "Missionary Lane API",
    "version": "1.0.0",
    "description": "Read and update a church's missionary gallery, newsletters, announcements and missions giving. Authenticate with a church API key (Settings → API keys): `Authorization: Bearer ml_live_…`. Every request acts only on the key's church. Restricted missionaries are only ever returned as the public gallery shows them (alias, region, r-… slug). Rate limit: 120 requests per minute per key (X-RateLimit-* headers; 429 with Retry-After). Errors are JSON: {\"error\": {\"code\", \"message\", \"details\"?}}. Lists use cursor pagination (limit ≤ 100, next_cursor). GET responses carry a weak ETag; send If-None-Match for 304. Read endpoints over public gallery data allow cross-origin browser reads; writes and giving endpoints are server-to-server only.",
    "contact": {
      "name": "Missionary Lane",
      "url": "https://missionarylane.org/docs/api"
    }
  },
  "servers": [
    {
      "url": "https://missionarylane.org/api/v1",
      "description": "Hosted Missionary Lane"
    },
    {
      "url": "{supabase_url}/functions/v1/api",
      "description": "Self-hosted (your own Supabase project)",
      "variables": {
        "supabase_url": {
          "default": "https://your-project.supabase.co"
        }
      }
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "me",
      "description": "The key itself."
    },
    {
      "name": "gallery",
      "description": "The public gallery."
    },
    {
      "name": "missionaries",
      "description": "Missionaries."
    },
    {
      "name": "newsletters",
      "description": "Newsletters (prayer letters)."
    },
    {
      "name": "announcements",
      "description": "Ticker and big-banner announcements."
    },
    {
      "name": "giving",
      "description": "Missions giving and support tracking. Missionary Lane never holds or moves money."
    }
  ],
  "x-scopes": {
    "gallery:read": "Read the public gallery and announcements, exactly as visitors see them",
    "missionaries:read": "Read missionaries and their newsletters, as the public gallery shows them",
    "missionaries:write": "Add missionaries and change their details",
    "newsletters:write": "Add newsletters (and ask the AI to read them)",
    "announcements:write": "Post announcements to the ticker and big banner",
    "giving:read": "Read giving totals, where gifts went, and monthly payments",
    "giving:write": "Import gifts and set each month's payments to missionaries"
  },
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "summary": "About this key",
        "description": "The key's church, name, prefix, scopes and current rate-limit window. Any valid key.",
        "tags": [
          "me"
        ],
        "x-scopes": [],
        "responses": {
          "200": {
            "description": "Key information.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Me"
                    }
                  }
                },
                "example": {
                  "data": {
                    "church": {
                      "id": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11",
                      "slug": "grace",
                      "name": "Grace Church",
                      "status": "active"
                    },
                    "key": {
                      "id": "0b1c2d3e-4f50-4617-8293-a4b5c6d7e8f9",
                      "name": "Website sync",
                      "prefix": "Xk3pQ9mZ",
                      "scopes": [
                        "gallery:read",
                        "missionaries:read"
                      ],
                      "created_at": "2026-10-01T15:20:00Z",
                      "last_used_at": "2026-10-31T09:59:40Z"
                    },
                    "rate_limit": {
                      "limit": 120,
                      "remaining": 117,
                      "reset_at": "2026-10-31T10:01:00Z"
                    },
                    "media_base_url": "https://cmnrhfezhypegbxjeqba.supabase.co/functions/v1/media/"
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/gallery": {
      "get": {
        "operationId": "getGallery",
        "summary": "The public gallery",
        "description": "Every published missionary exactly as the public gallery shows them, with kiosks, photos, newsletters, public family, dates and briefings. Restricted missionaries appear by alias and region only. Requires gallery:read.",
        "tags": [
          "gallery"
        ],
        "x-scopes": [
          "gallery:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "description": "Items per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of gallery missionaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Missionary"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as ?cursor= for the next page; null on the last page."
                    },
                    "church": {
                      "$ref": "#/components/schemas/Church"
                    }
                  }
                },
                "example": {
                  "church": {
                    "id": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11",
                    "slug": "grace",
                    "name": "Grace Church"
                  },
                  "data": [
                    {
                      "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                      "slug": "okafor-family",
                      "kind": "missionary",
                      "display_name": "The Okafor Family",
                      "status": "on_field",
                      "tagline": "Planting churches in Lagos",
                      "place_label": "Lagos, Nigeria",
                      "country": "Nigeria",
                      "region": "Africa",
                      "lat": 6.5244,
                      "lng": 3.3792,
                      "ministry_focus": "Church planting",
                      "portrait_path": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/portrait.jpg",
                      "security_mode": false,
                      "sort_order": 10,
                      "updated_at": "2026-10-30T14:02:11Z",
                      "newsletter_count": 2,
                      "latest_newsletter_date": "2026-10-01",
                      "members": "David, Grace, Ada and Tobi",
                      "timezone": "Africa/Lagos",
                      "status_note": null,
                      "mission_board": "ABWE",
                      "sending_church": "Faith Baptist Church",
                      "support_address": "ABWE, PO Box 8585, Harrisburg PA",
                      "contact": {
                        "email": "okafors@example.org",
                        "website": "https://okafors.example.org"
                      },
                      "logo_path": null,
                      "bio": "David and Grace serve…",
                      "began_support": null,
                      "last_visit": null,
                      "support_amount": null,
                      "kiosk": {
                        "palette": {
                          "primary": "#14532D",
                          "accent": "#F59E0B"
                        },
                        "welcome_narration": "Welcome to the Okafors' kiosk…"
                      },
                      "narration_audio": {},
                      "media": [
                        {
                          "id": "f0e1d2c3-b4a5-4968-8776-655443322110",
                          "kind": "photo",
                          "path": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/photos/1.jpg",
                          "url": null,
                          "caption": "Sunday in Ikeja"
                        }
                      ],
                      "newsletters": [
                        {
                          "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
                          "title": "October 2026",
                          "issue_date": "2026-10-01",
                          "period_label": "October",
                          "page_paths": [
                            "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/letters/2026-10-p1.jpg"
                          ],
                          "pdf_path": null,
                          "body_text": "Dear church family…",
                          "summary": "A new church plant in Ikeja.",
                          "highlights": [
                            "First service with 40 people"
                          ],
                          "prayer_requests": [
                            "Visas for the team"
                          ],
                          "praises": [
                            "Safe travel"
                          ],
                          "verse": "Isaiah 52:7"
                        }
                      ],
                      "family": [
                        {
                          "name": "Ada",
                          "relation": "child",
                          "birthday": "March 4"
                        }
                      ],
                      "important_dates": [
                        {
                          "label": "Arrived on field",
                          "date": "2019-08-15",
                          "month_day": null,
                          "recurs_yearly": false,
                          "notes": null
                        }
                      ],
                      "briefing": {
                        "place_label": "Lagos, Nigeria",
                        "facts": {
                          "population": "15 million"
                        },
                        "news": []
                      }
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/missionaries": {
      "get": {
        "operationId": "listMissionaries",
        "summary": "List missionaries",
        "description": "Published missionaries in gallery order, as summaries. Restricted missionaries appear by alias, region and r-… slug. Requires missionaries:read.",
        "tags": [
          "missionaries"
        ],
        "x-scopes": [
          "missionaries:read"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "description": "Items per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Search the name, place, country, region and tagline."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "on_field",
                "furlough",
                "preparing",
                "retired",
                "former"
              ]
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "missionary",
                "ministry"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of missionaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MissionarySummary"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as ?cursor= for the next page; null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                      "slug": "okafor-family",
                      "kind": "missionary",
                      "display_name": "The Okafor Family",
                      "status": "on_field",
                      "tagline": "Planting churches in Lagos",
                      "place_label": "Lagos, Nigeria",
                      "country": "Nigeria",
                      "region": "Africa",
                      "lat": 6.5244,
                      "lng": 3.3792,
                      "ministry_focus": "Church planting",
                      "portrait_path": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/portrait.jpg",
                      "security_mode": false,
                      "sort_order": 10,
                      "updated_at": "2026-10-30T14:02:11Z",
                      "newsletter_count": 2,
                      "latest_newsletter_date": "2026-10-01"
                    },
                    {
                      "id": "9d8c7b6a-5f4e-4d3c-8b2a-1e0f9d8c7b6a",
                      "slug": "r-4f1c9a2b7e",
                      "kind": "missionary",
                      "display_name": "The R Family",
                      "status": "on_field",
                      "tagline": null,
                      "place_label": "Asia",
                      "country": null,
                      "region": "Asia",
                      "lat": 35,
                      "lng": 105,
                      "ministry_focus": null,
                      "portrait_path": null,
                      "security_mode": true,
                      "sort_order": 20,
                      "updated_at": "2026-10-12T09:00:00Z",
                      "newsletter_count": 1,
                      "latest_newsletter_date": "2026-09-01"
                    }
                  ],
                  "next_cursor": "WzIwLCJUaGUgUiBGYW1pbHkiLCI5ZDhjN2I2YSJd"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createMissionary",
        "summary": "Add a missionary",
        "description": "Creates a missionary (draft unless visibility says otherwise). Only the listed fields are accepted; church_id comes from the key. The plan's missionary limit applies (403 plan_limit). Requires missionaries:write.",
        "tags": [
          "missionaries"
        ],
        "x-scopes": [
          "missionaries:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MissionaryCreate"
              },
              "example": {
                "slug": "okafor-family",
                "display_name": "The Okafor Family",
                "place_label": "Lagos, Nigeria",
                "region": "Africa",
                "status": "on_field"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MissionaryCard"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                    "slug": "okafor-family",
                    "visibility": "draft",
                    "restricted": false,
                    "published": false,
                    "updated_at": "2026-10-31T10:00:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/missionaries/{id}": {
      "get": {
        "operationId": "getMissionary",
        "summary": "Get a missionary",
        "description": "One published missionary, exactly as the gallery shows them. Requires missionaries:read.",
        "tags": [
          "missionaries"
        ],
        "x-scopes": [
          "missionaries:read"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "okafor-family",
            "description": "The missionary's id, or their slug as the gallery shows it (restricted missionaries: r-…; their real slug never matches)."
          }
        ],
        "responses": {
          "200": {
            "description": "The missionary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Missionary"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                    "slug": "okafor-family",
                    "kind": "missionary",
                    "display_name": "The Okafor Family",
                    "status": "on_field",
                    "tagline": "Planting churches in Lagos",
                    "place_label": "Lagos, Nigeria",
                    "country": "Nigeria",
                    "region": "Africa",
                    "lat": 6.5244,
                    "lng": 3.3792,
                    "ministry_focus": "Church planting",
                    "portrait_path": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/portrait.jpg",
                    "security_mode": false,
                    "sort_order": 10,
                    "updated_at": "2026-10-30T14:02:11Z",
                    "newsletter_count": 2,
                    "latest_newsletter_date": "2026-10-01",
                    "members": "David, Grace, Ada and Tobi",
                    "timezone": "Africa/Lagos",
                    "status_note": null,
                    "mission_board": "ABWE",
                    "sending_church": "Faith Baptist Church",
                    "support_address": "ABWE, PO Box 8585, Harrisburg PA",
                    "contact": {
                      "email": "okafors@example.org",
                      "website": "https://okafors.example.org"
                    },
                    "logo_path": null,
                    "bio": "David and Grace serve…",
                    "began_support": null,
                    "last_visit": null,
                    "support_amount": null,
                    "kiosk": {
                      "palette": {
                        "primary": "#14532D",
                        "accent": "#F59E0B"
                      },
                      "welcome_narration": "Welcome to the Okafors' kiosk…"
                    },
                    "narration_audio": {},
                    "media": [
                      {
                        "id": "f0e1d2c3-b4a5-4968-8776-655443322110",
                        "kind": "photo",
                        "path": "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/photos/1.jpg",
                        "url": null,
                        "caption": "Sunday in Ikeja"
                      }
                    ],
                    "newsletters": [
                      {
                        "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
                        "title": "October 2026",
                        "issue_date": "2026-10-01",
                        "period_label": "October",
                        "page_paths": [
                          "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/letters/2026-10-p1.jpg"
                        ],
                        "pdf_path": null,
                        "body_text": "Dear church family…",
                        "summary": "A new church plant in Ikeja.",
                        "highlights": [
                          "First service with 40 people"
                        ],
                        "prayer_requests": [
                          "Visas for the team"
                        ],
                        "praises": [
                          "Safe travel"
                        ],
                        "verse": "Isaiah 52:7"
                      }
                    ],
                    "family": [
                      {
                        "name": "Ada",
                        "relation": "child",
                        "birthday": "March 4"
                      }
                    ],
                    "important_dates": [
                      {
                        "label": "Arrived on field",
                        "date": "2019-08-15",
                        "month_day": null,
                        "recurs_yearly": false,
                        "notes": null
                      }
                    ],
                    "briefing": {
                      "place_label": "Lagos, Nigeria",
                      "facts": {
                        "population": "15 million"
                      },
                      "news": []
                    }
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found in this church.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No missionary with that id or slug in this church."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateMissionary",
        "summary": "Change a missionary",
        "description": "Changes only the fields sent. In contact and field_visibility only the keys sent change (null clears a contact key). A restricted missionary can't be made visible again with a key (403 restricted_change). The response never echoes stored details. Requires missionaries:write.",
        "tags": [
          "missionaries"
        ],
        "x-scopes": [
          "missionaries:write"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "okafor-family",
            "description": "The missionary's id, or their slug as the gallery shows it (restricted missionaries: r-…; their real slug never matches)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MissionaryInput"
              },
              "example": {
                "status": "furlough",
                "status_note": "Home in Virginia until March",
                "contact": {
                  "phone": null
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MissionaryCard"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                    "slug": "okafor-family",
                    "visibility": "public",
                    "restricted": false,
                    "published": true,
                    "updated_at": "2026-10-31T10:00:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found in this church.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No missionary with that id or slug in this church."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/newsletters": {
      "get": {
        "operationId": "listNewsletters",
        "summary": "List newsletters",
        "description": "Published newsletters of published missionaries, newest first, as the gallery shows them (restricted missionaries' letters show only what was written under the restricted rules). Requires missionaries:read or newsletters:write.",
        "tags": [
          "newsletters"
        ],
        "x-scopes": [
          "missionaries:read",
          "newsletters:write"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "description": "Items per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page."
          },
          {
            "name": "missionary",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only this missionary (id or gallery slug)."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of newsletters.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Newsletter"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as ?cursor= for the next page; null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
                      "title": "October 2026",
                      "issue_date": "2026-10-01",
                      "period_label": "October",
                      "page_paths": [
                        "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/letters/2026-10-p1.jpg"
                      ],
                      "pdf_path": null,
                      "body_text": "Dear church family…",
                      "summary": "A new church plant in Ikeja.",
                      "highlights": [
                        "First service with 40 people"
                      ],
                      "prayer_requests": [
                        "Visas for the team"
                      ],
                      "praises": [
                        "Safe travel"
                      ],
                      "verse": "Isaiah 52:7",
                      "missionary": {
                        "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                        "slug": "okafor-family",
                        "display_name": "The Okafor Family",
                        "security_mode": false
                      }
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found in this church.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No missionary with that id or slug in this church."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createNewsletter",
        "summary": "Add a newsletter",
        "description": "Adds a newsletter to a missionary. Upload page images or a PDF to the church's media storage first (paths start with the church id). With ai_read: true the AI reads them in the background and fills in the title, summary, highlights, requests and praises (uses credits or the church's AI key). For a restricted missionary this is refused (422 restricted_ai_off) unless the church turned on \"Let the AI read restricted missionaries' letters\" in Settings. Requires newsletters:write.",
        "tags": [
          "newsletters"
        ],
        "x-scopes": [
          "newsletters:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewsletterInput"
              },
              "example": {
                "missionary": "okafor-family",
                "issue_date": "2026-10-01",
                "page_paths": [
                  "3f6c1a52-8d2e-4f4b-9b1c-2a7e5d9c0b11/okafor/letters/2026-10-p1.jpg"
                ],
                "ai_read": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NewsletterCreated"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
                    "missionary": {
                      "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                      "slug": "okafor-family",
                      "restricted": false
                    },
                    "issue_date": "2026-10-01",
                    "published": true,
                    "created_at": "2026-10-31T10:00:00Z",
                    "ai_read": "started"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found in this church.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No missionary with that id or slug in this church."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/announcements": {
      "get": {
        "operationId": "listAnnouncements",
        "summary": "Announcements feed",
        "description": "What the gallery's ticker and big banner show now and over the next `days`: posted announcements plus announced birthdays and dates of public missionaries. Requires gallery:read or announcements:write.",
        "tags": [
          "announcements"
        ],
        "x-scopes": [
          "gallery:read",
          "announcements:write"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "description": "Items per page."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "next_cursor from the previous page."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 90,
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of announcements.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FeedItem"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as ?cursor= for the next page; null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "kind": "furlough",
                      "title": "Welcome home, Okafors!",
                      "message": "Join us Sunday.",
                      "image_path": null,
                      "link": null,
                      "show_in": "both",
                      "on_date": "2026-11-01",
                      "created_at": "2026-10-31T10:00:00Z",
                      "slug": "okafor-family",
                      "display_name": "The Okafor Family",
                      "portrait_path": null
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAnnouncement",
        "summary": "Post an announcement",
        "description": "Posts to the ticker and/or big banner. Announcements about a restricted missionary are saved but never shown publicly (see warnings). Requires announcements:write.",
        "tags": [
          "announcements"
        ],
        "x-scopes": [
          "announcements:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnnouncementInput"
              },
              "example": {
                "title": "Welcome home, Okafors!",
                "message": "Join us Sunday.",
                "kind": "furlough",
                "missionary": "okafor-family",
                "ends_on": "2026-11-15"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnnouncementCreated"
                },
                "example": {
                  "data": {
                    "id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
                    "kind": "furlough",
                    "title": "Welcome home, Okafors!",
                    "starts_on": "2026-10-31",
                    "ends_on": "2026-11-15",
                    "show_in": "both",
                    "is_published": true,
                    "missionary": {
                      "id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                      "slug": "okafor-family",
                      "restricted": false
                    },
                    "created_at": "2026-10-31T10:00:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found in this church.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No missionary with that id or slug in this church."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/giving/summary": {
      "get": {
        "operationId": "getGivingSummary",
        "summary": "Giving summary",
        "description": "The church's missions giving overview from giving_overview (totals, connections, recent months). Restricted missionaries are masked to alias and r-… slug. Requires giving:read.",
        "tags": [
          "giving"
        ],
        "x-scopes": [
          "giving:read"
        ],
        "responses": {
          "200": {
            "description": "The overview.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "description": "giving_overview: {enabled, addon, settings, as_of, connections, funds, givers, documents, totals, months, last_computed}."
                    }
                  }
                },
                "example": {
                  "data": {
                    "enabled": true,
                    "as_of": "2026-10-31",
                    "connections": [
                      {
                        "id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                        "kind": "manual",
                        "name": "API imports",
                        "status": "active",
                        "config": {
                          "source": "api"
                        },
                        "last_sync_at": null,
                        "last_error": null,
                        "secret_set": false
                      }
                    ],
                    "funds": [
                      {
                        "id": "8e7d6c5b-4a39-4281-9f0e-1d2c3b4a5968",
                        "connection_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                        "external_id": "pco-fund-3",
                        "name": "Faith Promise",
                        "kind": "missions",
                        "missions_share": 1,
                        "given_cents_12m": 17420000
                      }
                    ],
                    "givers": {
                      "total": 41,
                      "claimed": 12
                    },
                    "documents": {
                      "to_review": 0,
                      "unread": 0
                    },
                    "totals": {
                      "given_cents": 17420000,
                      "counted_cents": 17420000,
                      "paid_cents": 16900000,
                      "traced_cents": 15800000,
                      "estimated_cents": 1100000,
                      "waiting_cents": 520000
                    },
                    "months": [
                      {
                        "month": "2026-10",
                        "given_cents": 1840000,
                        "counted_cents": 1840000,
                        "paid_cents": 1450000,
                        "proposed_cents": 0
                      }
                    ]
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/giving/traces": {
      "get": {
        "operationId": "getGivingTraces",
        "summary": "Where a month's gifts went",
        "description": "Gifts received in the month, traced to missionaries' payments (oldest money first; same-day gifts pro rata), estimated for undated totals, or waiting in a fund. Requires giving:read.",
        "tags": [
          "giving"
        ],
        "x-scopes": [
          "giving:read"
        ],
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "example": "2026-10",
            "description": "Month as YYYY-MM."
          }
        ],
        "responses": {
          "200": {
            "description": "The month's traces.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GivingTraces"
                    }
                  }
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "gift_count": 212,
                    "given_cents": 1840000,
                    "traced_cents": 1500000,
                    "estimated_cents": 245000,
                    "waiting_cents": 95000,
                    "missionaries": [
                      {
                        "missionary_id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                        "slug": "okafor-family",
                        "display_name": "The Okafor Family",
                        "restricted": false,
                        "traced_cents": 25000,
                        "estimated_cents": 0
                      },
                      {
                        "missionary_id": "9d8c7b6a-5f4e-4d3c-8b2a-1e0f9d8c7b6a",
                        "slug": "r-4f1c9a2b7e",
                        "display_name": "The R Family",
                        "restricted": true,
                        "traced_cents": 20000,
                        "estimated_cents": 5000
                      }
                    ]
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/giving/gifts": {
      "post": {
        "operationId": "importGifts",
        "summary": "Import gifts",
        "description": "Imports up to 1000 gifts. Idempotent: each gift's external_id is unique within its connection, so sending it again updates that gift. Without connection_id, gifts go under the church's API connection (created on first use). Requires giving:write.",
        "tags": [
          "giving"
        ],
        "x-scopes": [
          "giving:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GiftImport"
              },
              "example": {
                "gifts": [
                  {
                    "external_id": "pco-88123",
                    "amount_cents": 5000,
                    "received_on": "2026-10-05",
                    "fund": {
                      "external_id": "pco-fund-3",
                      "name": "Faith Promise"
                    },
                    "giver": {
                      "external_id": "pco-person-17",
                      "email": "jordan@example.org",
                      "name": "Jordan Ellis"
                    }
                  },
                  {
                    "external_id": "envelopes-2026-10",
                    "amount_cents": 120000,
                    "received_on": "2026-10-01",
                    "dated": false,
                    "fund": {
                      "external_id": "envelope-missions",
                      "name": "Missions envelopes"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Imported.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GiftImportResult"
                    }
                  }
                },
                "example": {
                  "data": {
                    "connection_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
                    "received": 2,
                    "result": {
                      "funds": {
                        "created": 1,
                        "updated": 0
                      },
                      "givers": {
                        "created": 1,
                        "matched": 0
                      },
                      "gifts": {
                        "inserted": 2,
                        "updated": 0
                      },
                      "traces": {
                        "gifts": 214,
                        "payments": 31,
                        "counted_cents": 1840000,
                        "traced_cents": 1500000,
                        "estimated_cents": 245000,
                        "waiting_cents": 95000
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found in this church.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "No missionary with that id or slug in this church."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/giving/payments": {
      "get": {
        "operationId": "getPayments",
        "summary": "A month's payments",
        "description": "The month's payment grid from payments_month: each supported missionary's proposal (support_amount on payment_day) and payments. Restricted missionaries are masked to alias and r-… slug. Requires giving:read.",
        "tags": [
          "giving"
        ],
        "x-scopes": [
          "giving:read"
        ],
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "example": "2026-10",
            "description": "Month as YYYY-MM."
          }
        ],
        "responses": {
          "200": {
            "description": "The month's payments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentsMonth"
                    }
                  }
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "status": "confirmed",
                    "proposed_cents": 45000,
                    "confirmed_cents": 45000,
                    "rows": [
                      {
                        "missionary_id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                        "slug": "okafor-family",
                        "display_name": "The Okafor Family",
                        "visibility": "public",
                        "support_amount_cents": 25000,
                        "payment_day": 15,
                        "proposed": {
                          "amount_cents": 25000,
                          "paid_on": "2026-10-15"
                        },
                        "payments": [
                          {
                            "id": "6b5a4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
                            "amount_cents": 25000,
                            "paid_on": "2026-10-15",
                            "source": "grid",
                            "status": "confirmed",
                            "document_id": null,
                            "external_ref": null,
                            "note": null
                          }
                        ]
                      },
                      {
                        "missionary_id": "9d8c7b6a-5f4e-4d3c-8b2a-1e0f9d8c7b6a",
                        "support_amount_cents": 20000,
                        "payment_day": 15,
                        "proposed": {
                          "amount_cents": 20000,
                          "paid_on": "2026-10-15"
                        },
                        "payments": [
                          {
                            "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                            "amount_cents": 20000,
                            "paid_on": "2026-10-15",
                            "source": "grid",
                            "status": "confirmed",
                            "document_id": null,
                            "external_ref": null,
                            "note": "QB-1043"
                          }
                        ],
                        "slug": "r-4f1c9a2b7e",
                        "display_name": "The R Family",
                        "restricted": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)."
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "setPayments",
        "summary": "Set a month's payments",
        "description": "Sets what the church paid in the month (payments_save). Each missionary listed has their payments for the month replaced by the ones sent (amount_cents 0 = none); missionaries not listed are unchanged. Every paid_on must fall in the month. Gifts are re-traced afterwards. Returns the month as GET does. Requires giving:write.",
        "tags": [
          "giving"
        ],
        "x-scopes": [
          "giving:write"
        ],
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "example": "2026-10",
            "description": "Month as YYYY-MM."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentsPut"
              },
              "example": {
                "payments": [
                  {
                    "missionary": "okafor-family",
                    "amount_cents": 25000,
                    "paid_on": "2026-10-15"
                  },
                  {
                    "missionary": "r-4f1c9a2b7e",
                    "amount_cents": 20000,
                    "paid_on": "2026-10-15",
                    "note": "QB-1043"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved; the month as GET returns it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentsMonth"
                    }
                  }
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "status": "confirmed",
                    "proposed_cents": 45000,
                    "confirmed_cents": 45000,
                    "rows": [
                      {
                        "missionary_id": "7b9e2c4d-1a3f-4e5b-8c6d-0f1e2d3c4b5a",
                        "slug": "okafor-family",
                        "display_name": "The Okafor Family",
                        "visibility": "public",
                        "support_amount_cents": 25000,
                        "payment_day": 15,
                        "proposed": {
                          "amount_cents": 25000,
                          "paid_on": "2026-10-15"
                        },
                        "payments": [
                          {
                            "id": "6b5a4c3d-2e1f-4a0b-9c8d-7e6f5a4b3c2d",
                            "amount_cents": 25000,
                            "paid_on": "2026-10-15",
                            "source": "grid",
                            "status": "confirmed",
                            "document_id": null,
                            "external_ref": null,
                            "note": null
                          }
                        ]
                      },
                      {
                        "missionary_id": "9d8c7b6a-5f4e-4d3c-8b2a-1e0f9d8c7b6a",
                        "support_amount_cents": 20000,
                        "payment_day": 15,
                        "proposed": {
                          "amount_cents": 20000,
                          "paid_on": "2026-10-15"
                        },
                        "payments": [
                          {
                            "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                            "amount_cents": 20000,
                            "paid_on": "2026-10-15",
                            "source": "grid",
                            "status": "confirmed",
                            "document_id": null,
                            "external_ref": null,
                            "note": "QB-1043"
                          }
                        ],
                        "slug": "r-4f1c9a2b7e",
                        "display_name": "The R Family",
                        "restricted": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, unknown or revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "This API key isn't recognised. Check that it was copied in full."
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the church is paused, or the change isn't allowed with a key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "insufficient_scope",
                    "message": "This API key can't do that. It needs the missionaries:write permission.",
                    "details": {
                      "required": [
                        "missionaries:write"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "The request didn't validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "validation_failed",
                    "message": "Some fields need attention.",
                    "details": {
                      "fields": [
                        {
                          "field": "church_id",
                          "message": "is set by your API key and can't be sent"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this key in the current window. See Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Too many requests. This key may make 120 requests per window; try again shortly.",
                    "details": {
                      "limit": 120,
                      "reset_at": "2026-10-31T10:01:00Z"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ml_live_<32 characters>",
        "description": "A church API key. Write scopes include the matching read scope."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "unauthorized",
                  "insufficient_scope",
                  "church_paused",
                  "restricted_change",
                  "restricted_ai_off",
                  "plan_limit",
                  "not_found",
                  "validation_failed",
                  "invalid_cursor",
                  "invalid_json",
                  "conflict",
                  "rejected",
                  "rate_limited",
                  "payload_too_large",
                  "method_not_allowed",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {}
            }
          }
        }
      },
      "Church": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "church": {
            "$ref": "#/components/schemas/Church"
          },
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "prefix": {
                "type": "string",
                "description": "First 8 characters after ml_live_."
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "gallery:read",
                    "missionaries:read",
                    "missionaries:write",
                    "newsletters:write",
                    "announcements:write",
                    "giving:read",
                    "giving:write"
                  ]
                }
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              },
              "last_used_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          },
          "rate_limit": {
            "type": "object",
            "properties": {
              "limit": {
                "type": "integer"
              },
              "remaining": {
                "type": "integer"
              },
              "reset_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "media_base_url": {
            "type": "string",
            "description": "Prefix for storage paths such as portrait_path. Storage is private: this address serves only files the public gallery shows (a redirect to a short-lived link); other files answer 404."
          }
        }
      },
      "MissionarySummary": {
        "type": "object",
        "properties": {
          "id": {},
          "slug": {},
          "kind": {},
          "display_name": {},
          "status": {},
          "tagline": {},
          "place_label": {},
          "country": {},
          "region": {},
          "lat": {},
          "lng": {},
          "ministry_focus": {},
          "portrait_path": {},
          "security_mode": {},
          "sort_order": {},
          "updated_at": {},
          "newsletter_count": {
            "type": "integer"
          },
          "latest_newsletter_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        },
        "description": "Gallery fields (restricted: alias as display_name, region as place_label, r-… slug, rounded lat/lng, no country or portrait)."
      },
      "Missionary": {
        "type": "object",
        "additionalProperties": true,
        "description": "Exactly one element of the public gallery (public.gallery()).",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "security_mode": {
            "type": "boolean",
            "description": "Restricted."
          },
          "kiosk": {
            "type": [
              "object",
              "null"
            ]
          },
          "media": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "newsletters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Newsletter"
            }
          },
          "family": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "important_dates": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "briefing": {
            "type": [
              "object",
              "null"
            ]
          }
        }
      },
      "MissionaryCard": {
        "type": "object",
        "description": "What a write returns: never the stored details.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string",
            "description": "As the gallery shows it (r-… when restricted)."
          },
          "visibility": {
            "type": "string"
          },
          "restricted": {
            "type": "boolean"
          },
          "published": {
            "type": "boolean"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MissionaryInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "slug": {
            "type": "string",
            "minLength": 1,
            "maxLength": 81,
            "pattern": "^[a-z0-9][a-z0-9-]{0,80}$",
            "description": "Web address name, unique in the church (lowercase letters, digits, dashes).",
            "examples": [
              "okafor-family"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "missionary",
              "ministry"
            ],
            "description": "A missionary family or a ministry."
          },
          "display_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Name shown on the kiosk.",
            "examples": [
              "The Okafor Family"
            ]
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "First name."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Last name."
          },
          "spouse_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Spouse's name."
          },
          "members": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Family members, as one line.",
            "examples": [
              "David, Grace, Ada and Tobi"
            ]
          },
          "tagline": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "One-line description."
          },
          "place_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Place shown to visitors.",
            "examples": [
              "Lagos, Nigeria"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Country."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "World region (shown even for restricted missionaries).",
            "examples": [
              "Africa"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -90,
            "maximum": 90,
            "description": "Latitude of a public place (never a home)."
          },
          "lng": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -180,
            "maximum": 180,
            "description": "Longitude of a public place (never a home)."
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 64,
            "pattern": "^[A-Za-z_]+(/[A-Za-z0-9_+-]+)*$",
            "description": "IANA time zone.",
            "examples": [
              "Africa/Lagos"
            ]
          },
          "ministry_focus": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Ministry focus."
          },
          "status": {
            "type": "string",
            "enum": [
              "on_field",
              "furlough",
              "preparing",
              "retired",
              "former"
            ],
            "description": "Where they are in their ministry."
          },
          "status_note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Short status note."
          },
          "mission_board": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Mission board."
          },
          "sending_church": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Sending church."
          },
          "support_address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Where to send support."
          },
          "contact": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Email."
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 60,
                "description": "Phone."
              },
              "website": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "maxLength": 500,
                "pattern": "^https?://",
                "description": "Website (https)."
              },
              "facebook": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "maxLength": 500,
                "pattern": "^https?://",
                "description": "Facebook page (https)."
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 300,
                "description": "Mailing address."
              }
            },
            "description": "Contact details. Only the keys you send change; send null to clear one. Each one's public display follows field_visibility."
          },
          "portrait_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Portrait image: a path in the church's media storage (<church_id>/…)."
          },
          "logo_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Logo image: a path in the church's media storage (<church_id>/…)."
          },
          "bio": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 8000,
            "description": "About them."
          },
          "support_amount": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Monthly support in dollars (staff only unless shown)."
          },
          "began_support": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date support began."
          },
          "ended_support": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date support ended."
          },
          "last_visit": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date of their last visit."
          },
          "visibility": {
            "type": "string",
            "enum": [
              "draft",
              "public",
              "limited",
              "hidden"
            ],
            "description": "draft (not shown), public, limited (restricted: alias and region only), hidden. A restricted missionary can be made public again only in the admin."
          },
          "field_visibility": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "members": {
                "type": "boolean"
              },
              "tagline": {
                "type": "boolean"
              },
              "place_label": {
                "type": "boolean"
              },
              "map_location": {
                "type": "boolean"
              },
              "ministry_focus": {
                "type": "boolean"
              },
              "status_note": {
                "type": "boolean"
              },
              "mission_board": {
                "type": "boolean"
              },
              "sending_church": {
                "type": "boolean"
              },
              "support_address": {
                "type": "boolean"
              },
              "bio": {
                "type": "boolean"
              },
              "contact_email": {
                "type": "boolean"
              },
              "contact_phone": {
                "type": "boolean"
              },
              "contact_website": {
                "type": "boolean"
              },
              "contact_facebook": {
                "type": "boolean"
              },
              "contact_address": {
                "type": "boolean"
              },
              "photos": {
                "type": "boolean"
              },
              "newsletters": {
                "type": "boolean"
              },
              "newsletter_scans": {
                "type": "boolean"
              },
              "briefing": {
                "type": "boolean"
              },
              "began_support": {
                "type": "boolean"
              },
              "last_visit": {
                "type": "boolean"
              },
              "support_amount": {
                "type": "boolean"
              },
              "admin_notes": {
                "type": "boolean"
              }
            },
            "description": "Which details show on the public site (true) or stay staff-only (false). Only the keys you send change."
          },
          "security_alias": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Name shown instead of theirs when restricted.",
            "examples": [
              "The O Family"
            ]
          },
          "sort_order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000,
            "description": "Gallery order (lower first)."
          },
          "payment_day": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 28,
            "description": "Day of the month the church pays their support (used to propose payments; default the 1st)."
          }
        }
      },
      "MissionaryCreate": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "slug": {
            "type": "string",
            "minLength": 1,
            "maxLength": 81,
            "pattern": "^[a-z0-9][a-z0-9-]{0,80}$",
            "description": "Web address name, unique in the church (lowercase letters, digits, dashes).",
            "examples": [
              "okafor-family"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "missionary",
              "ministry"
            ],
            "description": "A missionary family or a ministry."
          },
          "display_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Name shown on the kiosk.",
            "examples": [
              "The Okafor Family"
            ]
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "First name."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Last name."
          },
          "spouse_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Spouse's name."
          },
          "members": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Family members, as one line.",
            "examples": [
              "David, Grace, Ada and Tobi"
            ]
          },
          "tagline": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "One-line description."
          },
          "place_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Place shown to visitors.",
            "examples": [
              "Lagos, Nigeria"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Country."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 60,
            "description": "World region (shown even for restricted missionaries).",
            "examples": [
              "Africa"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -90,
            "maximum": 90,
            "description": "Latitude of a public place (never a home)."
          },
          "lng": {
            "type": [
              "number",
              "null"
            ],
            "minimum": -180,
            "maximum": 180,
            "description": "Longitude of a public place (never a home)."
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 64,
            "pattern": "^[A-Za-z_]+(/[A-Za-z0-9_+-]+)*$",
            "description": "IANA time zone.",
            "examples": [
              "Africa/Lagos"
            ]
          },
          "ministry_focus": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Ministry focus."
          },
          "status": {
            "type": "string",
            "enum": [
              "on_field",
              "furlough",
              "preparing",
              "retired",
              "former"
            ],
            "description": "Where they are in their ministry."
          },
          "status_note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Short status note."
          },
          "mission_board": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Mission board."
          },
          "sending_church": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Sending church."
          },
          "support_address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Where to send support."
          },
          "contact": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Email."
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 60,
                "description": "Phone."
              },
              "website": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "maxLength": 500,
                "pattern": "^https?://",
                "description": "Website (https)."
              },
              "facebook": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri",
                "maxLength": 500,
                "pattern": "^https?://",
                "description": "Facebook page (https)."
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 300,
                "description": "Mailing address."
              }
            },
            "description": "Contact details. Only the keys you send change; send null to clear one. Each one's public display follows field_visibility."
          },
          "portrait_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Portrait image: a path in the church's media storage (<church_id>/…)."
          },
          "logo_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Logo image: a path in the church's media storage (<church_id>/…)."
          },
          "bio": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 8000,
            "description": "About them."
          },
          "support_amount": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Monthly support in dollars (staff only unless shown)."
          },
          "began_support": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date support began."
          },
          "ended_support": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date support ended."
          },
          "last_visit": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date of their last visit."
          },
          "visibility": {
            "type": "string",
            "enum": [
              "draft",
              "public",
              "limited",
              "hidden"
            ],
            "description": "draft (not shown), public, limited (restricted: alias and region only), hidden. A restricted missionary can be made public again only in the admin."
          },
          "field_visibility": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "members": {
                "type": "boolean"
              },
              "tagline": {
                "type": "boolean"
              },
              "place_label": {
                "type": "boolean"
              },
              "map_location": {
                "type": "boolean"
              },
              "ministry_focus": {
                "type": "boolean"
              },
              "status_note": {
                "type": "boolean"
              },
              "mission_board": {
                "type": "boolean"
              },
              "sending_church": {
                "type": "boolean"
              },
              "support_address": {
                "type": "boolean"
              },
              "bio": {
                "type": "boolean"
              },
              "contact_email": {
                "type": "boolean"
              },
              "contact_phone": {
                "type": "boolean"
              },
              "contact_website": {
                "type": "boolean"
              },
              "contact_facebook": {
                "type": "boolean"
              },
              "contact_address": {
                "type": "boolean"
              },
              "photos": {
                "type": "boolean"
              },
              "newsletters": {
                "type": "boolean"
              },
              "newsletter_scans": {
                "type": "boolean"
              },
              "briefing": {
                "type": "boolean"
              },
              "began_support": {
                "type": "boolean"
              },
              "last_visit": {
                "type": "boolean"
              },
              "support_amount": {
                "type": "boolean"
              },
              "admin_notes": {
                "type": "boolean"
              }
            },
            "description": "Which details show on the public site (true) or stay staff-only (false). Only the keys you send change."
          },
          "security_alias": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "Name shown instead of theirs when restricted.",
            "examples": [
              "The O Family"
            ]
          },
          "sort_order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000,
            "description": "Gallery order (lower first)."
          },
          "payment_day": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 28,
            "description": "Day of the month the church pays their support (used to propose payments; default the 1st)."
          }
        },
        "required": [
          "slug",
          "display_name"
        ]
      },
      "Newsletter": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "issue_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "period_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "highlights": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "prayer_requests": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "praises": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "verse": {
            "type": [
              "string",
              "null"
            ]
          },
          "page_paths": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pdf_path": {
            "type": [
              "string",
              "null"
            ]
          },
          "body_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "missionary": {
            "type": "object",
            "description": "In list results only."
          }
        }
      },
      "NewsletterInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "missionary": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "The missionary: id, or slug as the gallery shows it.",
            "examples": [
              "okafor-family"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Title.",
            "examples": [
              "Spring 2026 update"
            ]
          },
          "issue_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Issue date.",
            "examples": [
              "2026-10-01"
            ]
          },
          "period_label": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "Period, e.g. “March & April”."
          },
          "body_text": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100000,
            "description": "Full text (never shown publicly for restricted missionaries)."
          },
          "summary": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000,
            "description": "Short summary."
          },
          "highlights": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 1000
            },
            "description": "Highlights."
          },
          "prayer_requests": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 1000
            },
            "description": "Prayer requests."
          },
          "praises": {
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "string",
              "maxLength": 1000
            },
            "description": "Praises."
          },
          "verse": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Bible verse."
          },
          "page_paths": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "maxLength": 500
            },
            "description": "Page images in the church's media storage (<church_id>/…)."
          },
          "pdf_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "PDF in the church's media storage (<church_id>/…)."
          },
          "published": {
            "type": "boolean",
            "description": "Shown in the gallery (default true)."
          },
          "ai_read": {
            "type": "boolean",
            "description": "Ask the AI to read the pages or PDF and fill in the title, summary, highlights, requests and praises (default false). Uses AI credits or the church's AI key."
          }
        },
        "required": [
          "missionary"
        ]
      },
      "NewsletterCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "missionary": {
            "type": "object"
          },
          "issue_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "published": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "ai_read": {
            "type": "string",
            "enum": [
              "started",
              "not_requested"
            ]
          }
        }
      },
      "FeedItem": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_path": {
            "type": [
              "string",
              "null"
            ]
          },
          "link": {
            "type": [
              "string",
              "null"
            ]
          },
          "show_in": {
            "type": "string"
          },
          "on_date": {
            "type": "string",
            "format": "date"
          },
          "created_at": {
            "type": "string"
          },
          "slug": {
            "type": [
              "string",
              "null"
            ]
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "portrait_path": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AnnouncementInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Headline.",
            "examples": [
              "Welcome home, Okafors!"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "description": "Message."
          },
          "kind": {
            "type": "string",
            "enum": [
              "birth",
              "milestone",
              "birthday",
              "anniversary",
              "furlough",
              "praise",
              "prayer",
              "event",
              "general"
            ],
            "description": "Kind (default general)."
          },
          "missionary": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 100,
            "description": "Optional missionary: id, or slug as the gallery shows it."
          },
          "image_path": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Image in the church's media storage (<church_id>/…)."
          },
          "link": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 500,
            "pattern": "^https?://",
            "description": "Link (https)."
          },
          "starts_on": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "First day shown (default today).",
            "examples": [
              "2026-11-01"
            ]
          },
          "ends_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Last day shown (optional)."
          },
          "show_in": {
            "type": "string",
            "enum": [
              "ticker",
              "jumbotron",
              "both"
            ],
            "description": "Where it shows: the ticker, the big banner, or both (default both)."
          },
          "is_published": {
            "type": "boolean",
            "description": "Published (default true)."
          }
        },
        "required": [
          "title"
        ]
      },
      "AnnouncementCreated": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "GivingTraces": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "gift_count": {
            "type": "integer"
          },
          "given_cents": {
            "type": "integer"
          },
          "traced_cents": {
            "type": "integer"
          },
          "estimated_cents": {
            "type": "integer"
          },
          "waiting_cents": {
            "type": "integer"
          },
          "missionaries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "missionary_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "slug": {
                  "type": "string"
                },
                "display_name": {
                  "type": "string"
                },
                "restricted": {
                  "type": "boolean"
                },
                "traced_cents": {
                  "type": "integer"
                },
                "estimated_cents": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Gift": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your system's id for this gift. Re-sending the same id updates it instead of adding it again.",
            "examples": [
              "pco-donation-88123"
            ]
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000,
            "description": "Amount in cents.",
            "examples": [
              5000
            ]
          },
          "received_on": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date received (for an undated monthly total, any day in that month).",
            "examples": [
              "2026-10-05"
            ]
          },
          "dated": {
            "type": "boolean",
            "description": "false for an undated monthly total (spread evenly over that month's payments). Default true."
          },
          "refunded": {
            "type": "boolean",
            "description": "The gift was refunded (default false)."
          },
          "fund": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Your system's fund id."
              },
              "id": {
                "type": "string",
                "minLength": 36,
                "maxLength": 36,
                "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                "description": "An existing Missionary Lane fund id."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Fund name (used when the fund is created)."
              }
            },
            "description": "The fund it was given to: your system's fund id (the fund is created on first use, named `name`), or the id of an existing Missionary Lane fund."
          },
          "giver": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Your system's person id."
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Email."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200,
                "description": "Name."
              }
            },
            "description": "Who gave it (matched to givers by external id, then email)."
          }
        },
        "required": [
          "external_id",
          "amount_cents",
          "received_on"
        ]
      },
      "GiftImport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "gifts"
        ],
        "properties": {
          "connection_id": {
            "type": "string",
            "format": "uuid",
            "description": "A giving connection of this church (default: the API connection)."
          },
          "gifts": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/Gift"
            }
          }
        }
      },
      "GiftImportResult": {
        "type": "object",
        "properties": {
          "connection_id": {
            "type": "string",
            "format": "uuid"
          },
          "received": {
            "type": "integer"
          },
          "result": {
            "type": "object",
            "description": "giving_import_rows result."
          }
        }
      },
      "Payment": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "missionary": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "The missionary: id, or slug as the gallery shows it.",
            "examples": [
              "okafor-family"
            ]
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000000,
            "description": "Amount paid in cents. 0 means no payment to them this month.",
            "examples": [
              25000
            ]
          },
          "paid_on": {
            "type": "string",
            "format": "date",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
            "description": "Date paid (inside the month).",
            "examples": [
              "2026-10-15"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "proposed",
              "confirmed"
            ],
            "description": "confirmed (default) or proposed."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Note for the treasurer (e.g. your accounting reference)."
          }
        },
        "required": [
          "missionary",
          "amount_cents",
          "paid_on"
        ]
      },
      "PaymentsMonth": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "empty",
              "open",
              "confirmed"
            ]
          },
          "proposed_cents": {
            "type": "integer"
          },
          "confirmed_cents": {
            "type": "integer"
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "missionary_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "slug": {
                  "type": "string"
                },
                "display_name": {
                  "type": "string"
                },
                "restricted": {
                  "type": "boolean"
                },
                "support_amount_cents": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "payment_day": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "proposed": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "payments": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      },
      "PaymentsPut": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "payments"
        ],
        "properties": {
          "payments": {
            "type": "array",
            "maxItems": 2000,
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          }
        }
      }
    }
  }
}
