openapi: 3.0.3
info:
  title: 'eLifesaver API Documentation'
  description: ''
  version: 1.0.0
servers:
  -
    url: 'http://localhost/elifesaver-app/public'
tags:
  -
    name: 'AI Blood Assistant'
    description: "\nGrounded Claude chatbot answering donation questions (can I donate, why\ndeferred, where to donate, what to eat). Works for guests and logged-in users."
  -
    name: Account
    description: ''
  -
    name: Appointments
    description: "\nDonor-facing appointment booking — the mobile equivalent of the donor panel's\n\"Book a slot to donate\". A donor books a screening/donation slot at a facility;\nstaff later check them in (which opens a donation journey)."
  -
    name: Authentication
    description: "\nRegistration and Sanctum token issuance for the mobile app. Tokens are bearer\ntokens returned in the `token` field; send as `Authorization: Bearer {token}`."
  -
    name: 'Blood Requests'
    description: ''
  -
    name: 'Campaigns (Blood Drives)'
    description: ''
  -
    name: 'Community Media'
    description: "\nUpload community photos/videos. Prefer the direct-to-Cloudinary flow (get a\nsignature, upload from the device, then attach the descriptor to a post) for\nlarge files — it keeps media off the app server."
  -
    name: 'Content & Community'
    description: "\nKnowledge Center and the community feed."
  -
    name: 'Donations (financial)'
    description: "\nMoney donations to support the platform, via CamPay mobile money. Anyone can\ngive — anonymously or named — no account required. Separate from blood donation."
  -
    name: 'Donor Data'
    description: ''
  -
    name: Eligibility
    description: "\nThe admin-authored donation-eligibility questionnaire. Medical rules live in\ndata (admins edit questions/options in the panel), so the app never hardcodes\nthem — fetch the questions, submit the answers, get a decision."
  -
    name: Endpoints
    description: ''
  -
    name: Engagement
    description: "\nAchievements, certificates, leaderboards, referrals and the health tracker."
  -
    name: Lookups
    description: "\nReference data for populating mobile dropdowns. Public (no auth required)."
  -
    name: Notifications
    description: "\nIn-app notification history for the mobile app (blood appeals, emergencies,\nrequest updates, milestones). Push delivery is separate (FCM); this is the\ndurable list the user can browse and mark as read."
  -
    name: 'Organizations & Volunteers'
    description: ''
  -
    name: Payments
    description: "\nCamPay mobile-money flow for the (optional) blood-appeal fee. When payment is\ndisabled the appeal is created immediately and no charge is made."
  -
    name: Profile
    description: ''
  -
    name: Webhooks
    description: "\nServer-to-server callbacks. No user auth — verified by a shared key instead."
components:
  securitySchemes:
    default:
      type: http
      scheme: bearer
      description: 'You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.'
security:
  -
    default: []
paths:
  /api/v1/assistant/status:
    get:
      summary: 'Whether the assistant is configured/available'
      operationId: whetherTheAssistantIsConfiguredavailable
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/assistant/status could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/assistant/status could not be found.'
      tags:
        - 'AI Blood Assistant'
      security: []
  /api/v1/assistant/message:
    post:
      summary: 'Send a message to the assistant'
      operationId: sendAMessageToTheAssistant
      description: 'Pass `conversation_uuid` to continue a conversation, or omit to start a new one.'
      parameters: []
      responses: {  }
      tags:
        - 'AI Blood Assistant'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
                  description: 'Must not be greater than 2000 characters.'
                  example: vmqeopfuudtdsufvyvddq
                conversation_uuid:
                  type: string
                  description: ''
                  example: 66529e01-d113-3473-8d6f-9e11e09332ea
                  nullable: true
              required:
                - message
      security: []
  /api/v1/account:
    delete:
      summary: 'Delete my account'
      operationId: deleteMyAccount
      description: "Play Store / App Store compliant self-service deletion. The account is\n**deactivated and soft-deleted** (retained in the database for audit,\nblood-safety traceability and legal retention) — the user can no longer\nsign in. We record an optional reason. All access tokens are revoked."
      parameters: []
      responses: {  }
      tags:
        - Account
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  description: 'Must not be greater than 100 characters.'
                  example: vmqeopfuudtdsufvyvddq
                  nullable: true
                details:
                  type: string
                  description: 'Must not be greater than 1000 characters.'
                  example: amniihfqcoynlazghdtqt
                  nullable: true
  /api/v1/appointments:
    get:
      summary: 'My appointments + the data the booking form needs (facilities, tags)'
      operationId: myAppointments+TheDataTheBookingFormNeedsfacilitiesTags
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/appointments could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/appointments could not be found.'
      tags:
        - Appointments
    post:
      summary: 'Book a donation slot'
      operationId: bookADonationSlot
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Appointments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                hospital_id:
                  type: integer
                  description: ''
                  example: 17
                scheduled_at:
                  type: string
                  description: 'Must be a valid date. Must be a date after <code>now</code>.'
                  example: '2107-09-01'
                tags:
                  type: array
                  description: ''
                  example:
                    - 17
                  items:
                    type: integer
              required:
                - hospital_id
                - scheduled_at
  '/api/v1/appointments/{appointment_id}/cancel':
    post:
      summary: 'Cancel a booked appointment'
      operationId: cancelABookedAppointment
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Appointments
    parameters:
      -
        in: path
        name: appointment_id
        description: 'The ID of the appointment.'
        example: 1
        required: true
        schema:
          type: integer
  /api/v1/auth/login:
    post:
      summary: Login
      operationId: login
      description: 'Authenticate a donor, patient, doctor or admin by email OR phone.'
      parameters: []
      responses: {  }
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  description: 'This field is required when <code>phone</code> is not present. Must be a valid email address.'
                  example: qkunze@example.com
                  nullable: true
                phone:
                  type: string
                  description: 'This field is required when <code>email</code> is not present.'
                  example: consequatur
                  nullable: true
                password:
                  type: string
                  description: ''
                  example: 'O[2UZ5ij-e/dl4m{o,'
              required:
                - password
      security: []
  /api/v1/auth/register/donor:
    post:
      summary: 'Register donor'
      operationId: registerDonor
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: 'Must not be greater than 255 characters.'
                  example: vmqeopfuudtdsufvyvddq
                gender:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                email:
                  type: string
                  description: 'Must be a valid email address.'
                  example: carolyne.luettgen@example.org
                phone:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                password:
                  type: string
                  description: 'Must be at least 6 characters.'
                  example: '[2UZ5ij-e/dl4'
                address:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                city:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                blood_group:
                  type: string
                  description: 'Must match an existing stored value.'
                  example: consequatur
              required:
                - name
                - email
                - password
                - blood_group
      security: []
  /api/v1/auth/register/patient:
    post:
      summary: 'Register patient'
      operationId: registerPatient
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: 'Must not be greater than 255 characters.'
                  example: vmqeopfuudtdsufvyvddq
                gender:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                email:
                  type: string
                  description: 'Must be a valid email address.'
                  example: carolyne.luettgen@example.org
                phone:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                password:
                  type: string
                  description: 'Must be at least 6 characters.'
                  example: '[2UZ5ij-e/dl4'
                blood_group:
                  type: string
                  description: 'Must match an existing stored value.'
                  example: consequatur
                  nullable: true
              required:
                - name
                - email
                - password
      security: []
  /api/v1/me:
    get:
      summary: 'Current user'
      operationId: currentUser
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/me could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/me could not be found.'
      tags:
        - Authentication
  /api/v1/auth/logout:
    post:
      summary: Logout
      operationId: logout
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Authentication
  /api/v1/blood-requests:
    get:
      summary: 'List blood requests'
      operationId: listBloodRequests
      description: "Patients see their own requests; donors see active requests compatible\nwith their blood group."
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/blood-requests could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/blood-requests could not be found.'
      tags:
        - 'Blood Requests'
    post:
      summary: 'Create a blood request'
      operationId: createABloodRequest
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Blood Requests'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                blood_group:
                  type: string
                  description: 'Must match an existing stored value.'
                  example: consequatur
                number_of_bags:
                  type: integer
                  description: 'Must be at least 1.'
                  example: 45
                  nullable: true
                health_facility:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                hospital_id:
                  type: integer
                  description: 'Must match an existing stored value.'
                  example: 17
                  nullable: true
                medical_info:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                priority:
                  type: string
                  description: ''
                  example: urgent
                  enum:
                    - routine
                    - urgent
                    - emergency
                  nullable: true
              required:
                - blood_group
  /api/v1/blood-requests/compatible:
    get:
      summary: 'Compatible appeals near me'
      operationId: compatibleAppealsNearMe
      description: "Open appeals the donor can donate to, ranked by urgency + proximity to home\nor the town they're currently visiting."
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/blood-requests/compatible could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/blood-requests/compatible could not be found.'
      tags:
        - 'Blood Requests'
  /api/v1/blood-requests/my-pledges:
    get:
      summary: 'My pledges'
      operationId: myPledges
      description: "Appeals this donor has pledged to (responded to). Lets the app show the\ndonor their commitments — separate from appeals they created as a requester."
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/blood-requests/my-pledges could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/blood-requests/my-pledges could not be found.'
      tags:
        - 'Blood Requests'
  '/api/v1/blood-requests/{bloodRequest_id}/respond':
    post:
      summary: 'Respond to a blood request'
      operationId: respondToABloodRequest
      description: 'A donor pledges/accepts a request. Records the response and credits the donor.'
      parameters: []
      responses: {  }
      tags:
        - 'Blood Requests'
    parameters:
      -
        in: path
        name: bloodRequest_id
        description: 'The ID of the bloodRequest.'
        example: 1
        required: true
        schema:
          type: integer
  '/api/v1/blood-requests/{bloodRequest_id}/cancel':
    post:
      summary: 'Cancel a blood request'
      operationId: cancelABloodRequest
      description: "The requester (or an admin) cancels their own appeal. It immediately drops\nout of every donor's feed. Already-fulfilled or cancelled appeals can't be\ncancelled again."
      parameters: []
      responses: {  }
      tags:
        - 'Blood Requests'
    parameters:
      -
        in: path
        name: bloodRequest_id
        description: 'The ID of the bloodRequest.'
        example: 1
        required: true
        schema:
          type: integer
  '/api/v1/blood-requests/{bloodRequest_id}/escalate':
    post:
      summary: 'Escalate to Emergency Mode'
      operationId: escalateToEmergencyMode
      description: "Ranks compatible nearby donors and fans out a multi-channel appeal. Only the\nrequester (or an admin) may escalate."
      parameters: []
      responses: {  }
      tags:
        - 'Blood Requests'
    parameters:
      -
        in: path
        name: bloodRequest_id
        description: 'The ID of the bloodRequest.'
        example: 1
        required: true
        schema:
          type: integer
  '/api/v1/blood-requests/{bloodRequest_id}/matches':
    get:
      summary: 'Ranked donor matches for a request (staff/requester view).'
      operationId: rankedDonorMatchesForARequeststaffrequesterView
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/blood-requests/1/matches could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/blood-requests/1/matches could not be found.'
      tags:
        - 'Blood Requests'
    parameters:
      -
        in: path
        name: bloodRequest_id
        description: 'The ID of the bloodRequest.'
        example: 1
        required: true
        schema:
          type: integer
  /api/v1/campaigns:
    get:
      summary: 'List published campaigns (public)'
      operationId: listPublishedCampaignspublic
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/campaigns could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/campaigns could not be found.'
      tags:
        - 'Campaigns (Blood Drives)'
      security: []
  '/api/v1/campaigns/{campaign_id}':
    get:
      summary: 'Show one campaign with progress (public)'
      operationId: showOneCampaignWithProgresspublic
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/campaigns/3 could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/campaigns/3 could not be found.'
      tags:
        - 'Campaigns (Blood Drives)'
      security: []
    parameters:
      -
        in: path
        name: campaign_id
        description: 'The ID of the campaign.'
        example: 3
        required: true
        schema:
          type: integer
  '/api/v1/campaigns/{campaign_id}/register':
    post:
      summary: 'Register for a campaign'
      operationId: registerForACampaign
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Campaigns (Blood Drives)'
    parameters:
      -
        in: path
        name: campaign_id
        description: 'The ID of the campaign.'
        example: 3
        required: true
        schema:
          type: integer
  /api/v1/my/campaign-registrations:
    get:
      summary: 'My campaign registrations'
      operationId: myCampaignRegistrations
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/my/campaign-registrations could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/my/campaign-registrations could not be found.'
      tags:
        - 'Campaigns (Blood Drives)'
  /api/v1/media:
    post:
      summary: 'Upload a media file (server compresses/offloads); returns a media descriptor'
      operationId: uploadAMediaFileserverCompressesoffloadsReturnsAMediaDescriptor
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Community Media'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: 'Must be a file.'
              required:
                - file
  /api/v1/media/signature:
    get:
      summary: 'Signature for a direct browser/device upload to Cloudinary'
      operationId: signatureForADirectBrowserdeviceUploadToCloudinary
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/media/signature could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/media/signature could not be found.'
      tags:
        - 'Community Media'
  /api/v1/knowledge/categories:
    get:
      summary: 'Knowledge categories with published article counts'
      operationId: knowledgeCategoriesWithPublishedArticleCounts
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/knowledge/categories could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/knowledge/categories could not be found.'
      tags:
        - 'Content & Community'
      security: []
  /api/v1/knowledge/articles:
    get:
      summary: 'List published knowledge articles (optionally by category slug)'
      operationId: listPublishedKnowledgeArticlesoptionallyByCategorySlug
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/knowledge/articles could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/knowledge/articles could not be found.'
      tags:
        - 'Content & Community'
      security: []
  '/api/v1/knowledge/articles/{article_id}':
    get:
      summary: 'Show a single knowledge article'
      operationId: showASingleKnowledgeArticle
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/knowledge/articles/1 could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/knowledge/articles/1 could not be found.'
      tags:
        - 'Content & Community'
      security: []
    parameters:
      -
        in: path
        name: article_id
        description: 'The ID of the article.'
        example: 1
        required: true
        schema:
          type: integer
  /api/v1/feed:
    get:
      summary: 'Community feed (public posts), ranked for the viewer'
      operationId: communityFeedpublicPostsRankedForTheViewer
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/feed could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/feed could not be found.'
      tags:
        - 'Content & Community'
      security: []
  '/api/v1/posts/{post_id}/comments':
    get:
      summary: "List a post's comments"
      operationId: listAPostsComments
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/posts/170/comments could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/posts/170/comments could not be found.'
      tags:
        - 'Content & Community'
      security: []
    parameters:
      -
        in: path
        name: post_id
        description: 'The ID of the post.'
        example: 170
        required: true
        schema:
          type: integer
  /api/v1/posts:
    post:
      summary: 'Create a post'
      operationId: createAPost
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Content & Community'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                body:
                  type: string
                  description: 'Must not be greater than 5000 characters.'
                  example: vmqeopfuudtdsufvyvddq
                type:
                  type: string
                  description: ''
                  example: announcement
                  enum:
                    - update
                    - announcement
                    - educational
                    - milestone
                  nullable: true
                image_url:
                  type: string
                  description: 'Must be a valid URL.'
                  example: 'http://www.kunde.com/'
                  nullable: true
                is_anonymous:
                  type: boolean
                  description: ''
                  example: false
                  nullable: true
                media:
                  type: array
                  description: ''
                  example: null
                  items:
                    type: object
                    nullable: true
                    properties:
                      type:
                        type: string
                        description: 'This field is required when <code>media</code> is present.'
                        example: image
                        enum:
                          - image
                          - video
                      url:
                        type: string
                        description: 'This field is required when <code>media</code> is present. Must be a valid URL.'
                        example: 'http://www.harber.com/fuga-aspernatur-natus-earum-quas'
              required:
                - body
  '/api/v1/posts/{post_id}/react':
    post:
      summary: 'React to a post (toggle)'
      operationId: reactToAPosttoggle
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Content & Community'
    parameters:
      -
        in: path
        name: post_id
        description: 'The ID of the post.'
        example: 170
        required: true
        schema:
          type: integer
  '/api/v1/posts/{post_id}/comment':
    post:
      summary: 'Comment on a post'
      operationId: commentOnAPost
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Content & Community'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                body:
                  type: string
                  description: 'Must not be greater than 2000 characters.'
                  example: vmqeopfuudtdsufvyvddq
                parent_id:
                  type: integer
                  description: 'Must match an existing stored value.'
                  example: 17
                  nullable: true
                is_anonymous:
                  type: boolean
                  description: ''
                  example: true
                  nullable: true
              required:
                - body
    parameters:
      -
        in: path
        name: post_id
        description: 'The ID of the post.'
        example: 170
        required: true
        schema:
          type: integer
  /api/v1/donate/info:
    get:
      summary: 'Donation config: enabled, currency, minimum + suggested amounts.'
      operationId: donationConfigEnabledCurrencyMinimum+SuggestedAmounts
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/donate/info could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/donate/info could not be found.'
      tags:
        - 'Donations (financial)'
      security: []
  /api/v1/donate:
    post:
      summary: "Initiate a donation collection (mobile-money prompt to the donor's phone)."
      operationId: initiateADonationCollectionmobileMoneyPromptToTheDonorsPhone
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Donations (financial)'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: integer
                  description: ''
                  example: 17
                phone:
                  type: string
                  description: ''
                  example: consequatur
                is_anonymous:
                  type: boolean
                  description: ''
                  example: false
                  nullable: true
                donor_name:
                  type: string
                  description: 'Must not be greater than 120 characters.'
                  example: mqeopfuudtdsufvyvddqa
                  nullable: true
                message:
                  type: string
                  description: 'Must not be greater than 500 characters.'
                  example: mniihfqcoynlazghdtqtq
                  nullable: true
              required:
                - amount
                - phone
      security: []
  /api/v1/credit:
    get:
      summary: 'Get donor credit balance'
      operationId: getDonorCreditBalance
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/credit could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/credit could not be found.'
      tags:
        - 'Donor Data'
  /api/v1/donation-info:
    get:
      summary: 'Next eligible donation date'
      operationId: nextEligibleDonationDate
      description: 'Males wait 3 months, females 4 months between donations.'
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/donation-info could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/donation-info could not be found.'
      tags:
        - 'Donor Data'
  /api/v1/results:
    get:
      summary: 'Get serology / blood test results'
      operationId: getSerologyBloodTestResults
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/results could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/results could not be found.'
      tags:
        - 'Donor Data'
  /api/v1/eligibility/questions:
    get:
      summary: 'Eligibility questionnaire'
      operationId: eligibilityQuestionnaire
      description: 'The active questions that apply to this donor, each with its answer options.'
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/eligibility/questions could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/eligibility/questions could not be found.'
      tags:
        - Eligibility
  /api/v1/eligibility/assess:
    post:
      summary: 'Submit eligibility answers'
      operationId: submitEligibilityAnswers
      description: "Records a self-assessment and returns the decision (eligible / deferred /\nrefer) plus any advice. This is advisory — staff re-screen at the centre."
      parameters: []
      responses: {  }
      tags:
        - Eligibility
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                answers:
                  type: object
                  description: 'Map of question_id => chosen option_id.'
                  example:
                    1: 2
                    3: 5
                  properties: {  }
              required:
                - answers
  /api/v1/towns:
    get:
      summary: ''
      operationId: getApiV1Towns
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/towns could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/towns could not be found.'
      tags:
        - Endpoints
      security: []
  /api/v1/leaderboard:
    get:
      summary: 'Public leaderboard (scope: national|region|hospital|gender)'
      operationId: publicLeaderboardscopeNationalregionhospitalgender
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/leaderboard could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/leaderboard could not be found.'
      tags:
        - Engagement
      security: []
  /api/v1/achievements:
    get:
      summary: 'My achievements'
      operationId: myAchievements
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/achievements could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/achievements could not be found.'
      tags:
        - Engagement
  /api/v1/certificates:
    get:
      summary: 'My certificates'
      operationId: myCertificates
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/certificates could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/certificates could not be found.'
      tags:
        - Engagement
  '/api/v1/certificates/{uuid}/download':
    get:
      summary: 'Download a certificate PDF'
      operationId: downloadACertificatePDF
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/certificates/66529e01-d113-3473-8d6f-9e11e09332ea/download could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/certificates/66529e01-d113-3473-8d6f-9e11e09332ea/download could not be found.'
      tags:
        - Engagement
    parameters:
      -
        in: path
        name: uuid
        description: ''
        example: 66529e01-d113-3473-8d6f-9e11e09332ea
        required: true
        schema:
          type: string
  /api/v1/referral:
    get:
      summary: 'My referral code + stats'
      operationId: myReferralCode+Stats
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/referral could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/referral could not be found.'
      tags:
        - Engagement
  /api/v1/health-records:
    get:
      summary: 'List my health records'
      operationId: listMyHealthRecords
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/health-records could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/health-records could not be found.'
      tags:
        - Engagement
    post:
      summary: 'Add a health record (BMI auto-computed)'
      operationId: addAHealthRecordBMIAutoComputed
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Engagement
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                weight_kg:
                  type: number
                  description: 'Must be at least 0. Must not be greater than 400.'
                  example: 21
                  nullable: true
                height_cm:
                  type: number
                  description: 'Must be at least 0. Must not be greater than 300.'
                  example: 13
                  nullable: true
                hemoglobin:
                  type: number
                  description: 'Must be at least 0. Must not be greater than 30.'
                  example: 16
                  nullable: true
                bp_systolic:
                  type: integer
                  description: 'Must be at least 0. Must not be greater than 300.'
                  example: 5
                  nullable: true
                bp_diastolic:
                  type: integer
                  description: 'Must be at least 0. Must not be greater than 200.'
                  example: 14
                  nullable: true
                pulse:
                  type: integer
                  description: 'Must be at least 0. Must not be greater than 300.'
                  example: 16
                  nullable: true
                temperature:
                  type: number
                  description: 'Must be at least 30. Must not be greater than 45.'
                  example: 6
                  nullable: true
                recorded_at:
                  type: string
                  description: 'Must be a valid date.'
                  example: '2026-08-03T01:03:13'
                  nullable: true
  /api/v1/hospitals:
    get:
      summary: 'List hospitals & health facilities'
      operationId: listHospitalsHealthFacilities
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/hospitals could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/hospitals could not be found.'
      tags:
        - Lookups
      security: []
  '/api/v1/hospitals/{hospital_id}/location':
    get:
      summary: "Get one hospital's GPS coordinates"
      operationId: getOneHospitalsGPSCoordinates
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/hospitals/1/location could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/hospitals/1/location could not be found.'
      tags:
        - Lookups
      security: []
    parameters:
      -
        in: path
        name: hospital_id
        description: 'The ID of the hospital.'
        example: 1
        required: true
        schema:
          type: integer
  /api/v1/blood-groups:
    get:
      summary: 'List blood groups'
      operationId: listBloodGroups
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/blood-groups could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/blood-groups could not be found.'
      tags:
        - Lookups
      security: []
  /api/v1/regions:
    get:
      summary: 'List regions (geography)'
      operationId: listRegionsgeography
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/regions could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/regions could not be found.'
      tags:
        - Lookups
      security: []
  /api/v1/celebration-assets:
    get:
      summary: 'Gamification assets — mascot moods, celebration banners + sounds, by trigger'
      operationId: gamificationAssetsMascotMoodsCelebrationBanners+SoundsByTrigger
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/celebration-assets could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/celebration-assets could not be found.'
      tags:
        - Lookups
      security: []
  /api/v1/app-config:
    get:
      summary: 'App startup config: version gating + social handles (admin-editable)'
      operationId: appStartupConfigVersionGating+SocialHandlesadminEditable
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/app-config could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/app-config could not be found.'
      tags:
        - Lookups
      security: []
  /api/v1/notifications:
    get:
      summary: 'List my notifications (paginated, newest first) + unread count'
      operationId: listMyNotificationspaginatedNewestFirst+UnreadCount
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/notifications could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/notifications could not be found.'
      tags:
        - Notifications
  '/api/v1/notifications/{id}/read':
    post:
      summary: 'Mark a single notification as read'
      operationId: markASingleNotificationAsRead
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Notifications
    parameters:
      -
        in: path
        name: id
        description: 'The ID of the notification.'
        example: consequatur
        required: true
        schema:
          type: string
  /api/v1/notifications/read-all:
    post:
      summary: 'Mark all my notifications as read'
      operationId: markAllMyNotificationsAsRead
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Notifications
  /api/v1/organizations/leaderboard:
    get:
      summary: 'Public organization leaderboard (by lives impacted)'
      operationId: publicOrganizationLeaderboardbyLivesImpacted
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/organizations/leaderboard could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/organizations/leaderboard could not be found.'
      tags:
        - 'Organizations & Volunteers'
      security: []
  '/api/v1/organizations/{organization_id}':
    get:
      summary: 'Public organization profile + impact stats'
      operationId: publicOrganizationProfile+ImpactStats
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/organizations/4 could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/organizations/4 could not be found.'
      tags:
        - 'Organizations & Volunteers'
      security: []
    parameters:
      -
        in: path
        name: organization_id
        description: 'The ID of the organization.'
        example: 4
        required: true
        schema:
          type: integer
  /api/v1/organizations/join:
    post:
      summary: 'Join an organization via its invite code (as a registered donor)'
      operationId: joinAnOrganizationViaItsInviteCodeasARegisteredDonor
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Organizations & Volunteers'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                invite_code:
                  type: string
                  description: ''
                  example: consequatur
              required:
                - invite_code
  /api/v1/volunteers/apply:
    post:
      summary: 'Apply to volunteer'
      operationId: applyToVolunteer
      description: ''
      parameters: []
      responses: {  }
      tags:
        - 'Organizations & Volunteers'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: string
                  description: ''
                  example: regional_ambassador
                  enum:
                    - campaign_volunteer
                    - event_coordinator
                    - regional_ambassador
                    - campus_ambassador
                organization_id:
                  type: integer
                  description: 'Must match an existing stored value.'
                  example: 17
                  nullable: true
              required:
                - type
  /api/v1/payments/info:
    get:
      summary: 'Payment configuration (call before showing a payment UI)'
      operationId: paymentConfigurationcallBeforeShowingAPaymentUI
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/payments/info could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/payments/info could not be found.'
      tags:
        - Payments
      security: []
  '/api/v1/payments/{reference}/status':
    get:
      summary: 'Check payment status (poll every ~5s) — reconciles with CamPay.'
      operationId: checkPaymentStatuspollEvery5sReconcilesWithCamPay
      description: ''
      parameters: []
      responses:
        404:
          description: ''
          content:
            application/json:
              schema:
                type: object
                example:
                  message: 'The route elifesaver-app/public/api/v1/payments/consequatur/status could not be found.'
                properties:
                  message:
                    type: string
                    example: 'The route elifesaver-app/public/api/v1/payments/consequatur/status could not be found.'
      tags:
        - Payments
      security: []
    parameters:
      -
        in: path
        name: reference
        description: ''
        example: consequatur
        required: true
        schema:
          type: string
  /api/v1/payments/initiate:
    post:
      summary: 'Initiate a blood-appeal payment'
      operationId: initiateABloodAppealPayment
      description: "If payment is disabled the appeal is created immediately (free). Otherwise a\npayment record is created and a CamPay collection is initiated."
      parameters: []
      responses: {  }
      tags:
        - Payments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                phone:
                  type: string
                  description: 'This field is required when <code>payment_enabled</code> is <code>true</code>.'
                  example: consequatur
                  nullable: true
                blood_group:
                  type: string
                  description: 'Must match an existing stored value.'
                  example: consequatur
                number_of_bags:
                  type: integer
                  description: 'Must be at least 1.'
                  example: 45
                  nullable: true
                health_facility:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                medical_info:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
              required:
                - blood_group
  /api/v1/profile:
    put:
      summary: "Update the authenticated user's profile"
      operationId: updateTheAuthenticatedUsersProfile
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Profile
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: 'Must not be greater than 255 characters.'
                  example: vmqeopfuudtdsufvyvddq
                password:
                  type: string
                  description: 'Must be at least 6 characters.'
                  example: OP>@;4
                  nullable: true
                phone:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                address:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                city:
                  type: string
                  description: ''
                  example: consequatur
                  nullable: true
                avatar_url:
                  type: string
                  description: 'Must be a valid URL. Must not be greater than 2048 characters.'
                  example: 'https://www.mueller.com/laborum-eius-est-dolor-dolores-minus-voluptatem'
                  nullable: true
                blood_group:
                  type: string
                  description: 'Must match an existing stored value.'
                  example: consequatur
                  nullable: true
  /api/v1/profile/device-token:
    post:
      summary: 'Register / update the device FCM token for push notifications'
      operationId: registerUpdateTheDeviceFCMTokenForPushNotifications
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                registration_token:
                  type: string
                  description: ''
                  example: consequatur
              required:
                - registration_token
  /api/v1/profile/location:
    post:
      summary: "Update my coarse current location (town I'm visiting)"
      operationId: updateMyCoarseCurrentLocationtownImVisiting
      description: "Consent-based and deliberately imprecise (city/subdivision only) so nearby\nappeals follow the donor when travelling. Send `shares_location:false` to stop."
      parameters: []
      responses: {  }
      tags:
        - Profile
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                current_subdivision_id:
                  type: integer
                  description: 'Must match an existing stored value.'
                  example: 17
                  nullable: true
                current_city:
                  type: string
                  description: 'Must not be greater than 120 characters.'
                  example: mqeopfuudtdsufvyvddqa
                  nullable: true
                shares_location:
                  type: boolean
                  description: ''
                  example: true
              required:
                - shares_location
  /api/v1/webhooks/campay:
    post:
      summary: 'CamPay payment IPN'
      operationId: camPayPaymentIPN
      description: ''
      parameters: []
      responses: {  }
      tags:
        - Webhooks
      security: []
