FairePlace Media API
Media management (photos, PDF documents)
Operations 12
PUT
/media/{entity_type}/{entity_id}/{media_id}/role
Assign a unique role to a media for an entity
#
Media management (photos, PDF documents)
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/faireplace-com:faireplace-com-media-api"
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.
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: 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