*REST API › Guides*

# Data types

> Money is a decimal string, dates are ISO 8601, a property can be present and null, and custom fields are maps. Parse each type once, at the edge of your integration.

## Summary

| Kind | In the OpenAPI document | In JSON | Handle it as |
| --- | --- | --- | --- |
| Money | `string` with pattern `^-?\d+\.\d{2}$` | `"1234.50"` | A decimal. Never a float. |
| Instant | `string`, `format: date-time` | `"2026-10-17T08:00:00.000Z"` | A timestamp. Responses are UTC; writes take an offset. |
| Bare day | `string`, `format: date` | `"2026-10-17"` | A calendar day with no time zone. |
| Day or instant | `anyOf` of `format: date` and `format: date-time` | Either of the two above | Filters accept both. |
| Nullable | `type: ["string", "null"]` | `null` | Present, with no value. |
| Map | `object` with `additionalProperties` | `{ "cf_123": "Acme" }` | A dictionary keyed by identifier. |
| Identifier | `string` | `"5e8d1a9c"` | Opaque. Never parse it. |

## Money is a decimal string

Every monetary amount is serialized as a decimal string with two decimals, rounded half up: for example [`grossPrice`](https://docs.filmo.io/rest-api/objects/order#response-grossPrice) is `"1234.50"`. It is never a floating-point number, so no precision is lost. Do the arithmetic with a decimal type or with integer cents, and send amounts back as strings. The currency is that of the document, and the account's default is in [`currency`](https://docs.filmo.io/rest-api/objects/account#response-currency); see [Amounts and currencies](https://docs.filmo.io/further-reading/amounts-and-currencies).

## Dates and times

### Instants

Dates with a time are ISO 8601 strings. Responses use UTC (`Z`). A write takes an instant with an offset, such as `2026-10-17T10:00:00+02:00`, and stores the instant; responses return it in UTC. REST writes need an instant with an offset, not a bare day: for example `startDate`.

### Bare days

A few places accept a bare day (`YYYY-MM-DD`): the date filters of lists, and the `invoiceDate` and `dueDate` of [`PUT /api/v1/invoices/external/{originSystem}/{originExternalId}`](https://docs.filmo.io/rest-api/endpoint-reference/invoices/create-or-update-the-mirror-of-an-externally-issued-invoice). In filters it is read in the business timezone of the account, which you get from [`timezone`](https://docs.filmo.io/rest-api/objects/account#response-timezone). A bare day in a filter is read in that timezone, not in UTC. See [Filtering and sorting](https://docs.filmo.io/rest-api/guides/filtering-and-sorting) and [Dates and times](https://docs.filmo.io/further-reading/dates-and-times).

### Day or instant

Date filters are declared as a union of `format: date` and `format: date-time`. In OpenAPI 3.1 that is an `anyOf`, and a generated client usually types it as `string`. Send either form; the server decides by its shape.

## Nullable values

OpenAPI 3.1 writes a nullable property as a type list, `type: ["string", "null"]`. A nullable property that is listed as required is always present in the response and may be `null`: for example the `contact` of an order is `null` when the order has none. When you write, check the body schema of the operation before you send `null`: an optional property and a nullable one are different things.

## Maps

Custom field values come back as a map from custom field identifier to value, such as [`customFields`](https://docs.filmo.io/rest-api/objects/order#response-customFields). The values can be of any JSON type, and the definitions are in [`GET /api/v1/custom-fields`](https://docs.filmo.io/rest-api/endpoint-reference/custom-fields/list-custom-field-definitions). Iterate the keys you receive instead of assuming a fixed set.

*Parse each type at the edge*

**Node**

```javascript
const order = {
  grossPrice: "1234.50",
  startDate: "2026-10-17T08:00:00.000Z",
  customFields: { cf_123: "Acme", cf_456: 3 },
  contact: null,
};

// Money: keep it as a string, or count in cents. Never Number("1234.50") for arithmetic.
const toCents = (amount) => BigInt(amount.replace(".", ""));
const fromCents = (cents) => {
  const sign = cents < 0n ? "-" : "";
  const digits = (cents < 0n ? -cents : cents).toString().padStart(3, "0");
  return sign + digits.slice(0, -2) + "." + digits.slice(-2);
};
console.log(fromCents(toCents(order.grossPrice) + toCents("0.05"))); // 1234.55

// Instants: parse them and keep working in UTC; format for people only at the edge.
const start = new Date(order.startDate);
console.log(start.toISOString());

// Nullable: the property is there, the value may be null.
console.log(order.contact?.name ?? "no contact");

// Maps: iterate the keys you get, whatever they are.
for (const [fieldId, value] of Object.entries(order.customFields)) console.log(fieldId, value);
```

**Python**

```python
from datetime import datetime
from decimal import Decimal

order = {
    "grossPrice": "1234.50",
    "startDate": "2026-10-17T08:00:00.000Z",
    "customFields": {"cf_123": "Acme", "cf_456": 3},
    "contact": None,
}

# Money: Decimal from the string, never float.
total = Decimal(order["grossPrice"]) + Decimal("0.05")
print(f"{total:.2f}")  # 1234.55

# Instants: parse them and keep working in UTC; format for people only at the edge.
start = datetime.fromisoformat(order["startDate"].replace("Z", "+00:00"))
print(start.isoformat())

# Nullable: the key is there, the value may be None.
print((order["contact"] or {}).get("name", "no contact"))

# Maps: iterate the keys you get, whatever they are.
for field_id, value in order["customFields"].items():
    print(field_id, value)
```

## Tolerate the unknown

- Ignore properties you do not recognize. Filmo adds properties to responses within v1 without notice.
- Accept enum values you do not know. Most statuses are closed unions today, and a new value is a documented additive change. The `status` of some schemas is a plain string in the spec, not an enum: `SerializedItemDto`, `SerializedItemDetailDto`, `InternalServiceDto`, `PlannerServiceDto`, `PlannerAssignResultDto`, `PlannerUnassignResultDto`, `IncidentDto`, `SupplierInvoiceDto`, `CreditNoteDto` and `ProformaDto`. Tolerate any value there.
- Do not reject a response because a property you expected to be a string is `null`.

The rules for what may change are in [Versioning](https://docs.filmo.io/rest-api/guides/versioning).

---

Canonical URL: https://docs.filmo.io/rest-api/guides/data-types  
Last updated: 2026-10-09