Frihet Deposits API
Manage client deposits and prepayments. Apply deposits against invoices or process refunds.
Manage client deposits and prepayments. Apply deposits against invoices or process refunds.
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/frihet-io:frihet-io-deposits-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: Frihet Deposits API
version: '2026-03-18'
description: 'The Frihet REST API provides programmatic access to your business data
in Frihet, the AI-native business management platform for modern businesses. ## Authentication
All API requests require an API key passed via the `X-API-Key` header. Generate keys from **Settings > API Keys** in the Frihet app. ```
X-API-Key: fri_your_api_key_here
```
### API-key scopes
A key without scopes (no list, or an empty one) is unrestricted. A key with a
non-empty scope list gets only what the list grants. On such a key, every
request other than `GET` and `HEAD` needs the `write` scope (or `*`), except
the read-only `POST /v1/gestoria/aging` and the POS and purchase-order routes,
which check their own `pos:` and `purchase-orders:` scopes. E-invoicing endpoints need an `einvoice:` scope,
and the sends (`einvoice/export`, `face/submit`, `ticketbai/submit`) also need
`write` or `einvoice:write`. A request without the scope it needs gets
`403 Forbidden` with a `detail` naming that scope. The refusal happens before
the idempotency gate, so it does not reserve the `Idempotency-Key`. `GET /v1/permissions/me` reports `denied.write`, `denied.einvoice` and
`denied.einvoiceSend` for the calling key. Inventory (`/v1/inventory/*`) and purchase-order (`/v1/purchase-orders/*`) routes are
not released yet: after the scope checks they answer `503` with code
`INVENTORY_AUTHORITY_NOT_RELEASED`, before the idempotency gate. ## Rate Limiting
The API enforces a rate limit of **100 requests per minute** per API key. When exceeded, the API returns `429 Too Many Requests` with an integer
delta-seconds `Retry-After` header and matching `retryAfter` JSON field. Every authenticated response includes rate limit headers:
| Header | Description |
|--------|-------------|
| `X-RateLimit-Limit` | Maximum requests per window (100) |
| `X-RateLimit-Remaining` | Remaining requests in current window |
| `X-RateLimit-Reset` | Unix timestamp (seconds) when the window resets |
| `Retry-After` | Integer seconds until the window resets (429 responses) |
## Idempotency
Most POST requests accept an `Idempotency-Key` header to prevent duplicate
resource creation from network retries. For protected routes, the key is
reserved against the operation *before* the request is executed, and it stays
reserved for 24 hours unless released by an explicit pre-write refusal. ```
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
```
Keys must be at most 64 characters. We recommend UUID v4. A retry of the same operation gets one of three answers:
| Situation | Response |
|---|---|
| The first request finished | The **original status and body**, verbatim, with `X-Idempotent-Replayed: true` |
| The first request is still running, or its outcome could not be recorded | `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` |
| The key is live but was used for a **different** operation | `409 IDEMPOTENCY_KEY_REUSED` — the second operation is not executed |
The replayed status is the **stored** one. A `201` replays as `201`, not `200`. This includes stored error responses: a request that answered `4xx` or `5xx`
replays that same status and body, it does not re-execute — except explicit
pre-mutation refusals on `POST /v1/pos/cash-sessions/{id}/close` (`503` when
the workspace POS money-authority grant is not enabled and `403 POS_INACTIVE`
before the POS app is activated), which release the reservation instead of
caching the refusal for 24 hours. ### When a retry is refused
A `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` after the first request already
returned means the outcome could not be recorded — the response was too large
or unserializable, or the bookkeeping write failed. The API refuses rather than
execute a second time, because for a fiscal document a duplicate is not
recoverable. **Reconcile; do not simply retry with a fresh key.** Read the resource back
(`GET` the collection, or look for the document the operation would have
created) and decide from its actual state.'
contact:
name: Frihet API Support
url: https://docs.frihet.io/desarrolladores/api-rest
email: api@frihet.io
license:
name: MIT
url: https://opensource.org/licenses/MIT
x-logo:
url: https://frihet.io/logos/frihet-logo.svg
servers:
- url: https://api.frihet.io
description: Production
security:
- ApiKeyAuth: []
tags:
- name: Deposits
description: Manage client deposits and prepayments. Apply deposits against invoices or process refunds.
paths:
/v1/deposits:
get:
operationId: listDeposits
summary: List deposits
description: Retrieve a paginated list of deposits with optional date range, client, and search filters.
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Cursor'
- $ref: '#/components/parameters/From'
- $ref: '#/components/parameters/To'
- $ref: '#/components/parameters/Search'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/FilterClientId'
responses:
'200':
description: A paginated list of deposits
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/PaginatedResponse'
- type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Deposit'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
post:
operationId: createDeposit
summary: Create a deposit
description: Record a new client deposit or prepayment.
tags:
- Deposits
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DepositCreate'
responses:
'201':
description: Deposit created
content:
application/json:
schema:
$ref: '#/components/schemas/Deposit'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/WriteScopeForbidden'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
/v1/deposits/{depositId}:
get:
operationId: getDeposit
summary: Get a deposit
description: Retrieve a single deposit by ID.
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/DepositId'
- $ref: '#/components/parameters/Fields'
responses:
'200':
description: Deposit details
content:
application/json:
schema:
$ref: '#/components/schemas/Deposit'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
put:
operationId: updateDeposit
summary: Full update a deposit
description: Replace all fields on an existing deposit.
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/DepositId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DepositCreate'
responses:
'200':
description: Deposit updated
content:
application/json:
schema:
$ref: '#/components/schemas/Deposit'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/WriteScopeForbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
patch:
operationId: patchDeposit
summary: Partial update a deposit
description: Update only the provided fields on an existing deposit.
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/DepositId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DepositCreate'
responses:
'200':
description: Deposit updated
content:
application/json:
schema:
$ref: '#/components/schemas/Deposit'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/WriteScopeForbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
delete:
operationId: deleteDeposit
summary: Delete a deposit
description: Permanently delete a deposit record.
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/DepositId'
responses:
'204':
description: Deposit deleted
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/WriteScopeForbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
/v1/deposits/{depositId}/apply:
post:
operationId: applyDeposit
summary: Apply deposit to an invoice
description: 'Apply all or part of a deposit''s remaining balance to a specific invoice.
The deposit balance and the invoice''s deposit settlement are written in one
transaction, so an application and a refund racing on the same balance can
never both succeed. Returns an error if the requested amount exceeds the
remaining balance (refunds already reduce it), if the deposit has already
been applied to the given invoice, or if the deposit or invoice does not exist.'
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/DepositId'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- invoiceId
- invoiceNumber
- amount
properties:
invoiceId:
type: string
description: ID of the invoice to apply the deposit to
example: abc123def456
invoiceNumber:
type: string
description: Invoice number (for audit trail)
example: FAC-2026-0042
amount:
type: number
description: Amount to apply (must be positive and not exceed remaining balance)
example: 500
responses:
'200':
description: Deposit applied successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
depositId:
type: string
example: dep_abc123
appliedAmount:
type: number
example: 500
remainingBalance:
type: number
example: 250
status:
type: string
enum:
- active
- partially_applied
- fully_applied
example: partially_applied
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/WriteScopeForbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: 'INVOICE_PAYMENT_CAPTURE_FENCED: a portal payment capture is in progress
on the invoice; retry after it settles. Nothing is written.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
/v1/deposits/{depositId}/refund:
post:
operationId: refundDeposit
summary: Refund a deposit
description: 'Refund all or part of a deposit''s remaining balance to the client.
If `amount` is omitted, the full remaining balance is refunded.
The refund is decided on the balance read in the same transaction that
writes it, so it can never also spend a balance a concurrent application
consumed. Returns an error if the deposit is already fully refunded, has no
remaining balance, or if the requested amount exceeds the remaining balance.'
tags:
- Deposits
parameters:
- $ref: '#/components/parameters/DepositId'
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
type: number
description: Amount to refund. Omit to refund the full remaining balance.
example: 250
responses:
'200':
description: Deposit refunded successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
depositId:
type: string
example: dep_abc123
refundedAmount:
type: number
example: 250
remainingBalance:
type: number
example: 0
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/WriteScopeForbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
components:
parameters:
Search:
name: q
in: query
description: 'Full-text search query. Searches across relevant fields per resource type
(e.g., clientName, documentNumber for invoices; name, email for clients).
Case-insensitive.
'
schema:
type: string
example: acme
Offset:
name: offset
in: query
description: Number of items to skip for pagination (max 10000)
schema:
type: integer
minimum: 0
maximum: 10000
default: 0
FilterClientId:
name: clientId
in: query
description: Filter invoices or quotes by client ID
schema:
type: string
To:
name: to
in: query
description: End date filter in ISO format (YYYY-MM-DD)
schema:
type: string
format: date
example: '2026-12-31'
From:
name: from
in: query
description: Start date filter in ISO format (YYYY-MM-DD)
schema:
type: string
format: date
example: '2026-01-01'
Limit:
name: limit
in: query
description: Number of items to return (default 50, max 100)
schema:
type: integer
minimum: 1
maximum: 100
default: 50
DepositId:
name: depositId
in: path
required: true
description: Deposit unique identifier
schema:
type: string
Cursor:
name: cursor
in: query
description: 'Cursor for cursor-based pagination. Use the `nextCursor` value from
a previous response to fetch the next page. When provided, `offset`
is ignored. The cursor is a base64url-encoded JSON string.
'
schema:
type: string
example: eyJpc3N1ZURhdGUiOiIyMDI2LTAzLTIwIiwiX19pZCI6ImFiYzEyMyJ9
Fields:
name: fields
in: query
description: 'Comma-separated list of fields to include in the response.
The `id` field is always included. Example: `fields=id,name,total`
'
schema:
type: string
example: id,name,total
schemas:
DepositCreate:
type: object
required:
- clientId
- clientName
- amount
- description
- receivedDate
properties:
clientId:
type: string
description: ID of the client who made the deposit
example: client_abc123
clientName:
type: string
description: Client name (denormalized for display)
example: Acme Corp
amount:
type: number
description: Deposit amount (must be positive)
example: 750
currency:
type: string
description: Currency code (ISO 4217)
default: EUR
example: EUR
description:
type: string
description: Description of the deposit
example: Prepayment for Q2 project
receivedDate:
type: string
format: date
description: Date the deposit was received (YYYY-MM-DD)
example: '2026-04-01'
paymentMethod:
type: string
description: Payment method used
example: bank_transfer
paymentReference:
type: string
description: Payment reference or transaction ID
example: TRF-2026-0042
Error:
type: object
required:
- error
properties:
error:
type: string
description: Human-readable error message
code:
type: string
description: Optional machine-readable code, including MISSING_FISCAL_INFO, AEAT_QR_UNAVAILABLE (422) and AEAT_RECORD_IRREPRODUCIBLE (409) on invoice PDF.
message:
type: string
description: Optional explanatory message accompanying the error code.
details:
type: array
description: Validation error details (Zod issues). Only present on 400 validation errors.
items:
type: object
properties:
code:
type: string
message:
type: string
path:
type: array
items:
type: string
meta:
type: object
properties:
requestId:
type: string
description: Unique request identifier for tracing
PaginatedResponse:
type: object
properties:
data:
type: array
items: {}
total:
type: integer
description: Total number of items matching the query
limit:
type: integer
description: Number of items per page
offset:
type: integer
description: Current offset
nextCursor:
type: string
description: 'Cursor for fetching the next page of results. Only present when there
are more results available. Pass this value as the `cursor` query parameter
in the next request. Base64url-encoded.
'
example: eyJpc3N1ZURhdGUiOiIyMDI2LTAzLTIwIiwiX19pZCI6ImFiYzEyMyJ9
truncated:
type: boolean
description: 'Only present (and `true`) when the underlying search query
saturated the internal 500-document cap. `total` therefore reports
the size of the first 500 matches, not the full match count.
Narrow the search query (taxId / fiscal id, exact email) to ensure
you see every result.
'
example: true
DepositStatus:
type: string
enum:
- active
- partially_applied
- fully_applied
- refunded
Deposit:
allOf:
- type: object
properties:
id:
type: string
description: Unique identifier
example: dep_abc123
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
- $ref: '#/components/schemas/DepositCreate'
- type: object
properties:
status:
$ref: '#/components/schemas/DepositStatus'
appliedAmount:
type: number
description: Total amount already applied to invoices
example: 250
remainingBalance:
type: number
description: Remaining balance available for application or refund
example: 500
refundedAmount:
type: number
description: Total amount refunded to the client
example: 0
applications:
type: array
description: List of invoice applications for this deposit
items:
type: object
properties:
invoiceId:
type: string
invoiceNumber:
type: string
amount:
type: number
appliedAt:
type: string
format: date
responses:
InternalError:
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Internal server error
meta:
requestId: 550e8400-e29b-41d4-a716-446655440000
BadRequest:
description: Bad request - invalid parameters or request body
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Validation error
details:
- code: invalid_type
message: Required
path:
- clientName
meta:
requestId: 550e8400-e29b-41d4-a716-446655440000
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Resource not found
meta:
requestId: 550e8400-e29b-41d4-a716-446655440000
Unauthorized:
description: Authentication required or invalid API key
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Invalid or expired API key
meta:
requestId: 550e8400-e29b-41d4-a716-446655440000
RateLimited:
description: Rate limit exceeded (100 requests per minute)
headers:
Retry-After:
description: Integer seconds until the current rate-limit window resets
schema:
type: integer
minimum: 1
example: 60
X-RateLimit-Reset:
$ref: '#/components/headers/X-RateLimit-Reset'
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Rate limit exceeded
message:
type: string
example: Maximum 100 requests per minute
retryAfter:
type: integer
description: Seconds until rate limit resets
example: 60
meta:
type: object
properties:
requestId:
type: string
WriteScopeForbidden:
description: Write scope required to mutate, send or submit. The API key must include the `write` scope (or `einvoice:write` for e-invoice sends) or be unrestricted.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Forbidden
detail: This API key does not have the write scope required to create, update, delete, send or submit. E-invoice sends also accept einvoice:write. Use an API key with write access.
meta:
requestId: 550e8400-e29b-41d4-a716-446655440000
headers:
X-RateLimit-Reset:
description: Unix timestamp (seconds) when the rate limit window resets
schema:
type: integer
example: 1742310060
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: 'API key for authentication. Keys start with `fri_` prefix.
Generate keys from Settings > API Keys in the Frihet app.
'