> ## 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.

# Create a user

> Call this once, after your user has agreed to the terms and tapped **Sync Now**.

FanFeed creates a profile and returns a `user_id`. **Store that id on your side** and
send it on every subsequent call. The `user_id` is the handle, and losing it means
losing the link to the user's synced history.

Email is the only required field.

Idempotent on email, scoped to your partner key: a retry returns the same `user_id`
rather than creating a second profile, and the response marks it with
`existing: true`. The same email under a different FanFeed partner is a different,
independent profile; no call made with your key returns data another partner created.




## OpenAPI

````yaml /api-reference/openapi.yaml post /users
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:
    post:
      tags:
        - Users
      summary: Create a user
      description: >
        Call this once, after your user has agreed to the terms and tapped
        **Sync Now**.


        FanFeed creates a profile and returns a `user_id`. **Store that id on
        your side** and

        send it on every subsequent call. The `user_id` is the handle, and
        losing it means

        losing the link to the user's synced history.


        Email is the only required field.


        Idempotent on email, scoped to your partner key: a retry returns the
        same `user_id`

        rather than creating a second profile, and the response marks it with

        `existing: true`. The same email under a different FanFeed partner is a
        different,

        independent profile; no call made with your key returns data another
        partner created.
      operationId: createUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: User created or returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    CreateUserRequest:
      properties:
        email:
          description: The only required field.
          examples:
            - fan@example.com
          format: email
          type: string
        partner_user_id:
          description: >
            Your own identifier for this user, stored with the partner-user link
            so both

            sides can correlate. Recommended.
          examples:
            - partner_user_12345
          anyOf:
            - type: string
            - type: 'null'
        first_name:
          description: User's first name.
          examples:
            - Jane
          anyOf:
            - type: string
            - type: 'null'
        last_name:
          description: User's last name.
          examples:
            - Doe
          anyOf:
            - type: string
            - type: 'null'
        phone_number:
          description: >
            Stored when it is not already in use by another profile, and
            silently ignored when

            it is. Never causes this call to fail.
          examples:
            - '+12125551234'
          anyOf:
            - type: string
            - type: 'null'
        address:
          anyOf:
            - $ref: '#/components/schemas/Address'
            - type: 'null'
      required:
        - email
      type: object
    CreateUserResponse:
      properties:
        user_id:
          description: Store this. Every other endpoint needs it.
          examples:
            - 8f14e45f-ceea-467a-9a1e-2b4d9c3f0a11
          format: uuid
          type: string
        existing:
          description: >-
            `true` when this call reused a previously created user record
            instead of creating a new one.
          examples:
            - false
          type: boolean
        created_at:
          examples:
            - '2026-08-25T18:31:07Z'
          format: date-time
          type: string
      required:
        - created_at
        - existing
        - user_id
      type: object
    Address:
      description: >
        Optional. Stored with the partner-user link. **Not used for matching**;
        matching is

        driven entirely by photo coordinates.
      properties:
        line1:
          examples:
            - 350 5th Ave
          anyOf:
            - type: string
            - type: 'null'
        line2:
          examples:
            - Apt 4B
          anyOf:
            - type: string
            - type: 'null'
        city:
          examples:
            - New York
          anyOf:
            - type: string
            - type: 'null'
        region:
          description: State or province code.
          examples:
            - NY
          anyOf:
            - type: string
            - type: 'null'
        postal_code:
          examples:
            - '10001'
          anyOf:
            - type: string
            - type: 'null'
        country:
          description: ISO 3166-1 alpha-2.
          examples:
            - US
          anyOf:
            - type: string
            - type: 'null'
      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
    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'
    UnsupportedMediaType:
      description: Request body must be application/json.
      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.

````