> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fanfeed.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Get a user's profile statistics

> Aggregate statistics derived from the user's matched event history: the numbers behind
a profile screen. Recomputed as new media is ingested.

Also carries `has_synced` and `last_sync_at`, the two fields your sync logic reads at
launch to decide between a full and an incremental scan. Both are meaningful even for
a user with no matched events.




## OpenAPI

````yaml /api-reference/openapi.yaml get /users/{user_id}/stats
openapi: 3.1.0
info:
  title: FanFeed Partner API
  version: 1.0.0
  description: >
    FanFeed turns a user's camera roll into an attended-event history. Your app
    sends photo

    *metadata*; FanFeed matches it against live-event data and returns the
    events those photos

    were taken at, along with the stats derived from that history.


    Five endpoints cover the whole integration:


    | Endpoint | Purpose |

    |---|---|

    | `POST /users` | Create a FanFeed user, once, when your user opts in |

    | `POST /users/{user_id}/media` | Send photo metadata in batches; get
    matched events back |

    | `POST /users/{user_id}/sync-complete` | Tell FanFeed a library scan
    finished |

    | `GET /users/{user_id}/events` | The user's matched event history |

    | `GET /users/{user_id}/stats` | Profile statistics |
  contact:
    name: FanFeed Engineering
    email: engineering@fanfeed.ai
servers:
  - url: https://api.fanfeed.ai/v1
    description: Production
  - url: https://api-dev.fanfeed.ai/v1
    description: Development
security:
  - PartnerApiKey: []
tags:
  - name: Users
    description: Creating a FanFeed user for one of your users.
  - name: Media
    description: Batch camera-roll metadata ingest and event matching.
  - name: Sync
    description: Recording that a library scan finished.
  - name: Events
    description: A user's matched event history.
  - name: Stats
    description: Profile statistics derived from the event history.
paths:
  /users/{user_id}/stats:
    get:
      tags:
        - Stats
      summary: Get a user's profile statistics
      description: >
        Aggregate statistics derived from the user's matched event history: the
        numbers behind

        a profile screen. Recomputed as new media is ingested.


        Also carries `has_synced` and `last_sync_at`, the two fields your sync
        logic reads at

        launch to decide between a full and an incremental scan. Both are
        meaningful even for

        a user with no matched events.
      operationId: getUserStats
      parameters:
        - $ref: '#/components/parameters/UserId'
      responses:
        '200':
          description: Profile statistics.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  parameters:
    UserId:
      name: user_id
      in: path
      required: true
      description: The FanFeed user id returned by `POST /users`.
      schema:
        type: string
        format: uuid
        examples:
          - 8f14e45f-ceea-467a-9a1e-2b4d9c3f0a11
  schemas:
    StatsResponse:
      description: >
        All fields are snake_case, consistent with the rest of this API.
        Superlative fields are

        `null` for users with too little history to compute them.
      properties:
        total_events:
          examples:
            - 47
          type: integer
        total_venues:
          examples:
            - 23
          type: integer
        sports_count:
          examples:
            - 12
          default: 0
          description: A fresh profile has 0 sports events, not `null`.
          type: integer
        concerts_count:
          examples:
            - 35
          default: 0
          description: A fresh profile has 0 concert events, not `null`.
          type: integer
        first_attended:
          description: >
            The calendar date at the venue, taken from the event's local start
            time, the same

            basis as `starts_at_local`, with the time dropped. Not a UTC date;
            do not convert it.
          examples:
            - '2014-08-02'
          anyOf:
            - format: date
              type: string
            - type: 'null'
        most_recently_attended:
          description: >-
            The calendar date at the venue, on the same basis as
            `first_attended`.
          examples:
            - '2026-06-13'
          anyOf:
            - format: date
              type: string
            - type: 'null'
        busiest_year:
          examples:
            - '2024'
          anyOf:
            - type: string
            - type: 'null'
        top_season:
          examples:
            - Summer
          anyOf:
            - type: string
            - type: 'null'
        top_artist:
          examples:
            - Zach Bryan
          anyOf:
            - type: string
            - type: 'null'
        top_genre:
          examples:
            - Country
          anyOf:
            - type: string
            - type: 'null'
        top_team:
          examples:
            - null
          anyOf:
            - type: string
            - type: 'null'
        top_league:
          examples:
            - null
          anyOf:
            - type: string
            - type: 'null'
        unique_venues:
          description: Unique venues with visits, or an empty list for a fresh profile.
          items:
            $ref: '#/components/schemas/VenueVisits'
          type: array
        has_synced:
          description: >
            Whether this user has ever completed a scan that examined the
            library. Read it at

            launch to decide between a full library scan and an incremental one.
          examples:
            - true
          type: boolean
        last_sync_at:
          description: >
            Server-side timestamp of the last time FanFeed received media or a
            `sync-complete`

            for this user. `null` until FanFeed first receives anything.
          examples:
            - '2026-08-25T18:44:12Z'
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
      required:
        - has_synced
        - total_events
        - total_venues
      type: object
    VenueVisits:
      properties:
        venue:
          $ref: '#/components/schemas/Venue'
        visit_count:
          examples:
            - 6
          type: integer
      required:
        - venue
        - visit_count
      type: object
    Error:
      description: The published error envelope.
      properties:
        error:
          description: The ``error`` member of the envelope.
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
              description: Stable, machine-readable error code.
            message:
              description: Human-readable explanation. Not intended for end users.
              examples:
                - Send at most 200 media items per request.
              type: string
            details:
              description: >
                Optional per-field or per-item context. Present only where it
                adds something;

                its shape varies by code.
              anyOf:
                - additionalProperties: true
                  type: object
                - type: 'null'
          required:
            - code
            - message
          type: object
      required:
        - error
      type: object
    Venue:
      properties:
        id:
          examples:
            - 4412
          type: integer
        name:
          examples:
            - Madison Square Garden
          type: string
        city:
          examples:
            - New York
          anyOf:
            - type: string
            - type: 'null'
        region:
          examples:
            - NY
          anyOf:
            - type: string
            - type: 'null'
        country:
          examples:
            - US
          anyOf:
            - type: string
            - type: 'null'
        latitude:
          examples:
            - 40.750504
          anyOf:
            - type: number
            - type: 'null'
          format: double
        longitude:
          examples:
            - -73.993439
          anyOf:
            - type: number
            - type: 'null'
          format: double
      required:
        - id
        - name
      type: object
    ErrorCode:
      description: >
        Stable, machine-readable error code. Branch on this, not on `message`.


        Note that per-item rejections inside a media batch are *not* errors: the
        request

        still returns `200` and the items appear in `rejected[]` with a
        `reason`. An

        `error.code` means the whole request failed.


        The first nine codes are published application errors. The last four are

        framework-level codes that malformed or unsupported requests can trigger
        (for

        example, a non-JSON body returns `unsupported_media_type` and an unknown
        path

        returns `not_found`). Treat any unrecognized code as a retry or fix-once
        failure

        according to the HTTP status.
      examples:
        - invalid_batch_size
      enum:
        - invalid_api_key
        - user_not_found
        - invalid_email
        - empty_batch
        - invalid_batch_size
        - invalid_media_item
        - invalid_cursor
        - rate_limited
        - internal_error
        - unsupported_media_type
        - invalid_request
        - not_found
        - method_not_allowed
      type: string
  responses:
    BadRequest:
      description: The request was malformed or failed validation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: No FanFeed user with that id.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooManyRequests:
      description: >
        Rate limited. Nothing is throttled today, but the limiter exists, so
        build for this

        from day one: honor `Retry-After` and back off.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    PartnerApiKey:
      type: apiKey
      in: header
      name: X-PARTNER-API-KEY
      description: >
        Your FanFeed partner API key, issued to your organization.


        FanFeed trusts your authentication: the key identifies you, and the
        `user_id` in the

        path identifies which of your users the request is for. FanFeed performs
        no end-user

        authentication of its own.


        Treat the key as a server-side secret. See the guide for what that
        implies for the

        camera-roll ingest path, which is the one call that naturally originates
        on-device.

````