Programmatic EngineAPI All connections

OTS API

Beta · Version 1.0

Beta. Available to try; fields and behaviour may still change.

Base URL

https://ots.programmatic-engine.ru/v1

Authentication

Bearer token in the Authorization header

API key issued by us. Send it as "Authorization: Bearer ots_<key>".

Spec file
ots-api.json

Import into Postman: File → Import → paste the URL.

The OTS API returns the number of people who have an opportunity to see one output of a creative on a screen, for any hour of the year, and lets you run the same calculation on your own inputs.

What OTS per output means

OTS (opportunity to see) counts people who could see the screen while it shows the creative. This API reports it per output, one showing of the creative:

ots_per_output = daily_ots x hourly_share / 12
  • daily_ots is the estimated number of people who pass the screen in a day. Each screen has a value for the day regime (10 second creative) and, where the schedule separates them, a value for the night regime (60 second creative). Values are stored per calendar month.
  • hourly_share is the fraction of the daily traffic that falls into the local hour. The 24 shares of a month sum to 1.
  • 12 is the fixed number of outputs per hour and is returned as outputs_per_hour.

Results are rounded to 1 decimal. No random spread is applied: the same request returns the same numbers every time.

Access

Keys are issued by us on request. Issue one key per integrating system so that it can be revoked on its own. A key looks like ots_ followed by 40 letters and digits. Send it on every request:

Authorization: Bearer ots_<40 characters>

Call GET /me first. It confirms the key, shows the account and the rate limit, and returns the server time. An account sees only the screens granted to it. Any other screen id returns 404 not_found, the same as a screen that does not exist.

Time

  • Instants in responses are UTC and end with Z, for example 2026-10-09T14:03:11Z.
  • Instants in requests (the at parameter) must carry Z or an offset such as +03:00. A timestamp without a zone is rejected with 400 invalid_parameter. In a query string write + as %2B.
  • Screen-local times in responses carry an explicit offset, for example 2026-10-09T17:00:00+03:00.
  • A date parameter is a screen-local calendar date, YYYY-MM-DD.
  • at and date are accepted for the years 2000 to 2099. The data is stored per calendar month, so the year does not change the result.
  • Every screen has a fixed offset from UTC in whole hours (utc_offset_hours). Daylight saving time is not applied.

Limits

Each key has a per-minute request limit, 60 by default, counted over a sliding 60 second window. Every response to an authenticated request carries:

  • X-RateLimit-Limit: the limit of the key.
  • X-RateLimit-Remaining: requests left in the current window.

A request over the limit gets 429 rate_limited and a Retry-After header with the number of seconds to wait. Rejected requests do not count against the limit.

A request body larger than 64 KiB is refused with 413 invalid_parameter.

Errors

Every error uses the same envelope:

{"error": {"code": "not_found", "message": "Screen 99999 was not found.", "param": null}}

Branch on code. The message is for people and can change. param names the offending parameter or body field, or is null.

HTTP code Meaning
400 invalid_parameter A parameter or body field is missing, malformed or out of range.
401 unauthorized The key is missing, unknown or revoked. The response has WWW-Authenticate: Bearer.
404 not_found The screen does not exist or is not granted to your account.
413 invalid_parameter The request body is larger than 64 KiB.
422 no_data The screen exists but the data for the requested month or date is incomplete.
429 rate_limited The per-minute limit is exceeded. Wait for Retry-After seconds.
500 internal_error Unexpected failure on our side. Retry later and quote the time of the request.
503 service_unavailable The service cannot reach its data right now. Retry later.

Versioning

The version is part of the path: this document describes /v1. Within v1 changes are additive only: new endpoints, new optional parameters and new response fields. Existing fields keep their name, type and meaning. Read response fields by name and ignore fields you do not know. A breaking change would be published under /v2 with notice.

Account

Check the key and see what the account can access.

GET/me

Show the account and the key#

Returns the account that owns the key, the key itself (prefix and label, never the secret), its rate limit, the number of granted screens and the current server time. Call it first to check that a new key works.

Responses

200

The key is valid.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
tenantrequired object

The account the key belongs to.

tenant.idrequired integer

Numeric id of the account that owns the key.

3
tenant.namerequired string

Account name.

Acme Media
keyrequired object

The key used for this request.

key.prefixrequired string

First 8 characters after "ots_" in the key. Identifies the key in support requests without revealing it.

7Hk2xQ9p
key.labelrequired string

Label given to the key when it was issued, usually the integrating system.

BI export
key.created_atrequired string

When the key was issued, UTC, ISO 8601 with Z.

2026-10-09T10:00:00Z
rate_limit_per_minuterequired integer

Requests per minute allowed for this key.

60
screensrequired integer

Number of screens granted to the account.

12
server_timerequired string

Current server time, UTC, ISO 8601 with Z. Use it to detect clock drift before sending the "at" parameter.

2026-10-09T14:03:11Z
{
    "tenant": {
        "id": 3,
        "name": "Acme Media"
    },
    "key": {
        "prefix": "7Hk2xQ9p",
        "label": "BI export",
        "created_at": "2026-10-09T10:00:00Z"
    },
    "rate_limit_per_minute": 60,
    "screens": 12,
    "server_time": "2026-10-09T14:03:11Z"
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl 'https://ots.programmatic-engine.ru/v1/me' \
  -H "Authorization: Bearer $API_KEY"

Screens

Screens granted to the account and the inputs of their model.

GET/screens

List granted screens#

Returns every screen granted to the account, ordered by id. months_with_data lists the months for which OTS can be calculated; request other months and you get 422 no_data. The list is not paginated.

Responses

200

Granted screens.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
itemsrequired object[]

Granted screens ordered by id.

[{"active":true,"city":"Москва","id":10403,"months_with_data":[1,2,3,4,5,6,7,8,9,10,11,12],"name":"Kutuzovsky Prospekt 48","schedule":{"name":"Москва","night_mode":"separate"},"utc_offset_hours":3}]
items[].idrequired integer

Screen id. The same id is used by the unauthenticated OTS lookup used by ad platforms.

10403
items[].namerequired string

Screen name.

Kutuzovsky Prospekt 48
items[].cityrequired string

City where the screen stands.

Москва
items[].utc_offset_hoursrequired integer

Fixed offset of screen-local time from UTC, in whole hours. Daylight saving time is not applied.

3
items[].activerequired boolean

False when the screen is switched off in the registry. Data stays readable.

true
items[].schedulerequired any of

Broadcast schedule of the screen, or null when none is assigned. Without a schedule no OTS can be calculated for the screen.

items[].schedule (option 1)optional object
items[].schedule.namerequired string

Broadcast schedule name.

Москва
items[].schedule.night_moderequired string one of: separate, same

"separate": the night has its own daily OTS value (a longer creative at night). "same": the day value applies around the clock.

separate
items[].schedule (option 2)optional null
items[].months_with_datarequired integer[]

Months (1 to 12) for which OTS can be calculated: the day value exists, the night value exists when night_mode is "separate", and all 24 hourly shares exist. The list is empty when the screen has no usable schedule.

[1,2,3,4,5,6,7,8,9,10,11,12]
{
    "items": [
        {
            "id": 10403,
            "name": "Kutuzovsky Prospekt 48",
            "city": "Москва",
            "utc_offset_hours": 3,
            "active": true,
            "schedule": {
                "name": "Москва",
                "night_mode": "separate"
            },
            "months_with_data": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
        }
    ]
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl 'https://ots.programmatic-engine.ru/v1/screens' \
  -H "Authorization: Bearer $API_KEY"
GET/screens/{screen_id}

Get one screen#

Returns one granted screen. A screen that is not granted to the account returns 404 not_found, the same as a screen that does not exist.

Parameters

NameInTypeRequiredDescriptionExample
screen_id path integermin 1 · max 2147483647 yes

Screen id from GET /screens.

10403

Responses

200

The screen.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
idrequired integer

Screen id. The same id is used by the unauthenticated OTS lookup used by ad platforms.

10403
namerequired string

Screen name.

Kutuzovsky Prospekt 48
cityrequired string

City where the screen stands.

Москва
utc_offset_hoursrequired integer

Fixed offset of screen-local time from UTC, in whole hours. Daylight saving time is not applied.

3
activerequired boolean

False when the screen is switched off in the registry. Data stays readable.

true
schedulerequired any of

Broadcast schedule of the screen, or null when none is assigned. Without a schedule no OTS can be calculated for the screen.

schedule (option 1)optional object
schedule.namerequired string

Broadcast schedule name.

Москва
schedule.night_moderequired string one of: separate, same

"separate": the night has its own daily OTS value (a longer creative at night). "same": the day value applies around the clock.

separate
schedule (option 2)optional null
months_with_datarequired integer[]

Months (1 to 12) for which OTS can be calculated: the day value exists, the night value exists when night_mode is "separate", and all 24 hourly shares exist. The list is empty when the screen has no usable schedule.

[1,2,3,4,5,6,7,8,9,10,11,12]
{
    "id": 10403,
    "name": "Kutuzovsky Prospekt 48",
    "city": "Москва",
    "utc_offset_hours": 3,
    "active": true,
    "schedule": {
        "name": "Москва",
        "night_mode": "separate"
    },
    "months_with_data": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
}
400

A parameter or body field is missing, malformed or out of range.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "invalid_parameter",
        "message": "Parameter 'screen_id' is invalid. Input should be a valid integer.",
        "param": "screen_id"
    }
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
404

The screen does not exist or is not granted to the account.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "not_found",
        "message": "Screen 99999 was not found.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl 'https://ots.programmatic-engine.ru/v1/screens/10403' \
  -H "Authorization: Bearer $API_KEY"
GET/screens/{screen_id}/inputs

Model inputs of a screen for one month#

Returns the values behind the calculation for one month: the daily OTS values, the 24 hourly shares and the broadcast schedule with all its day windows. Use it to run your own calculation or to check ours with POST /calculate. daily_ots.night is null when the schedule has night_mode "same". Returns 422 no_data when the month is not in months_with_data.

Parameters

NameInTypeRequiredDescriptionExample
screen_id path integermin 1 · max 2147483647 yes

Screen id from GET /screens.

10403
month query integermin 1 · max 12 no

Calendar month, 1 to 12. Defaults to the current month in screen-local time.

10

Responses

200

Inputs for the month.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
screen_idrequired integer

Screen id.

10403
monthrequired integer

Calendar month, 1 to 12.

10
daily_otsrequired object

Daily OTS values for the month.

daily_ots.dayrequired integer

Daily OTS of the day creative (10 seconds).

60500
daily_ots.nightrequired any of

Daily OTS of the night creative (60 seconds). Null when night_mode is "same".

daily_ots.night (option 1)optional integer
daily_ots.night (option 2)optional null
hourly_sharerequired number[]

24 fractions, index 0 is the hour starting at 00:00 screen-local time. They sum to 1 within 0.005.

[0.036,0.024,0.019,0.013,0.013,0.018,0.026,0.029,0.036,0.042,0.051,0.057,0.062,0.064,0.062,0.061,0.057,0.053,0.051,0.05,0.048,0.046,0.043,0.039]
schedulerequired any of

Schedule with all its day windows, or null when none is assigned.

schedule (option 1)optional object
schedule.namerequired string

Broadcast schedule name.

Москва
schedule.night_moderequired string one of: separate, same

"separate": the night has its own daily OTS value (a longer creative at night). "same": the day value applies around the clock.

separate
schedule.periodsrequired object[]

All day windows of the schedule, ordered by effective_from.

[{"day_end":19,"day_start":6,"effective_from":"2026-01-01"}]
schedule.periods[].effective_fromrequired string

First screen-local date on which this window applies, YYYY-MM-DD. The period in force is the latest one that has started.

2026-01-01
schedule.periods[].day_startrequired integer

First hour of the day regime, 0 to 23.

6
schedule.periods[].day_endrequired integer

Last hour of the day regime, 0 to 23, inclusive. The window does not wrap past midnight.

19
schedule (option 2)optional null
{
    "screen_id": 10403,
    "month": 10,
    "daily_ots": {
        "day": 60500,
        "night": 151100
    },
    "hourly_share": [
        0.036, 0.024, 0.019, 0.013, 0.013, 0.018, 0.026, 0.029, 0.036,
        0.042, 0.051, 0.057, 0.062, 0.064, 0.062, 0.061, 0.057, 0.053,
        0.051, 0.05, 0.048, 0.046, 0.043, 0.039
    ],
    "schedule": {
        "name": "Москва",
        "night_mode": "separate",
        "periods": [
            {
                "effective_from": "2026-01-01",
                "day_start": 6,
                "day_end": 19
            }
        ]
    }
}
400

A parameter or body field is missing, malformed or out of range.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "invalid_parameter",
        "message": "Parameter 'month' is invalid. Input should be less than or equal to 12.",
        "param": "month"
    }
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
404

The screen does not exist or is not granted to the account.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "not_found",
        "message": "Screen 99999 was not found.",
        "param": null
    }
}
422

The screen exists but its data for the requested month or date is incomplete.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "no_data",
        "message": "Screen 10403 has no complete OTS data for month 11.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl 'https://ots.programmatic-engine.ru/v1/screens/10403/inputs' \
  -H "Authorization: Bearer $API_KEY"

OTS

Calculated OTS for granted screens.

GET/screens/{screen_id}/ots

OTS per output at one instant#

Calculates OTS for one output of the creative at the instant at. The instant is converted to screen-local time with the fixed offset of the screen. The local hour selects the hourly share and, through the broadcast schedule, the day or night value. The value is daily_ots x hourly_share / 12, rounded to 1 decimal, with no random spread. Returns 422 no_data when the month has no complete data or no schedule period is in force on that local date.

Parameters

NameInTypeRequiredDescriptionExample
screen_id path integermin 1 · max 2147483647 yes

Screen id from GET /screens.

10403
at query string no

Instant to calculate for, ISO 8601 with Z or a UTC offset. A naive timestamp is rejected. Defaults to the current time. Percent-encode "+" as %2B. Accepted from 2000-01-01T00:00:00Z to 2099-12-31T23:59:59Z.

2026-10-09T14:00:00Z

Responses

200

OTS for the hour that contains the instant.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
screen_idrequired integer

Screen id.

10403
atrequired string

The requested instant, UTC, ISO 8601 with Z.

2026-10-09T14:00:00Z
local_timerequired string

The same instant in screen-local time, ISO 8601 with an explicit offset.

2026-10-09T17:00:00+03:00
daypartrequired string one of: day, night

Which regime applies at that local hour according to the schedule.

day
daily_otsrequired integer

Daily OTS value used: the day value in the day regime, the night value in the night regime when night_mode is "separate", otherwise the day value.

60500
hourly_sharerequired number

Share of daily traffic that falls into this local hour, as a fraction.

0.062
outputs_per_hourrequired integer

Fixed divisor of the formula: 12 outputs per hour.

12
otsrequired number

OTS for one output of the creative, rounded to 1 decimal. daily_ots x hourly_share / outputs_per_hour. No random spread is applied.

312.6
{
    "screen_id": 10403,
    "at": "2026-10-09T14:00:00Z",
    "local_time": "2026-10-09T17:00:00+03:00",
    "daypart": "day",
    "daily_ots": 60500,
    "hourly_share": 0.053,
    "outputs_per_hour": 12,
    "ots": 267.2
}
400

A parameter or body field is missing, malformed or out of range.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "invalid_parameter",
        "message": "Parameter 'at' must be an ISO 8601 timestamp with Z or a UTC offset, for example 2026-10-09T14:00:00Z. A '+' in an offset must be percent-encoded as %2B.",
        "param": "at"
    }
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
404

The screen does not exist or is not granted to the account.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "not_found",
        "message": "Screen 99999 was not found.",
        "param": null
    }
}
422

The screen exists but its data for the requested month or date is incomplete.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "no_data",
        "message": "Screen 10403 has no complete OTS data for month 11.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl 'https://ots.programmatic-engine.ru/v1/screens/10403/ots' \
  -H "Authorization: Bearer $API_KEY"
GET/screens/{screen_id}/ots/hourly

OTS for each hour of a local day#

Returns 24 rows for one screen-local calendar date. ots_per_output is the value for one output, ots_hour is that value times 12 outputs, and total_ots is the sum of ots_hour over the day. The day and night windows come from the schedule period in force on that date. Returns 422 no_data when the month has no complete data or no schedule period is in force on that date.

Parameters

NameInTypeRequiredDescriptionExample
screen_id path integermin 1 · max 2147483647 yes

Screen id from GET /screens.

10403
date query string no

Screen-local calendar date, YYYY-MM-DD, from 2000-01-01 to 2099-12-31. Defaults to today in screen-local time.

2026-10-09

Responses

200

Hourly OTS for the date.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
screen_idrequired integer

Screen id.

10403
daterequired string

Screen-local calendar date, YYYY-MM-DD.

2026-10-09
utc_offset_hoursrequired integer

Screen offset from UTC used to build the "start" values.

3
outputs_per_hourrequired integer

Fixed divisor of the formula: 12.

12
hoursrequired object[]

Exactly 24 rows, hour 0 to 23.

[{"daily_ots":60500,"daypart":"day","hour":12,"hourly_share":0.062,"ots_hour":3751.2,"ots_per_output":312.6,"start":"2026-10-09T12:00:00+03:00"}]
hours[].hourrequired integer

Screen-local hour of day, 0 to 23.

12
hours[].startrequired string

Start of the hour in screen-local time, ISO 8601 with offset.

2026-10-09T12:00:00+03:00
hours[].daypartrequired string one of: day, night

Regime that applies in this hour.

day
hours[].daily_otsrequired integer

Daily OTS value used for this hour.

60500
hours[].hourly_sharerequired number

Fraction of daily traffic in this hour.

0.062
hours[].ots_per_outputrequired number

OTS for one output in this hour, rounded to 1 decimal.

312.6
hours[].ots_hourrequired number

OTS for all 12 outputs of the hour: ots_per_output x 12, rounded to 1 decimal.

3751.2
total_otsrequired number

Sum of ots_hour over the 24 rows, rounded to 1 decimal.

87589.2
{
    "screen_id": 10403,
    "date": "2026-10-09",
    "utc_offset_hours": 3,
    "outputs_per_hour": 12,
    "hours": [
        {
            "hour": 0,
            "start": "2026-10-09T00:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.036,
            "ots_per_output": 453.3,
            "ots_hour": 5439.6
        },
        {
            "hour": 1,
            "start": "2026-10-09T01:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.024,
            "ots_per_output": 302.2,
            "ots_hour": 3626.4
        },
        {
            "hour": 2,
            "start": "2026-10-09T02:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.019,
            "ots_per_output": 239.2,
            "ots_hour": 2870.4
        },
        {
            "hour": 3,
            "start": "2026-10-09T03:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.013,
            "ots_per_output": 163.7,
            "ots_hour": 1964.4
        },
        {
            "hour": 4,
            "start": "2026-10-09T04:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.013,
            "ots_per_output": 163.7,
            "ots_hour": 1964.4
        },
        {
            "hour": 5,
            "start": "2026-10-09T05:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.018,
            "ots_per_output": 226.6,
            "ots_hour": 2719.2
        },
        {
            "hour": 6,
            "start": "2026-10-09T06:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.026,
            "ots_per_output": 131.1,
            "ots_hour": 1573.2
        },
        {
            "hour": 7,
            "start": "2026-10-09T07:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.029,
            "ots_per_output": 146.2,
            "ots_hour": 1754.4
        },
        {
            "hour": 8,
            "start": "2026-10-09T08:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.036,
            "ots_per_output": 181.5,
            "ots_hour": 2178
        },
        {
            "hour": 9,
            "start": "2026-10-09T09:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.042,
            "ots_per_output": 211.8,
            "ots_hour": 2541.6
        },
        {
            "hour": 10,
            "start": "2026-10-09T10:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.051,
            "ots_per_output": 257.1,
            "ots_hour": 3085.2
        },
        {
            "hour": 11,
            "start": "2026-10-09T11:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.057,
            "ots_per_output": 287.4,
            "ots_hour": 3448.8
        },
        {
            "hour": 12,
            "start": "2026-10-09T12:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.062,
            "ots_per_output": 312.6,
            "ots_hour": 3751.2
        },
        {
            "hour": 13,
            "start": "2026-10-09T13:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.064,
            "ots_per_output": 322.7,
            "ots_hour": 3872.4
        },
        {
            "hour": 14,
            "start": "2026-10-09T14:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.062,
            "ots_per_output": 312.6,
            "ots_hour": 3751.2
        },
        {
            "hour": 15,
            "start": "2026-10-09T15:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.061,
            "ots_per_output": 307.5,
            "ots_hour": 3690
        },
        {
            "hour": 16,
            "start": "2026-10-09T16:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.057,
            "ots_per_output": 287.4,
            "ots_hour": 3448.8
        },
        {
            "hour": 17,
            "start": "2026-10-09T17:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.053,
            "ots_per_output": 267.2,
            "ots_hour": 3206.4
        },
        {
            "hour": 18,
            "start": "2026-10-09T18:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.051,
            "ots_per_output": 257.1,
            "ots_hour": 3085.2
        },
        {
            "hour": 19,
            "start": "2026-10-09T19:00:00+03:00",
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.05,
            "ots_per_output": 252.1,
            "ots_hour": 3025.2
        },
        {
            "hour": 20,
            "start": "2026-10-09T20:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.048,
            "ots_per_output": 604.4,
            "ots_hour": 7252.8
        },
        {
            "hour": 21,
            "start": "2026-10-09T21:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.046,
            "ots_per_output": 579.2,
            "ots_hour": 6950.4
        },
        {
            "hour": 22,
            "start": "2026-10-09T22:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.043,
            "ots_per_output": 541.4,
            "ots_hour": 6496.8
        },
        {
            "hour": 23,
            "start": "2026-10-09T23:00:00+03:00",
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.039,
            "ots_per_output": 491.1,
            "ots_hour": 5893.2
        }
    ],
    "total_ots": 87589.2
}
400

A parameter or body field is missing, malformed or out of range.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "invalid_parameter",
        "message": "Parameter 'date' must be a calendar date in the form YYYY-MM-DD.",
        "param": "date"
    }
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
404

The screen does not exist or is not granted to the account.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "not_found",
        "message": "Screen 99999 was not found.",
        "param": null
    }
}
422

The screen exists but its data for the requested month or date is incomplete.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "no_data",
        "message": "Screen 10403 has no complete OTS data for month 11.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl 'https://ots.programmatic-engine.ru/v1/screens/10403/ots/hourly' \
  -H "Authorization: Bearer $API_KEY"

Generator

Run the calculation on your own inputs.

POST/calculate

Calculate OTS from your own inputs#

Runs the same calculation as the screen endpoints on inputs you send. It reads no data of any account and stores nothing. Send the daily OTS values, 24 hourly shares as fractions, the day window and the night mode. With night_mode "separate", hours inside day_hours (both ends included) use daily_ots.day and the other hours use daily_ots.night. With "same", daily_ots.day is used for all 24 hours. The day window does not wrap past midnight. The response has one row per hour of the day, hour 0 to 23, and total_ots for the whole day.

Request body · application/json

FieldTypeDescriptionExample
daily_otsrequired object

Daily OTS values of the screen.

daily_ots.dayrequired integermax 1000000000 · above 0

Daily OTS of the day creative.

60500
daily_ots.nightoptional any of

Daily OTS of the night creative. Required when night_mode is "separate". Ignored when night_mode is "same".

daily_ots.night (option 1)optional integermax 1000000000 · above 0
daily_ots.night (option 2)optional null
hourly_sharerequired number[]minItems 24 · maxItems 24

Exactly 24 fractions of daily traffic, index 0 is hour 00. Use fractions, not percent: 0.062, not 6.2. Each value is between 0 and 1 and the sum is 1 within 0.005.

[0.036,0.024,0.019,0.013,0.013,0.018,0.026,0.029,0.036,0.042,0.051,0.057,0.062,0.064,0.062,0.061,0.057,0.053,0.051,0.05,0.048,0.046,0.043,0.039]
day_hoursoptional any of

Hours that belong to the day regime. Required when night_mode is "separate". When night_mode is "same" it only labels the dayparts and defaults to the whole day.

day_hours (option 1)optional object
day_hours.startrequired integermin 0 · max 23

First hour of the day regime.

6
day_hours.endrequired integermin 0 · max 23

Last hour of the day regime, inclusive. Must not be less than start. A window that wraps past midnight is not supported.

19
day_hours (option 2)optional null
night_moderequired string one of: separate, same

"separate": hours outside day_hours use daily_ots.night. "same": daily_ots.day is used for all 24 hours.

separate
{
    "daily_ots": {
        "day": 60500,
        "night": 151100
    },
    "hourly_share": [
        0.036, 0.024, 0.019, 0.013, 0.013, 0.018, 0.026, 0.029, 0.036,
        0.042, 0.051, 0.057, 0.062, 0.064, 0.062, 0.061, 0.057, 0.053,
        0.051, 0.05, 0.048, 0.046, 0.043, 0.039
    ],
    "day_hours": {
        "start": 6,
        "end": 19
    },
    "night_mode": "separate"
}

Responses

200

Hourly OTS for the inputs.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
outputs_per_hourrequired integer

Fixed divisor of the formula: 12.

12
hoursrequired object[]

Exactly 24 rows, hour 0 to 23.

[{"daily_ots":60500,"daypart":"day","hour":12,"hourly_share":0.062,"ots_hour":3751.2,"ots_per_output":312.6}]
hours[].hourrequired integer

Hour of day, 0 to 23.

12
hours[].daypartrequired string one of: day, night

Regime that applies in this hour.

day
hours[].daily_otsrequired integer

Daily OTS value used for this hour.

60500
hours[].hourly_sharerequired number

Fraction of daily traffic in this hour.

0.062
hours[].ots_per_outputrequired number

OTS for one output in this hour, rounded to 1 decimal.

312.6
hours[].ots_hourrequired number

OTS for all 12 outputs of the hour: ots_per_output x 12, rounded to 1 decimal.

3751.2
total_otsrequired number

Sum of ots_hour over the 24 rows, rounded to 1 decimal.

87589.2
{
    "outputs_per_hour": 12,
    "hours": [
        {
            "hour": 0,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.036,
            "ots_per_output": 453.3,
            "ots_hour": 5439.6
        },
        {
            "hour": 1,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.024,
            "ots_per_output": 302.2,
            "ots_hour": 3626.4
        },
        {
            "hour": 2,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.019,
            "ots_per_output": 239.2,
            "ots_hour": 2870.4
        },
        {
            "hour": 3,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.013,
            "ots_per_output": 163.7,
            "ots_hour": 1964.4
        },
        {
            "hour": 4,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.013,
            "ots_per_output": 163.7,
            "ots_hour": 1964.4
        },
        {
            "hour": 5,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.018,
            "ots_per_output": 226.6,
            "ots_hour": 2719.2
        },
        {
            "hour": 6,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.026,
            "ots_per_output": 131.1,
            "ots_hour": 1573.2
        },
        {
            "hour": 7,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.029,
            "ots_per_output": 146.2,
            "ots_hour": 1754.4
        },
        {
            "hour": 8,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.036,
            "ots_per_output": 181.5,
            "ots_hour": 2178
        },
        {
            "hour": 9,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.042,
            "ots_per_output": 211.8,
            "ots_hour": 2541.6
        },
        {
            "hour": 10,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.051,
            "ots_per_output": 257.1,
            "ots_hour": 3085.2
        },
        {
            "hour": 11,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.057,
            "ots_per_output": 287.4,
            "ots_hour": 3448.8
        },
        {
            "hour": 12,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.062,
            "ots_per_output": 312.6,
            "ots_hour": 3751.2
        },
        {
            "hour": 13,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.064,
            "ots_per_output": 322.7,
            "ots_hour": 3872.4
        },
        {
            "hour": 14,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.062,
            "ots_per_output": 312.6,
            "ots_hour": 3751.2
        },
        {
            "hour": 15,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.061,
            "ots_per_output": 307.5,
            "ots_hour": 3690
        },
        {
            "hour": 16,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.057,
            "ots_per_output": 287.4,
            "ots_hour": 3448.8
        },
        {
            "hour": 17,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.053,
            "ots_per_output": 267.2,
            "ots_hour": 3206.4
        },
        {
            "hour": 18,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.051,
            "ots_per_output": 257.1,
            "ots_hour": 3085.2
        },
        {
            "hour": 19,
            "daypart": "day",
            "daily_ots": 60500,
            "hourly_share": 0.05,
            "ots_per_output": 252.1,
            "ots_hour": 3025.2
        },
        {
            "hour": 20,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.048,
            "ots_per_output": 604.4,
            "ots_hour": 7252.8
        },
        {
            "hour": 21,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.046,
            "ots_per_output": 579.2,
            "ots_hour": 6950.4
        },
        {
            "hour": 22,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.043,
            "ots_per_output": 541.4,
            "ots_hour": 6496.8
        },
        {
            "hour": 23,
            "daypart": "night",
            "daily_ots": 151100,
            "hourly_share": 0.039,
            "ots_per_output": 491.1,
            "ots_hour": 5893.2
        }
    ],
    "total_ots": 87589.2
}
400

A parameter or body field is missing, malformed or out of range.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "invalid_parameter",
        "message": "Parameter 'hourly_share' is invalid. The shares must sum to 1 within 0.005 (fractions, not percent), got 100.0000.",
        "param": "hourly_share"
    }
}
401

The key is missing, unknown or revoked.

HeaderTypeDescriptionExample
WWW-Authenticatestring

Always "Bearer".

Bearer
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "unauthorized",
        "message": "The API key is not valid.",
        "param": null
    }
}
413

The request body is larger than 64 KiB.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "invalid_parameter",
        "message": "The request body is larger than 64 KiB.",
        "param": null
    }
}
429

The per-minute limit of the key is exceeded. Wait for Retry-After seconds.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Retry-Afterinteger

Seconds to wait before the next request.

17
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "rate_limited",
        "message": "The limit of 60 requests per minute for this key is exceeded.",
        "param": null
    }
}
500

Unexpected failure on our side.

HeaderTypeDescriptionExample
X-RateLimit-Limitinteger

Requests per minute allowed for the key.

60
X-RateLimit-Remaininginteger

Requests left in the current 60 second window.

59
Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "internal_error",
        "message": "The service failed to process the request.",
        "param": null
    }
}
503

The service cannot reach its data right now. Retry later.

Response fields
FieldTypeDescriptionExample
errorrequired object

Details of the failure.

error.coderequired string one of: invalid_parameter, unauthorized, not_found, no_data, rate_limited, internal_error, service_unavailable

Stable machine-readable error code. Branch on this field, not on the message.

not_found
error.messagerequired string

English sentence for a human. The wording can change between releases.

Screen 99999 was not found.
error.paramrequired any of

Name of the request parameter or body field that caused the error, or null when the error is not tied to one parameter. Nested body fields use dots, for example "daily_ots.night".

error.param (option 1)optional string
error.param (option 2)optional null
{
    "error": {
        "code": "service_unavailable",
        "message": "The service is temporarily unavailable. Retry later.",
        "param": null
    }
}

Example request

curl -X POST 'https://ots.programmatic-engine.ru/v1/calculate' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "daily_ots": {
        "day": 60500,
        "night": 151100
    },
    "hourly_share": [
        0.036,
        0.024,
        0.019,
        0.013,
        0.013,
        0.018,
        0.026,
        0.029,
        0.036,
        0.042,
        0.051,
        0.057,
        0.062,
        0.064,
        0.062,
        0.061,
        0.057,
        0.053,
        0.051,
        0.05,
        0.048,
        0.046,
        0.043,
        0.039
    ],
    "day_hours": {
        "start": 6,
        "end": 19
    },
    "night_mode": "separate"
}'