openapi: 3.2.0 info: title: Firma Partner Workspaces API description: RESTful API for document signing and template management. version: 01.38.00 contact: name: API Support url: https://firma.com/support servers: - url: https://api.firma.dev/functions/v1/signing-request-api description: Production API - Recommended (Current) - url: https://api.firma.dev/api/v1 description: Production API - Planned security: - ApiKeyAuth: [] tags: - name: Workspaces description: Workspace management operations paths: /workspaces: get: summary: List workspaces description: Retrieve a paginated list of workspaces for the authenticated company tags: - Workspaces security: - ApiKeyAuth: [] parameters: - name: page in: query description: Page number for pagination schema: type: integer minimum: 1 default: 1 - name: page_size in: query description: Number of items per page schema: type: integer minimum: 1 maximum: 200 default: 50 - name: name in: query schema: type: string description: Filter by workspace name (partial match, case-insensitive) - name: protected in: query schema: type: string enum: - '0' - '1' - 'true' - 'false' description: Filter by protected status - name: created_after in: query schema: type: string format: date-time description: Filter workspaces created after this date (ISO 8601 format) - name: created_before in: query schema: type: string format: date-time description: Filter workspaces created before this date (ISO 8601 format) - name: sort_by in: query schema: type: string enum: - name - protected - created_on default: created_on description: Field to sort by - name: sort_order in: query schema: type: string enum: - asc - desc default: desc description: Sort order responses: '200': description: Workspaces retrieved successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 200 requests per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/WorkspaceListResponse' example: results: - id: 789e4567-e89b-12d3-a456-426614174000 name: Sales Workspace protected: false api_key: fk_a1b2c3d4e5f6g7h8i9j0 created_date: '2024-01-20T09:00:00Z' updated_date: '2024-01-20T09:00:00Z' - id: 456e4567-e89b-12d3-a456-426614174000 name: Default Workspace protected: true api_key: fk_z9y8x7w6v5u4t3s2r1q0 created_date: '2024-01-15T10:30:00Z' updated_date: '2024-01-15T10:30:00Z' pagination: current_page: 1 page_size: 20 total_count: 2 total_pages: 1 '400': description: Validation error - invalid date format or sort parameters content: application/json: examples: invalidDate: summary: Invalid date format value: error: Validation failed code: VALIDATION_ERROR details: - path: - created_after message: created_after must be a valid ISO 8601 date invalidSort: summary: Invalid sort parameter value: error: Validation failed code: VALIDATION_ERROR details: - path: - sort_order message: sort_order must be either 'asc' or 'desc' '401': $ref: '#/components/responses/UnauthorizedError' '429': $ref: '#/components/responses/RateLimitError' operationId: listWorkspaces x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: 'import { FirmaClient } from "@firma-dev/sdk"; const firma = new FirmaClient({ apiKey: "YOUR_API_KEY" }); const response = await firma.workspaces.listWorkspaces(); console.log(response);' post: summary: Create a new workspace description: Create a new workspace for the authenticated company. All workspaces are created as non-protected by default. The protected status can only be set during company account creation. tags: - Workspaces security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: type: object required: - name properties: name: type: string maxLength: 255 description: Workspace name example: name: Marketing Workspace responses: '201': description: Workspace created successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 120 requests per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/Workspace' example: id: abc12345-e89b-12d3-a456-426614174000 name: Marketing Workspace protected: false api_key: fk_m1n2o3p4q5r6s7t8u9v0 created_date: '2024-01-25T14:00:00Z' updated_date: '2024-01-25T14:00:00Z' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/UnauthorizedError' '429': $ref: '#/components/responses/RateLimitError' operationId: createWorkspace x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.createWorkspace({\n name: \"Marketing Workspace\"\n});\nconsole.log(response);" /workspaces/{id}: get: summary: Get a workspace description: Retrieve a specific workspace by ID including its API key tags: - Workspaces security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: Workspace ID schema: type: string format: uuid responses: '200': description: Workspace retrieved successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 200 requests per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/Workspace' example: id: 789e4567-e89b-12d3-a456-426614174000 name: Sales Workspace protected: false api_key: fk_a1b2c3d4e5f6g7h8i9j0 created_date: '2024-01-20T09:00:00Z' updated_date: '2024-01-20T09:00:00Z' webhook_enabled: false webhook_secret: null webhook_secret_rotated_at: null webhook_secret_created_at: null ignore_company_webhooks: false '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitError' operationId: getWorkspace x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.getWorkspace({\n id: \"id\"\n});\nconsole.log(response);" put: summary: Update a workspace description: Update an existing workspace's information. This is a full replacement operation requiring the name field. tags: - Workspaces security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: Workspace ID schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object required: - name properties: name: type: string maxLength: 255 description: Workspace name example: name: Enterprise Sales Workspace responses: '200': description: Workspace updated successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 120 requests per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/Workspace' '400': $ref: '#/components/responses/ValidationError' '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitError' operationId: updateWorkspace x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.updateWorkspace({\n id: \"id\",\n name: \"Enterprise Sales Workspace\"\n});\nconsole.log(response);" patch: summary: Partially update a workspace description: Update specific workspace fields. Only the name can be updated via API. The protected field is read-only and can only be set during company account creation. tags: - Workspaces security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: Workspace ID schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object properties: name: type: string maxLength: 255 description: Workspace name example: name: Updated Workspace Name responses: '200': description: Workspace updated successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 120 requests per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/Workspace' '400': description: Validation error content: application/json: examples: protectedField: summary: Attempted to update protected field value: error: Validation failed details: - path: - protected message: The 'protected' field cannot be updated via API noFields: summary: No fields provided value: error: Validation failed details: - path: - body message: No fields provided to update '401': $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitError' operationId: patchWorkspace x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.patchWorkspace({\n id: \"id\",\n name: \"Updated Workspace Name\"\n});\nconsole.log(response);" /workspaces/{id}/api-key/regenerate: post: summary: Regenerate workspace API key description: Regenerates the API key for a workspace. Sets 24-hour expiration on existing active keys and creates a new key. Cannot be used on protected workspaces. Rate limited to 1 request per minute. tags: - Workspaces security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: Workspace ID schema: type: string format: uuid requestBody: required: false content: application/json: schema: type: object properties: key_type: type: string enum: - live - test default: live description: Which key type to regenerate ('live' or 'test'). Defaults to 'live'. Regenerating one type does not expire or affect the other. responses: '201': description: API key regenerated successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 1 request per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/ApiKeyRegenerateResponse' example: new_key: api_key: firma_live_abc123xyz... created_at: '2024-12-17T10:30:00Z' key_type: live expiring_keys: - id: old-key-uuid api_key: firma_old456... expires_at: '2024-12-18T10:30:00Z' workspace_id: 123e4567-e89b-12d3-a456-426614174000 '400': description: Cannot regenerate key for protected workspace content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Cannot regenerate API key for protected workspace '401': $ref: '#/components/responses/UnauthorizedError' '403': $ref: '#/components/responses/ForbiddenError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitError' operationId: regenerateWorkspaceApiKey x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.regenerateWorkspaceApiKey({\n id: \"id\"\n});\nconsole.log(response);" /workspaces/{id}/api-key/expire: post: summary: Expire pending API keys description: Immediately expires all pending API keys (those with expires_at set) for a workspace. Useful after verifying that the new key works correctly. Cannot be used on protected workspaces. Rate limited to 1 request per minute. tags: - Workspaces security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: Workspace ID schema: type: string format: uuid requestBody: required: false content: application/json: schema: type: object properties: key_type: type: string enum: - live - test description: Optionally restrict expiry to one key type ('live' or 'test'). If omitted, all pending keys are expired (current behavior). responses: '200': description: Pending keys expired successfully headers: X-RateLimit-Limit: schema: type: integer description: 'Rate limit: 1 request per minute' X-RateLimit-Remaining: schema: type: integer description: Remaining requests in current window X-RateLimit-Reset: schema: type: integer description: Unix timestamp when rate limit resets content: application/json: schema: $ref: '#/components/schemas/ApiKeyExpireResponse' example: success: true expired_keys: - id: old-key-uuid api_key: firma_old456... original_expires_at: '2024-12-18T10:30:00Z' expired_at: '2024-12-17T10:30:00Z' workspace_id: 123e4567-e89b-12d3-a456-426614174000 '400': description: Cannot expire keys for protected workspace content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Cannot expire API keys for protected workspace '401': $ref: '#/components/responses/UnauthorizedError' '403': $ref: '#/components/responses/ForbiddenError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitError' operationId: expireWorkspaceApiKey x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.expireWorkspaceApiKey({\n id: \"id\"\n});\nconsole.log(response);" /workspaces/{id}/logo: post: tags: - Workspaces summary: Upload Workspace Logo description: Upload a logo for a specific workspace. Overrides the company logo on certificates and emails for this workspace. Accepts PNG or JPEG, max 2MB. operationId: uploadWorkspaceLogo parameters: - name: id in: path required: true schema: type: string format: uuid description: Workspace ID requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: Logo image file (PNG or JPEG, max 2MB) required: - file responses: '200': description: Logo uploaded successfully content: application/json: schema: $ref: '#/components/schemas/LogoUploadResponse' '400': description: Invalid file (wrong format, too large, or missing) x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import * as fs from \"fs\";\nimport { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.uploadWorkspaceLogo({\n file: fs.createReadStream(\"/path/to/your/file\"),\n id: \"id\"\n});\nconsole.log(response);" delete: tags: - Workspaces summary: Remove Workspace Logo description: Remove the workspace logo. Certificates will fall back to the company logo, or the Firma default. operationId: deleteWorkspaceLogo parameters: - name: id in: path required: true schema: type: string format: uuid description: Workspace ID responses: '200': description: Logo removed successfully content: application/json: schema: $ref: '#/components/schemas/LogoDeleteResponse' x-codeSamples: - lang: TypeScript label: '@firma-dev/sdk' source: "import { FirmaClient } from \"@firma-dev/sdk\";\n\nconst firma = new FirmaClient({ apiKey: \"YOUR_API_KEY\" });\n\nconst response = await firma.workspaces.deleteWorkspaceLogo({\n id: \"id\"\n});\nconsole.log(response);" components: responses: RateLimitError: description: Too Many Requests - Rate limit exceeded headers: X-RateLimit-Limit: schema: type: integer description: Maximum requests per minute X-RateLimit-Remaining: schema: type: integer description: Requests remaining X-RateLimit-Reset: schema: type: integer description: Unix timestamp of reset Retry-After: schema: type: integer description: Seconds until retry allowed content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Rate Limit Exceeded message: Too many requests. Please wait before retrying. details: retry_after: 45 UnauthorizedError: description: Unauthorized - Invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Unauthorized message: Invalid API key ValidationError: description: Bad Request - Validation failed content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Validation Error message: Invalid input data details: name: Name is required email: Invalid email format ForbiddenError: description: Forbidden - Insufficient permissions content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Forbidden message: You do not have permission to access this resource NotFoundError: description: Not Found - Resource does not exist content: application/json: schema: $ref: '#/components/schemas/Error' example: error: Not Found message: The requested resource was not found schemas: ApiKeyExpireResponse: type: object properties: success: type: boolean description: Whether the operation succeeded expired_keys: type: array items: type: object properties: id: type: string format: uuid api_key: type: string description: The expired API key value original_expires_at: type: string format: date-time description: When the key was originally set to expire expired_at: type: string format: date-time description: When the key was actually expired description: List of expired keys with details workspace_id: type: string format: uuid message: type: string description: Optional message when no pending keys exist description: API key expiration result required: - success - workspace_id LogoDeleteResponse: type: object properties: success: type: boolean description: Logo deletion confirmation required: - success Pagination: type: object properties: current_page: type: integer page_size: type: integer total_count: type: integer total_pages: type: integer description: Pagination metadata for list responses required: - current_page - page_size - total_count - total_pages Workspace: type: object properties: id: type: string format: uuid description: Unique identifier for the workspace name: type: string description: Workspace name maxLength: 255 protected: type: boolean description: Protected workspaces cannot be deleted api_key: type: - string - 'null' description: Live-mode API key for this workspace. Used for authenticating API requests scoped to this workspace. test_api_key: type: - string - 'null' description: Test-mode API key for this workspace. Requests authenticated with this key do not consume credits and produce test-flagged, watermarked signing requests. created_date: type: string format: date-time description: Workspace creation timestamp updated_date: type: string format: date-time description: Workspace last update timestamp webhook_enabled: type: boolean description: Whether workspace-level webhooks are enabled webhook_secret: type: - string - 'null' description: Webhook signing secret for this workspace webhook_secret_rotated_at: type: - string - 'null' format: date-time description: Timestamp of the last webhook secret rotation webhook_secret_created_at: type: - string - 'null' format: date-time description: Timestamp when the webhook secret was first created ignore_company_webhooks: type: boolean description: When true, company-level webhooks do not fire for events in this workspace icon_url: type: - string - 'null' format: uri description: Publicly accessible URL to the workspace logo image. Returns the workspace-specific logo, or null if not set (company logo is NOT included as fallback in this field). example: https://ielmshcswdhuacyjlpiy.supabase.co/functions/v1/logo/workspace/company-id/workspace-id required: - id - name - created_date Error: type: object properties: error: type: string description: Human-readable error message code: type: string description: 'Machine-readable error code Seal-related codes: SEALS_DISABLED, SEAL_ALREADY_REVOKED, SEAL_CREATION_DISABLED_EDGE, SEAL_ERASE_NOT_ELIGIBLE, SEAL_IMAGE_INVALID, SEAL_MUTATION_NOT_ALLOWED, SEAL_NOT_FOUND, SEAL_ORDER_COLLISION, SEAL_PAUSED, SEAL_SCOPE_FORBIDDEN, SEAL_UNAVAILABLE' errors: type: array description: All validation errors when multiple failures are reported together. The top-level error repeats the first item for backward compatibility. items: type: object required: - message properties: message: type: string message: type: string description: Detailed error description details: type: object description: Additional error details additionalProperties: true required: - error description: ' Organization Seal error codes: SEALS_DISABLED, SEAL_ALREADY_REVOKED, SEAL_CREATION_DISABLED_EDGE, SEAL_ERASE_NOT_ELIGIBLE, SEAL_IMAGE_INVALID, SEAL_MUTATION_NOT_ALLOWED, SEAL_NOT_FOUND, SEAL_ORDER_COLLISION, SEAL_PAUSED, SEAL_SCOPE_FORBIDDEN, SEAL_UNAVAILABLE' ApiKeyRegenerateResponse: type: object properties: new_key: type: object description: The newly generated API key properties: api_key: type: string description: The new API key value created_at: type: string format: date-time description: When the key was created key_type: type: string enum: - live - test description: The key type that was regenerated expiring_keys: type: array items: type: object properties: id: type: string format: uuid api_key: type: string description: The expiring API key value expires_at: type: string format: date-time description: List of keys that were set to expire in 24 hours workspace_id: type: string format: uuid description: API key regeneration result required: - new_key - key_type - workspace_id LogoUploadResponse: type: object properties: success: type: boolean icon_url: type: string format: uri description: Public URL to the uploaded logo description: Logo upload confirmation with URL required: - success - icon_url WorkspaceListResponse: type: object description: Paginated list of workspaces properties: results: type: array items: $ref: '#/components/schemas/Workspace' pagination: $ref: '#/components/schemas/Pagination' required: - results - pagination securitySchemes: ApiKeyAuth: type: apiKey in: header name: Authorization description: API key for authentication. Use your API key directly without any prefix (e.g., 'your-api-key'). Bearer prefix is optional but not required.