FairePlace Media API

Media management (photos, PDF documents)

GitHub OpenAPI

Operations 12

POST /media/upload/{entity_type}/{entity_id} Upload media and attach to entity #
GET /media/{entity_type}/{entity_id} List media attached to an entity #
GET /media/search Search media by tags (optionally scoped to an entity) #
PUT /media/{id}/tags Replace media tags #
PUT /media/{entity_type}/{entity_id}/{media_id}/role Assign a unique role to a media for an entity #
DELETE /media/{entity_type}/{entity_id}/role/{role} Clear role assignment for an entity #
GET /media/{entity_type}/{entity_id}/role/{role} Get media by role for an entity #
GET /media/serve/{id} Serve media (JSON for unencrypted, binary for encrypted) #
GET /media/download/{id} Download media as a binary stream #
DELETE /media/{id} Delete media (S3 object and DB record) #
GET /media/worker/{id} Serve media for workers (Base64 JSON) #
POST /media/upload-signed/{media_id} Upload signed PDF from worker #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/faireplace-com:faireplace-com-media-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

faireplace-com-media-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: FairePlace Property Management Media API
  description: 'Comprehensive API for managing properties, leases, tenants, and all aspects of real estate management.

    Built with Rust and Actix-web following hexagonal architecture principles.


    ## Authentication

    This API uses API keys for authentication with tenant-based isolation.


    ## Base URL

    - Production: `https://api.faireplace.com/api`'
  version: 1.0.0
  contact:
    name: FairePlace API Support
    email: api-support@faireplace.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT
servers:
- url: https://api.faireplace.com/api
  description: Production server
security:
- BearerAuth: []
tags:
- name: Media
  description: Media management (photos, PDF documents)
paths:
  /media/upload/{entity_type}/{entity_id}:
    post:
      operationId: uploadMediaAttachEntity
      summary: Upload media and attach to entity
      description: 'Uploads a file and immediately attaches it to the target entity.

        - MIME and size validation according to a security level determined by `entity_type`.

        - In secure mode (SSE-C), the content is stored encrypted on the S3 side and not returned in plaintext.

        - The response contains the `Media` object (not the binary).

        - Supports tags (comma-separated) and a unique role for the entity.'
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: entity_type
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/EntityType'
      - name: entity_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to upload
                tags:
                  type: string
                  description: Comma-separated tags (e.g., photo,primary,facade)
                  example: photo,primary
                role:
                  type: string
                  enum:
                  - AVATAR
                  - ID_DOCUMENT
                  - PRIMARY_PHOTO
                  - LEASE_ORIGINAL
                  - LEASE_SIGNED
                  description: Unique role of the media for this entity
                  example: PRIMARY_PHOTO
              required:
              - file
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Media'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /media/{entity_type}/{entity_id}:
    get:
      operationId: listMediaAttachedEntity
      summary: List media attached to an entity
      description: Returns the `Media` objects linked to the entity. Encrypted contents are not returned here.
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: entity_type
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/EntityType'
      - name: entity_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Media'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /media/search:
    get:
      operationId: searchMediaTags
      summary: Search media by tags (optionally scoped to an entity)
      description: 'Searches on tags (TEXT[] + GIN). `match_all=true` (default) requires that all tags match.

        Can be restricted to an entity.'
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: tags
        in: query
        required: false
        description: Comma-separated list of tags (lowercase)
        schema:
          type: string
      - name: match_all
        in: query
        required: false
        description: If true (default), all tags must match; else any
        schema:
          type: boolean
      - name: entity_type
        in: query
        required: false
        schema:
          $ref: '#/components/schemas/EntityType'
      - name: entity_id
        in: query
        required: false
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Media'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /media/{id}/tags:
    put:
      operationId: replaceMediaTags
      summary: Replace media tags
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateMediaTagsRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Media'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
  /media/{entity_type}/{entity_id}/{media_id}/role:
    put:
      operationId: assignUniqueRoleMediaEntity
      summary: Assign a unique role to a media for an entity
      description: 'Assigns a unique role (ENUM `media_role`) to a `media_id` for an entity.

        - Uniqueness is guaranteed by a partial index (tenant_id, entity_type, entity_id, role).

        - The body accepts a `role` corresponding to the MediaRole enum values (AVATAR, ID_DOCUMENT, PRIMARY_PHOTO, LEASE_ORIGINAL, LEASE_SIGNED).

        - Case-insensitive support for flexibility (e.g., "avatar" or "AVATAR").'
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: entity_type
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/EntityType'
      - name: entity_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: media_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssignRoleRequest'
      responses:
        '204':
          description: No Content
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
  /media/{entity_type}/{entity_id}/role/{role}:
    delete:
      operationId: clearRoleAssignmentEntity
      summary: Clear role assignment for an entity
      description: Removes the role assignment for the given entity.
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: entity_type
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/EntityType'
      - name: entity_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: role
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/MediaRole'
      responses:
        '204':
          description: No Content
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
    get:
      operationId: getMediaRoleEntity
      summary: Get media by role for an entity
      description: Retrieves the `Media` with the requested role for the entity.
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: entity_type
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/EntityType'
      - name: entity_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: role
        in: path
        required: true
        schema:
          $ref: '#/components/schemas/MediaRole'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Media'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /media/serve/{id}:
    get:
      operationId: serveMedia
      summary: Serve media (JSON for unencrypted, binary for encrypted)
      description: '- If the media is encrypted (SSE-C), the server returns the binary content with server-side decryption and `checksum` verification.

        - If not encrypted, returns the `Media` object (with the S3 URL).'
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Media'
            application/octet-stream:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /media/download/{id}:
    get:
      operationId: downloadMediaAsBinaryStream
      summary: Download media as a binary stream
      description: Forces the binary download (SSE-C decryption if needed) with `Content-Disposition`.
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: OK
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /media/{id}:
    delete:
      operationId: deleteMedia
      summary: Delete media (S3 object and DB record)
      tags:
      - Media
      security:
      - BearerAuth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '204':
          description: No Content
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /media/worker/{id}:
    get:
      operationId: serveMediaWorkers
      summary: Serve media for workers (Base64 JSON)
      description: 'Endpoint intended for workers (specific API key/Bearer).

        - Returns the file in Base64.

        - Requires `X-Tenant-ID` and `X-Worker-Type`.'
      tags:
      - Media
      security:
      - WorkerBearer: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: X-Worker-Type
        in: header
        required: true
        schema:
          type: string
        description: Worker type (e.g. signature-processor, otp-validator)
      - name: X-Tenant-ID
        in: header
        required: true
        schema:
          type: string
        description: Tenant identifier
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkerMediaResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /media/upload-signed/{media_id}:
    post:
      operationId: uploadSignedPDFWorker
      summary: Upload signed PDF from worker
      description: 'Uploads a signed PDF from a worker. The document is stored in secure mode (SSE-C) with `checksum`.

        Returns the metadata of the created signed media.'
      tags:
      - Media
      security:
      - WorkerApiKey: []
      parameters:
      - name: media_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: X-Tenant-ID
        in: header
        required: true
        schema:
          type: string
      - name: X-Worker-Type
        in: header
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/pdf:
            schema:
              type: string
              format: binary
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkerUploadSignedPdfResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
components:
  schemas:
    EntityType:
      type: string
      description: Business entity type supported for media
      enum:
      - PLACE
      - ESTATE
      - ROOM
      - EQUIPMENT
      - LEASE
      - OWNER
      - CONTACT
      - LESSEE
      - CONDITION_REPORT
      - LEASE_NOTICE
      - MANDATE
    Media:
      type: object
      properties:
        id:
          type:
          - string
          - 'null'
          format: uuid
        url:
          type:
          - string
          - 'null'
        media_type:
          type:
          - string
          - 'null'
          description: MIME type (e.g. application/pdf, image/jpeg)
        file_size:
          type:
          - integer
          - 'null'
          format: int64
        security_level:
          type:
          - string
          - 'null'
          allOf:
          - $ref: '#/components/schemas/MediaSecurityLevel'
        encryption_key_id:
          type:
          - string
          - 'null'
          description: SSE-C key identifier if the media is encrypted (secure mode)
        checksum:
          type:
          - string
          - 'null'
          description: SHA-256 of the source content for integrity verification
        tags:
          type:
          - array
          - 'null'
          items:
            type: string
          description: Tags normalized to lowercase; max 32 tags; each tag <= 64 chars, [a-z0-9_-]
        roles:
          type:
          - array
          - 'null'
          items:
            $ref: '#/components/schemas/MediaRole'
          description: Roles associated with this media for different entities
        created_at:
          type:
          - string
          - 'null'
          format: date-time
        updated_at:
          type:
          - string
          - 'null'
          format: date-time
    UpdateMediaTagsRequest:
      type: object
      properties:
        tags:
          type: array
          items:
            type: string
      required:
      - tags
    MediaSecurityLevel:
      type: string
      description: Security/encryption level. CRITICAL and SENSITIVE may trigger SSE-C encrypted storage; STANDARD is unencrypted.
      enum:
      - STANDARD
      - SENSITIVE
      - CRITICAL
    Error:
      type: object
      description: "The error envelope returned by every authenticated endpoint.\n\nTwo things are worth knowing before you write your error handling:\n\n- **`code` is a symbolic string, not a number.** The numeric HTTP status is\n  in `status`. Branch on `code`: it is stable, and show `message`, which\n  is written for humans and may be reworded without notice.\n- The public guest-signature endpoints use a **different, flatter shape**\n  (`{ \"error_code\": …, \"message\": … }`, no `error` wrapper), because they\n  are consumed by a page that has no session. Their responses document it\n  inline.\n\nThe `X-Request-Id` response header carries the request identifier: quote it\nwhen reporting a problem. It is a header, not a body field.\n"
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          - status
          properties:
            code:
              type: string
              description: Stable symbolic code. Branch on this.
              enum:
              - NOT_FOUND
              - UNAUTHORIZED
              - VALIDATION_ERROR
              - DATABASE_ERROR
              - BUSINESS_ERROR
              - STORAGE_ERROR
              - CONFIG_ERROR
              - FORBIDDEN
              - CONFLICT
              - PARSE_ERROR
              - SERIALIZATION_ERROR
              - AUTHENTICATION_ERROR
              - AUTHORIZATION_ERROR
              - EXTERNAL_SERVICE_ERROR
              - RATE_LIMIT_EXCEEDED
              - PAYMENT_REQUIRED
              - PLAN_LIMIT_REACHED
              - GONE
              - BAD_REQUEST
              - INTERNAL_ERROR
              - INTERNAL_SERVER_ERROR
              example: NOT_FOUND
            message:
              type: string
              description: 'Human-readable explanation, most often in French. Safe to display,

                not safe to parse.

                '
              example: Lease not found
            status:
              type: integer
              description: The HTTP status code, repeated in the body.
              example: 404
            details:
              type: object
              additionalProperties: true
              description: 'Extra machine-readable context. Only present on some errors: on

                `RATE_LIMIT_EXCEEDED` it carries `retry_after_seconds`.

                '
              example:
                retry_after_seconds: 12
    AssignRoleRequest:
      type: object
      properties:
        role:
          type: string
          description: Media role (enum MediaRole) - accepts values AVATAR, ID_DOCUMENT, PRIMARY_PHOTO, LEASE_ORIGINAL, LEASE_SIGNED
          enum:
          - AVATAR
          - ID_DOCUMENT
          - PRIMARY_PHOTO
          - LEASE_ORIGINAL
          - LEASE_SIGNED
          example: AVATAR
      required:
      - role
    MediaRole:
      type: string
      description: 'Role a media plays for the entity it is attached to.


        A role is **unique per (entity, role)** pair, which is why it cannot

        identify one rent receipt among the many a lease accumulates: fetch those

        through the `media_id` carried by the receipt itself. `RENT_RECEIPT` types

        and validates the file, it does not address it.

        '
      enum:
      - AVATAR
      - ID_DOCUMENT
      - PRIMARY_PHOTO
      - LEASE_ORIGINAL
      - LEASE_SIGNED
      - LEASE_ANNEX
      - RENT_RECEIPT
    WorkerMediaResponse:
      type: object
      properties:
        pdf_base64:
          type:
          - string
          - 'null'
        document_name:
          type:
          - string
          - 'null'
        content_type:
          type:
          - string
          - 'null'
        file_size:
          type:
          - integer
          - 'null'
          format: int64
        security_level:
          type:
          - string
          - 'null'
          allOf:
          - $ref: '#/components/schemas/MediaSecurityLevel'
    WorkerUploadSignedPdfResponse:
      type: object
      properties:
        success:
          type:
          - boolean
          - 'null'
        message:
          type:
          - string
          - 'null'
        data:
          type:
          - object
          - 'null'
          properties:
            signed_media_id:
              type:
              - string
              - 'null'
              format: uuid
            signed_pdf_url:
              type:
              - string
              - 'null'
            original_media_id:
              type:
              - string
              - 'null'
              format: uuid
            file_size:
              type:
              - integer
              - 'null'
              format: int64
            security_level:
              type:
              - string
              - 'null'
              allOf:
              - $ref: '#/components/schemas/MediaSecurityLevel'
  responses:
    Forbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    UnprocessableEntity:
      description: Unprocessable Entity
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    InternalServerError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key with fp_* prefix, passed as Bearer token
    WorkerApiKey:
      type: apiKey
      in: header
      name: X-API-Key
    WorkerBearer:
      type: http
      scheme: bearer
      bearerFormat: Worker