Main API: moving from v4 to v5

What this page is

Version 5 of the Main API does everything version 4 does. The functions are the same. What has changed is the addresses, the form of the replies, the status codes and the types of the values. This page lists every difference that a program calling the API will notice: what v4 does, what v5 does, and what to change.

Version 4 keeps working until 2027-09-30, but it will not be developed or changed: new functions are only added to version 5. Every v4 reply has a Deprecation header, a Sunset header with the end date and a Link header to this page. From 2027-10-01 every v4 call is answered with error 10060 (410 Gone). The same tokens work in both versions, so a program can be moved one call at a time.

New in version 5 is OAuth 2.0: the token is exchanged for an access token that is valid for an hour, instead of being sent with every call. Sending the token directly, as in v4, keeps working in v5 until 2027-12-31. See Authentication: OAuth 2.0.

Version 5 is described in full on the documentation page. The same description can be fetched as openapi.yaml (OpenAPI 3.0) and as a Postman collection with every endpoint.

The most common changes

Most programs need the changes in this list. The sections after it have the details and the less common cases.

  1. Change the base address to https://api.myweblog.se/main/v5/, and remove the slash at the end of every address: users/ becomes /users.
  2. Send the token as Authorization: Bearer <token>. The word Bearer is required. Better: exchange the token for an access token with OAuth 2.0 (see Authentication), which is the only way accepted from 2028-01-01.
  3. Put the id of an item in the address: users/?id=5 becomes /users/5. The same goes for PATCH and DELETE.
  4. Read the result from data. One item is an object, not a list, and an item that does not exist gives 404.
  5. Replace verbose=true with include= and the names of the blocks that are needed.
  6. Read yes and no as true and false (v4: 1 and 0), and send filters the same way.
  7. Read member numbers, phone numbers, postal codes and account numbers as text, and send them as text.
  8. Read a list page by page: follow links.next until it is null. A page has 100 items unless limit says otherwise (v4: 500).
  9. Handle the status codes that are new or have moved: 403 (the token lacks the right), 404 (not found), 409 (cannot be carried out as things are) and 429 (too many calls).
  10. Read an error as one object with code and title, and with errors when parameters or fields are wrong.
  11. Remove parameters and fields that the endpoint does not have. v4 ignored them; v5 answers 400 and names them.

The rules that changed everywhere

Address and token

v4 v5 What to do
Base addresshttps://api.myweblog.se/main/v4/https://api.myweblog.se/main/v5/Change the base address.
Slash at the endEvery address ends with one: users/No slash: /users. /users/ gives 404, and the reply says why.Remove the slash at the end.
TokenCreated on the websiteThe same tokens work. No new token is needed, except for the sensitive user data (see Users). The website also shows each token's Client ID, used for OAuth 2.0.Nothing.
Authorization headerBearer <token>, and the token alone is also acceptedMust be Bearer <access token> (OAuth 2.0), or Bearer <token> until 2027-12-31Add Bearer if it is missing, and move to OAuth 2.0 before 2028 (next section).

Authentication: OAuth 2.0

v4 has one way to authenticate: the token from the website is sent with every call. v5 adds OAuth 2.0 client credentials, the standard way for a program to get access to an API. The token is exchanged for a short-lived access token, and the access token is what every call carries. The token itself travels to one address only, and seldom.

Until 2027-12-31 both ways work in v5, so a program can be moved to v5 first and to OAuth 2.0 later. Every reply to a call where the token is sent directly has a Sunset header with that date. From 2028-01-01 such a call is answered with 401 (code 10015). v4 is not affected by the date.

Value
Token URLhttps://api.myweblog.se/main/v5/oauth/token
Grant typeclient_credentials
Client IDThe number shown as "Client ID" next to the token on the website (Integrations, API)
Client secretThe token itself, the value that starts with mwla1_
Client authenticationHTTP Basic (client_id:client_secret, the default in most libraries), or the form fields client_id and client_secret
Request bodyForm data (application/x-www-form-urlencoded) with grant_type=client_credentials. Not JSON.
Access token lifetime3600 seconds. Read expires_in; do not assume the number.
ScopesNone to request. The rights set on the token on the website apply; the reply lists them in scope for information.
Rate limit, IP addresses, revocationThose of the token. Fetching an access token counts as one call. Deleting the token on the website stops its access tokens at once.

What a program does:

  1. Read the token's Client ID on the website and store it next to the token.
  2. Fetch an access token with one POST to the token URL. Keep access_token and the time it expires: now plus expires_in seconds, minus a margin of about 60 seconds.
  3. Where the program sent Bearer <token>, send Bearer <access token>. Nothing else in the request changes.
  4. On 401 with error="invalid_token" in the WWW-Authenticate header: forget the stored access token, fetch a new one, retry the request once. Any other 401 means the id or secret is wrong or the token was deleted; do not retry in a loop.
  5. Fetch an access token when there is none or it is about to expire, not before every call.

With curl:

## 1. Fetch an access token (Basic authentication with the client id and the token)
curl -u "4711:mwla1_your_token_here" \
  -d "grant_type=client_credentials" \
  "https://api.myweblog.se/main/v5/oauth/token"
## {"access_token":"mwlat_...","token_type":"Bearer","expires_in":3600,"scope":"users:rw transactions:r"}

## 2. Call the API with it
curl "https://api.myweblog.se/main/v5/token" \
  -H "Authorization: Bearer mwlat_..."

In PHP:

function get_access_token($client_id, $client_secret){
  $curl = curl_init('https://api.myweblog.se/main/v5/oauth/token');
  curl_setopt($curl, CURLOPT_USERPWD, $client_id . ':' . $client_secret);
  curl_setopt($curl, CURLOPT_POSTFIELDS, 'grant_type=client_credentials');
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  $reply = json_decode(curl_exec($curl), true);
  curl_close($curl);
  if(empty($reply['access_token'])) return null;
  // expires_in is seconds from now; keep a margin of 60 seconds
  return ['token' => $reply['access_token'], 'expires_at' => time() + $reply['expires_in'] - 60];
}

// Before each call
if($access === null or $access['expires_at'] < time()){
  $access = get_access_token($client_id, $client_secret);
}
$headers = ['Authorization: Bearer ' . $access['token']];

In Python with the requests package:

import time, requests

access = {"token": None, "expires_at": 0}

def bearer():
  if access["expires_at"] < time.time():
    r = requests.post("https://api.myweblog.se/main/v5/oauth/token", auth=(CLIENT_ID, CLIENT_SECRET), data={"grant_type": "client_credentials"})
    r.raise_for_status()
    access["token"] = r.json()["access_token"]
    access["expires_at"] = time.time() + r.json()["expires_in"] - 60
  return {"Authorization": "Bearer " + access["token"]}

r = requests.get("https://api.myweblog.se/main/v5/users", headers=bearer())
if r.status_code == 401 and "invalid_token" in r.headers.get("WWW-Authenticate", ""):
  access["expires_at"] = 0
  r = requests.get("https://api.myweblog.se/main/v5/users", headers=bearer())

Libraries that do the same with less code: requests-oauthlib (Python, BackendApplicationClient), IdentityModel (.NET), Spring Security's client registration (Java), league/oauth2-client (PHP). In Postman: Authorization type OAuth 2.0, grant type Client Credentials, the token URL above, client ID and client secret from the website, client authentication "Send as Basic Auth header", then Get New Access Token.

Mistake What you see What to do
Fetching an access token before every callIt works, but every fetch counts against the 120 calls per minuteKeep the access token until expires_in minus a margin.
Sending the token request as JSON400 invalid_requestSend application/x-www-form-urlencoded, as the examples do.
Keeping Bearer <token> in the calls after fetching an access tokenIt works until 2027-12-31, so the mistake hides. The replies have a Sunset header.Send the fetched access_token.
Retrying 401 invalid_client in a loop429 after a while, from the limit on failed attemptsCheck the client id and the token; see whether the token was deleted on the website.
Calling from an IP address not in the token's list401 invalid_client at the token URLThe token's IP list applies to the token URL and to the calls alike.

Replies

v4 v5 What to do
ResultUnder the resource's name: {"users": [...]}Always under data: {"data": ...}Read data instead of the resource name.
One itemA list with one item, or an empty listOne object under data, or 404Read an object, and handle 404.
Token owner in every replyauth_token block in every reply, also in errorsNot sent. GET /token has it.Stop reading auth_token; call GET /token if it is needed.
meta_information{"objects": n} in every replyA list has meta (see Lists). One item has none.Read meta.count instead.
Content type of an errorapplication/jsonapplication/problem+jsonAccept both content types.
Error body{"errors": [{"id", "message", "error_subcode"}]}One error object: type, title, status, code, request_id, and errors (a list of field, code, message) when parameters or fields are wrongRead code (the same numbers as v4's id) and title. error_subcode is gone.
Which field is wrongInside the message texterrors[].fieldUse field. All problems are listed at once.
Missing valuesnull or "", as storedAlways null. The key is always there.Treat null as "no value".
A list without items[], null or the key left outAlways []Nothing.
Text that looks like a numberTurned into a number: member number 007 becomes 7Each field has one type. Member numbers, phone numbers, postal codes and account numbers are always text: "007".Read these as text.
Yes/no values1 and 0true and falseRead booleans.
Points in timeSeveral forms: 2026-10-04T08:00:00+00:00, 2026-10-03 12:00:00 (server time)Always UTC, written 2026-10-04T08:00:00Z, in a field whose name ends with _atRead _at fields as UTC.
A reference to something elseTwo fields: organization_id, organization_nameOne object: organization: {"id", "name"}Read .id and .name.

Lists

v4 v5 What to do
Page sizelimit, 500 if left out. Above 500 is cut to 500 without notice.limit, 100 if left out, 1 to 500. Outside that gives 400.Send limit=500 to keep v4's page size, or follow the pages.
Knowing if there is moreNot possiblemeta.has_more, and the address of the next page in links.nextFollow links.next until it is null.
OrderNot fixed for every list (users had none)Every list has a fixed order, the same as v4 where v4 had oneNothing.
Total numberNot availableinclude_total=true adds meta.totalAsk for it on the first page only.
Extra dataverbose=true gives everythinginclude=block,block gives the named blocksList the blocks that are needed.
Leaving fields outNot availableexclude=field,field leaves the named fields out of each item, to make the reply smaller. It takes the fields an item always has, by their name at the top level of the item. Also where one item is returned, and on the reply to POST and PATCH.Nothing. Use it where only a few fields are needed.
Yes/no filter0 = both, 1 = yes, 2 = no (also true/false)true or false; leave the filter out for both. active=1 gives 400.Change the values, and remove =0 filters.
Text search* and _ are wildcards* is the only wildcard. _ and % are ordinary characters.Replace _ wildcards with *.
Dates in filtersYYYY-MM-DD and YYYYMMDDYYYY-MM-DD onlyWrite dates with hyphens.
A parameter without a value (?name=)Treated as a filter on empty text, or ignored400Leave the parameter out.
The id filter?id=5 on the listNot a filter. The id is part of the address: /users/5. ?id=5 gives 400.Use the address of the item.
Lists that had no paging (flight types, approved flight types)Everything in one replyPaged like every other listFollow links.next, or send limit=500.

Status codes

Situation v4 v5
Token missing or not accepted401401 (now always and only this)
Access token expired (OAuth 2.0)-401, with WWW-Authenticate: Bearer error="invalid_token": fetch a new one
Token sent directly after 2027-12-31Works401 (code 10015)
Token lacks the right for the module401403
A parameter has the wrong format401400
The item does not exist200 with an empty list (GET)404
Unknown addressApache's HTML page404 as JSON
Wrong method for the address405405, with an Allow header
Too many callsNot limited429, with Retry-After
Too many failed token checks from one IP address401429, with Retry-After
Flight types for an organization that does not use the flight log500409 (code 10200)
Approved flight types for a user that does not exist500404
Plain HTTP406Redirected to HTTPS by the server
A user or a transaction was created201 (user), 200 (transaction)201, with the address of the new item in the Location header
A user was deleted204204
The user to change or delete does not exist400 (code 10100)404
A field of the body is missing or wrong400400, with every wrong field in errors
PATCH with nothing to change304400 (code 10105)
Username, member number or KSAK member number already in use400 (code 10030)409, with the fields in errors
The same transaction sent twice400 (code 10130)409
Transaction dated before the lock date400 (code 10150)409
Deleting an administrator, or a user with a balance400 (code 10105)409
Changing name, address, email or phone of a user whose login was created elsewhere400 (code 10105)409
Trial organization already has 5 active users403 (code 10040)409
Login check with a wrong username or password401200 with "valid": false
Login check for a username with too many failed logins429 (code 10012)The same

In v5, 403 only ever means that the token lacks a right, and 401 only that the token is not accepted. 409 means that the request is right in its form but cannot be carried out as things are.

Parameters, headers and limits

v4 v5 What to do
A query parameter the endpoint does not knowIgnored400, naming the parameterRemove misspelled or unused parameters.
Body of a POST or PATCH that is not JSONTreated as an empty body400 (code 10004)Send a JSON object.
Request-Id headerReturned as sentReturned as sent (at most 64 printable characters). If none is sent, one is made and returned. It is also in the body of an error.Nothing. Quote it to support.
user_defined_key headerDeprecatedRemovedUse Request-Id.
no_json_numeric_check header, no_autonum_json parameterDevelopment switchesRemovedRemove them.
Calls per minuteNo limit120 per token and minute. Every reply has RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset.Slow down when RateLimit-Remaining is low; wait Retry-After seconds on 429.
Sunset and Link headersNoneOn every reply to a call where the token is sent directly: the date after which that stops working (2027-12-31) and a link to this guide.Move to OAuth 2.0 before the date (see Authentication).

Sending data (POST and PATCH)

v4 v5 What to do
The id of the item to change or deleteIn the body (PATCH) or the query string (DELETE)In the address: PATCH /users/5, DELETE /users/5. id in the body gives 400, and the reply says where it went.Move the id to the address.
Types of valuesLoose: "3" or 3, 1 or true, "12.50" or 12.5The type the value has in a reply. Text: member number, KSAK member number, phone numbers, account numbers ("007", not 7). Number: ids and amounts. true/false: yes or no.Send each value with its type. A wrong type gives 400 that names the field.
A field the endpoint does not knowIgnored400 (code 10007), naming the fieldRemove misspelled or unused fields.
A single value where a list is expected ("email": "a@b.se")Accepted400: email, user_groups and street_address must be listsSend a list, also for one value.
DatesYYYY-MM-DD and YYYYMMDD; for a transaction anything PHP could read as a dateYYYY-MM-DD onlyWrite dates with hyphens.
Errors in the bodyUsers: all listed, the field inside the message. Transactions: only the first one.All listed at once in errors, each with field, code and message. A field inside an object is named address.city, an item of a list email[1] (counted from 0).Read errors[].field.
The reply to POST and PATCHThe item under the resource name, as a list for transactions. PATCH returned everything verbose gives.The item under data, as GET gives it. ?include= adds blocks, with the same rights as for GET.Read data; ask for the blocks that are needed.
What is saved when creating or changing a user fails halfwayA change could be saved in part (the membership but not the login), and a 500 could come after the change was savedNothing: the login, the membership and the event log entry are saved together or not at allNothing.

Error codes

New in v5:

Code Meaning
10004The request body must be a JSON object
10007Unknown parameter or field
10013Too many requests
10014Too many failed authorization attempts
10015The token must be exchanged for an access token (401, from 2028-01-01)
10106Other data depends on this: a setting was not deleted (409; only with the new expiry date types)
10200The organization does not use the flight log (409)

No longer sent:

Code In v4 In v5
10003Invalid parameter dataNot used
10120Invalid amount format10006, with amount in errors
10140Comment can not be empty10006, with comment in errors ("Required")
10160Invalid account format10006, with the account field in errors

Kept, with a new status: 10030, 10040, 10105, 10130 and 10150 are now 409 (see Status codes). 10100 is 404 when it is the item in the address that does not exist.

Endpoint by endpoint

Where each endpoint went

v4 v5 Note
GET token/rights/GET /tokenReturns description, owner, organization and rights.
GET test/, POST test/GET /tokenThe way to check that a token works.
GET organizations/location/GET /organizationReturns id, name and location. Any valid token, as in v4.
GET users/GET /usersSee Users.
GET users/?id=5GET /users/5One object, or 404.
GET users/memberships/GET /memberships, GET /memberships/3See Memberships and user groups.
GET users/groups/GET /user-groups, GET /user-groups/7
GET objects/GET /objects, GET /objects/24See Objects.
GET objects/status/GET /objects?include=statusThe status is a block of the object.
GET bookings/GET /bookings, GET /bookings/4711See Bookings.
GET flightlogs/GET /flightlogs, GET /flightlogs/88See Flight log.
GET flightlogs/flighttypes/GET /flight-types
GET flightlogs/flighttypes/approved/?user_id=5GET /users/5/approved-flight-types
GET transactions/GET /transactions, GET /transactions/900See Transactions.
POST users/POST /usersSee Creating a user.
PATCH users/ with the id in the bodyPATCH /users/5See Changing a user.
DELETE users/?id=5DELETE /users/5See Deleting a user.
POST transactions/POST /transactionsSee Creating a transaction.
POST logincheck/POST /login-checksSee Login check.
Not in v4GET, POST /expiry-date-types, GET, PATCH, DELETE /expiry-date-types/12New in v5: the organization's expiry date types. A new module on the website, Organization settings (organization_settings).
Not in v4GET /users/5/expiry-dates, PUT, DELETE /users/5/expiry-dates/12, GET /expiry-datesNew in v5: a user's expiry dates, read, set and removed, and every member's dates in one list. The module Users (users).

In GET /token, a module has the same name as its resource. A token's rights for Economy, Logbook, Login check and Organisation are listed as transactions, flightlogs, login_checks and organization (v4: economy, logbook, login_check, organisation).

Users

Fields of a user:

v4 v5
status.active (1/0)active (true/false)
status.locked, status.locked_login, status.has_invalid_email_address (only with verbose)locked, locked_login, has_invalid_email_address, always there
membership_id, membership_namemembership.id, membership.name (membership is null if the user has none)
first_name, last_name_particle, last_name, last_first_name (only with verbose)Always there
ksak_member_number or nlf_member_number, a number, only present for organizations in Sweden or NorwayBoth always there, as text, null when they do not apply
organization (only with verbose)Removed. It is always the token's own organization; GET /token and GET /organization have it.
email[].is_invalid (1/0)email[].invalid (true/false)
roles left out when the user has noneroles is []
expiry_dates is null when there are none; is_expiredexpiry_dates is []; expired is true, false, or null when there is no date
annual_fee, member_account_balance, external_customer_numberaccount.annual_fee, account.balance, account.external_customer_number

Blocks, replacing verbose=true. Each adds one key with the same name:

Block Contents Right needed
emailList of addresses with invalid and invalid_reasonusers
addressstreet_address (list), postal_code, city, countryusers
phonehome, work, mobile (country_code, initial_number, number)users
user_groupsList of id and nameusers
rolesList of admin, instructor, student, board_member, maintenanceusers
expiry_datesList of id, name, expiry_date, expiredusers
accountbalance, annual_fee, external_customer_numberusers
misc_infoTextusers
personal_identification_numberTextusers and users_sensitive
internal_notesTextusers and users_sensitive
emergency_contactname, phone, relationusers and users_sensitive
access_cardnumber, code, expiry_dateusers and users_sensitive

The four sensitive blocks need a token with the new right "users_sensitive". In v4, read access to Users was enough. A token cannot be changed, so an integration that needs these blocks needs a new token with that right.

Filters of GET /users:

v4 v5
idUse GET /users/{id}
name, username, member_number, membership_name (with * and _)The same names, text search with *
membership_idThe same
active (0, 1, 2)active (true, false)
invalid_emailhas_invalid_email_address (true, false)
locked, locked_login (1, 0)The same names (true, false)
verboseinclude

Creating a user

POST /users. The field names are the same as in v4.

v4 v5
membership_id as 3 or "3"A number: 3
user_groups as one id or a list, ids as numbers or textA list of numbers: [7, 8]
email as one address or a listA list of at most two addresses
address.street_address as one text or a listA list of at most two rows
ksak_member_number, phone.mobile.country_code, initial_number, number as numbers or textText with digits only: "070". The country code may still be written +46 or 0046.
member_number as number or textText. "007" and "7" are different numbers.
No member number sent: the user gets the highest member number plus oneThe same. If no user in the organization has a member number, the new one gets 10000 (v4: none).
Reply 201 with the user under usersReply 201 with the user under data, and its address in the Location header
Duplicate username, member number or KSAK member number: 400, the first found409, all of them in errors

The username rule is unchanged: it starts with the organization's id and a dash, and has a name after it. The password is still not set through the API.

Changing a user

PATCH /users/{id}.

v4 v5
id in the bodyIn the address
status: {"active": true} (also 1, "1", "true")active: true or false
email: [{"address": "a@b.se"}, {"address": "c@d.se"}], each place changed on its ownemail: ["a@b.se", "c@d.se"]. The list replaces both addresses: send both to keep both, one to remove the second, [] to remove both.
address.street_address: each row changed on its ownThe list replaces both rows
"" emptied a value; null did so for some fields onlynull and "" both empty the value. first_name and last_name cannot be emptied.
access_card.number longer than 50 characters and code longer than 10 were cut off without notice400
access_card.expiry_date as YYYY-MM-DD or YYYYMMDDYYYY-MM-DD
Reply: the user with everything verbose gives, including the sensitive dataThe user as GET /users/{id} gives it. Blocks with ?include=; the sensitive ones need the right users_sensitive.

Unchanged: what can be changed (names, active, email, street address, postal code, city, phone, misc_info, internal_notes, access card). Name, address, email and phone can only be changed for users whose login was created by the organization.

Deleting a user

DELETE /users/{id}. The same rules as in v4: not an administrator, and the balance must be zero. Those two now give 409 instead of 400, and a user that does not exist gives 404 instead of 400. The reply is 204 without a body, as in v4.

Memberships and user groups

v4 v5
id, nameThe same
verbose=true adds usersinclude=users adds users
users[].active (1/0), users[].member_number as a number when it looks like oneactive is true/false, member_number is text
Users of a membership or group in no fixed orderSorted by name
Filters id, namename; the id is in the address

Objects

Fields of an object:

v4 v5
organization_id, organization_nameorganization.id, organization.name
type_designator, type_name only present for aircraftAlways there, null for equipment and premises
active (1/0)active (true/false)
verbose=trueBlocks: status, active_remarks, maintenance_items, settings, time_summary
status.active_remarksIts own block: active_remarks
status.maintenance_itemsIts own block: maintenance_items
settings only present for aircraft; force_* as 1/0settings is null for equipment and premises; force_* as true/false; type_* as text
objects/status/: id, registration, category_id, status_idGET /objects?include=status: status.total_id is v4's status_id; the category is category.id
Filter active (0, 1, 2)active (true, false)

objects/status/ in v4 listed the objects of the booking calendar. GET /objects lists all objects, as objects/ did; an object the website has no status for gets null in the status fields.

Bookings

v4 v5
times.start_utc, times.end_utc (...+00:00)start_at, end_at (...Z)
times.start_local, times.end_localstart_local, end_local
object.timezone (as stored, may be empty)timezone: the time zone the local times are given in (Europe/Stockholm when the organization has none)
object.organization_id, object.organization_nameobject.organization.id, object.organization.name
object.category_id, object.category_nameobject.category.id, object.category.name
users.booking_owneruser
users.student, left out when there is nonestudent, null when there is none
user_mobilemobile
is_primary (1/0)is_primary (true/false)
Filter registrationobject_registration
Filter is_primary (1, 0)is_primary (true, false)

Flight log

v4 v5
No date of its own; only inside the four timesdate
object.object_registration, object.object_organization_id, object.object_organization_nameobject.registration, object.organization.id, object.organization.name
type_of_flightflight_type (null when the flight has none)
departure.blockoff_datetime, departure.takeoff_datetime, arrival.landing_datetime, arrival.blockon_datetime (...+00:00)departure.blockoff_at, departure.takeoff_at, arrival.landing_at, arrival.blockon_at (...Z)
departure.tachometer_out, departure.hobbs_out, arrival.tachometer_in, arrival.hobbs_indeparture.tachometer, departure.hobbs, arrival.tachometer, arrival.hobbs
users: a list with role Pilot, Instructor, Scoutpilot, instructor, scout: one object each, or null
Crew member: organization_id, organization_nameCrew member: organization.id, organization.name (organization is null when not known)
Hidden crew: users is one entry with id -1 and the name "Undisclosed by integrity settings"crew_hidden is true, and pilot, instructor and scout are null
instruction.is_solo: the stored code, "EK" for a student's solo flight, "DK" for a flight that is not one, or nullis_solo_instruction: true (EK), false (DK), or null when not filled in
uplift.fuel_uplift, uplift.oil_upliftuplift.fuel, uplift.oil
summary.total_block, summary.total_block_minutes, ...totals.block, totals.block_minutes, ... (the same for airborne, tach and hobbs)
summary.number_of_flightsnumber_of_flights
daily_check (1/0)daily_check (true, false, or null)
created_at_utccreated_at
Filter has_instructor (0, 1, 2), has_remark (1, 0)true, false
verbose=true for the whole flightRemoved. A flight has no blocks: everything verbose gave is always there. Sending verbose gives 400. exclude=uplift,totals leaves fields out that are not needed.

A flight that passes midnight: each time that is earlier than the time before it is on the next day. v4 only compared a time with the one directly before it, so with a time missing in between (block off 23:10, no take-off or landing, block on 01:20) v4 put the later time on the same day. v5 puts it on the next day.

Flight types a user may log

GET /users/{id}/approved-flight-types. The list of flight types, GET /flight-types, has the same fields as in v4.

v4 v5
The user as a parameter: ?user_id=5The user's id in the address: /users/5/approved-flight-types
The list under approvedflighttypesThe list under data
The object as id and registration on the item itselfobject.id, object.registration
available_flight_types, left out for an object without anyflight_types, [] for an object without any

Transactions

v4 v5
organization_idRemoved. It is always the token's own organization.
created_ts (server time, 2026-10-03 12:00:00)created_at (UTC, 2026-10-03T10:00:00Z)
include_account_rows=true adds accounting.rowsinclude=account_rows adds account_rows
include_account_summary=true adds accounting.summaryinclude=account_summary adds account_summary
account as a numberaccount as text
debit and credit can have many decimals when sums are not exactAlways at most two decimals
A summary row can have 0 as both debit and creditSuch rows are left out
Filter dateThe same, an exact date
Filter amountThe same, a number with at most two decimals

Creating a transaction

POST /transactions.

v4 v5
accounting.debit, accounting.credit (number or text)accounting.debit_account, accounting.credit_account, text with digits: "1930"
amount as a number or as textA number with at most two decimals
user_id as 5 or "5"A number
date: anything that could be read as a dateYYYY-MM-DD
Checks stopped at the first errorAll errors at once, each with its field
Reply 200 with a list of one transaction under transactionsReply 201 with the transaction under data, and its address in the Location header
Unknown user: 400 with code 10100400 with user_id in errors (code 10100)
Duplicate (same user, date, amount and comment): 400409 (code 10130). The check itself is unchanged.
Date before the lock date: 400409 (code 10150). The reply names the lock date.

The renamed account fields avoid a mix-up with debit and credit in a reply, which are amounts.

Login check

POST /login-checks, with username and password as before.

v4 v5
Right login: 200 with logincheck: {user_id, organization_id, username, user_fullname, user_firstname, user_lastname_particle, user_lastname}200 with data: {"valid": true, "user": {id, username, name, first_name, last_name_particle, last_name}}
Wrong login: 401 with logincheck: false200 with data: {"valid": false, "user": null}
Username or password missing: treated as a wrong login (401)400, naming the field
Module login_checkThe same token right. It is listed as login_checks in GET /token.

Too many failed logins for a username still give 429 with code 10012 and a Retry-After header.

Help

Questions about moving to version 5 are answered by support@myweblog.se. Every reply from v5 has a Request-Id header; quote it when the question is about a certain call.