Mobile API: moving from v3 to v5

What this page is

Version 5 of the Mobile API does everything version 3 does: the member's objects, bookings, flight log, balance and transactions, and the bookings the member makes. What has changed is how the app and the member are identified, the addresses, the form of the replies, the status codes and the names and types of the values. This page lists every difference an app notices, with what to change. The names of version 3's functions are used as headings so the page can be read function by function.

Version 3 keeps working until 2027-09-30, but it will not be developed or changed: new functions are only added to version 5. Every v3 reply has a Deprecation header, a Sunset header with the end date and a Link header pointing to this page. From 2027-10-01 every v3 call is answered with 410 Gone.

Version 5 is built on the same framework as the Main API v5, so an app that also uses the Main API meets the same rules there. It 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.

The most common changes

Every app needs the changes in this list. The sections after it have the details, function by function.

  1. Get a client id from myWebLog for your app and register its redirect addresses (write to support@myweblog.se). An app with a server of its own also gets a key, through a link that works once; keep it out of source code repositories and out of app binaries that users can unpack. A native app without a server gets no key (a public client). They replace app_token.
  2. Log the member in once with OAuth 2.1, the authorization code flow with PKCE: the app opens myWebLog's login page in the browser, the member logs in there, and the app exchanges the code at POST /oauth/token. The app never sees the password, and your own login screen goes. Keep the access token and the refresh token; send Authorization: Bearer <access token> with every other call, and renew with the refresh token when a call answers 401. See Authentication.
  3. Call one address per function with the HTTP method that fits, instead of api_mobile.php with qtype: GET /bookings, POST /bookings, POST /bookings/{id}/cancel and so on. See Where each function went.
  4. Read the result from data. One item is an object; a list is an array under data with meta and links beside it. Result, qType and APIVersion are gone.
  5. Fetch the organization once with GET /organization. v3 sent orgData with every reply; v5 does not repeat it.
  6. Read errors from the HTTP status code and a Problem Details body with code and title, instead of Error[{Code, Message}] with status 200. A refused booking rule is 409.
  7. Read times as ISO 8601 text with a zone, 2026-10-04T08:00:00Z or 2026-10-04T10:00:00+02:00, and send them the same way. v3 gave Unix timestamps for bookings and bare clock times for flights.
  8. Read yes and no as true and false (v3: 1 and 0), amounts and totals as numbers (v3: text such as "1.0000"), and member numbers as text.
  9. Read a list page by page: follow links.next until it is null. v3's limit cut the list; v5's limit and offset page through it.
  10. Send JSON in the body of a POST, with Content-Type: application/json, except to the two /oauth endpoints, which take form data. returnType, charset and language are gone: replies are JSON in UTF-8, and the language of the booking messages comes from the Accept-Language header.
  11. Remove parameters the endpoint does not have. v3 ignored them; v5 answers 400 and names them.

The rules that changed everywhere

Address and call

v3 v5 What to do
Addresshttps://api.myweblog.se/api_mobile.php, one address for everythinghttps://api.myweblog.se/mobile/v5/{resource}, one address per resource. No slash at the end: /me works, /me/ gives 404.Change the base address and build the address per function.
The functionPOST with the form field qtypeThe HTTP method and the address: GET reads, POST creates or actsSee Where each function went.
The itemA form field: ac_id, bookingID, booking_id, transaction_idIn the address: /objects/710, /bookings/4711/cancel, /transactions/903Put the id in the address, never in the body.
The version?version=3.0.0, optionalIn the address: /mobile/v5/Remove the parameter.
ParametersForm fields in the POST body, also for readingQuery parameters for GET (/bookings?mine=true&date_from=2026-10-04); a JSON object in the body for POSTSend filters in the query string and data as JSON.
Reply formatreturnType JSON, XML, ARRAY or PLIST; charsetJSON in UTF-8 onlyRemove the two fields. Read JSON.
LanguageForm field language (se, en, no)The Accept-Language header: sv, en, nb or da. English when left out.Send the header on the booking actions, whose messages are for the member.
Unknown parametersIgnored400, naming the parameter or fieldRemove misspelled and unused parameters.

Authentication: OAuth 2.1

v3 identified the app with app_token and the member with mwl_u and mwl_p on every call, where mwl_p was the password or the user token from Login. v5 separates the two and uses OAuth 2.1 (the authorization code flow with PKCE), which every platform has a client library for. There is no password grant: no app ever handles a member's password.

v3 v5 What to do
The appapp_token with every callA client id, sent only to the /oauth endpoints. An app with a server of its own also has a key, sent in a Basic Authorization header (client_id:key in base64) or as the form fields client_id and client_secret; a native app without a server has no key (a public client) and sends client_id alone.Ask myWebLog for your client id (and key) and register your redirect address. Give them to your OAuth 2.0 library with the authorization URL https://api.myweblog.se/mobile/v5/oauth/authorize and the token URL https://api.myweblog.se/mobile/v5/oauth/token.
The member's loginmwl_u and mwl_p with every call; qtype=Login returned userToken, which could be sent as mwl_p from then on and never expiredThe authorization code flow with PKCE: the app opens GET /oauth/authorize in the system browser, the member logs in on myWebLog's page and allows the app, and the app gets a code at its redirect address, which it exchanges at POST /oauth/token with grant_type=authorization_code, code, redirect_uri, code_verifier and, if you like, device_name. The reply has access_token (valid an hour), refresh_token (valid 90 days from its last use), expires_in and scope.Remove your login screen: the app never handles the password. Use AppAuth or your platform's OAuth 2.0 library. Keep both tokens as you kept userToken.
Every other callThe credentials in the form fieldsThe header Authorization: Bearer <access token>Send the header. When a call answers 401 with WWW-Authenticate: Bearer error="invalid_token", renew (below) and retry once.
RenewingNot needed; the user token lived foreverPOST /oauth/token with grant_type=refresh_token and refresh_token. The reply has a new access token and a new refresh token; the old refresh token is dead at once.Renew when a call says so, not before every call. Replace both stored tokens.
Logging outForget the credentialsPOST /oauth/revoke with the form field token = the refresh token. Always 200.Call it at logout, then forget the tokens. The login disappears from the member's My settings on the website.
The member ends itNot possible, except by a password changeThe member sees the app's login on My settings on the website, next to device_name, and can end it. A password change ends every login. The next call then gets 401 and the refresh gives invalid_grant.Treat invalid_grant on a refresh as "logged out": show the login screen.
Wrong passwordError 200 with status 200Handled on myWebLog's login page, which tells the member; the app never sees the password. The website's limits on failed logins apply there. If the member gives up, the app gets error=access_denied at its redirect address.Nothing to show. Treat access_denied as "the member changed their mind" and show your start screen.
v3 user tokensuserToken from LoginNot acceptedEvery member logs in again once, with their password, when the app moves to v5.
The app aloneNothinggrant_type=client_credentials gives an access token for the app alone, good for GET /client only: does my key work, what are my rights.Useful in a setup check; nothing else.

Replies

v3 v5 What to do
The result{"APIVersion": "3.0.0", "qType": "GetObjects", "result": {"orgData": ..., "Object": [...], "Result": true}}{"data": ...}: one object for one item, an array for a list, with meta and links beside a listRead data. Drop APIVersion, qType and Result.
orgDataSent with every replyGET /organization, once per session. See orgData.Fetch it after the login and keep it.
Empty valuesA field that was empty was left out of the item (GetObjects), or was "" or "0"Every field of an item is always there: null when there is no value, false for no, 0 for noneStop testing whether a key exists. Test for null.
NumbersText: "1.0000", "123.45", "1118"Numbers: 1, 123.45, 1118. Member numbers stay text, since they can have leading zeros.Read numbers as numbers.
Yes and no1 and 0, or the key missingtrue and falseRead and send booleans.
Points in timeUnix timestamps (bStart), clock times without a date (block_start), server-time text (created)ISO 8601 with a zone: ..._at in UTC (2026-10-04T08:00:00Z), ..._local in the organization's time zone with its offsetParse ISO 8601. Show _local, or convert _at with the organization's timezone.
Datesyyyy-mm-ddYYYY-MM-DD, the sameNothing.
NamesSwedish and English mixed: Fornamn, bStart, platserkvar, nature_beskrEnglish, lower case with underscores, the same names as in the Main API v5See the tables below for each function.
PicturesobjectThumbnail as base64 text inside the objectGET /objects/{id}/thumbnail: the JPEG itself, with an ETag and a day's Cache-Control; has_thumbnail in the object says whether there is oneFetch pictures as pictures, and cache them.

Errors

v3 v5 What to do
Status code200 also for errors (403 for a blocked address)The real status: 400, 401, 403, 404, 405, 409, 429, 500Test the status code first.
The body{"APIVersion": "3.0.0", "Error": [{"Code": "200", "Message": "INVALID USERNAME OR PASSWORD"}]}Problem Details (RFC 9457, content type application/problem+json): type, title, status, code, request_id, and errors when parameters, fields or booking rules are namedRead code and title. See Error codes for where each v3 code went.
A refused bookingStatus 200 with errorMessage: the rule texts409 with code 10300 and the texts in errors[].messageShow the texts to the member, as before.
The OAuth endpoints-/oauth/token and /oauth/revoke answer in the OAuth form: error and error_descriptionYour OAuth library reads them.
SupportNothing to quoteEvery reply has a Request-Id header, also in the body of an errorLog it; quote it to support.

Lists

v3 v5 What to do
Lengthlimit cut the list (transactions 20, flight log 50); the rest could not be fetchedlimit (100, transactions 20; at most 500) and offset page through the whole list. meta.has_more and links.next say whether there is more.Follow links.next until it is null.
Count-include_total=true adds meta.totalOnly when the count is shown.
Fewer fields-exclude=field,field leaves fields out; include=block,block adds the optional blocks of an objectAsk for the object blocks you need; they are what takes time.

Headers and limits

v3 v5 What to do
Calls per minuteNo limit120 per member and 600 per app (over all its members). Every reply has RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; too many give 429 with Retry-After.Fetch what a screen needs, not more; cache the organization and the objects. Wait Retry-After seconds on 429.
Content-Type of a POSTForm dataapplication/json, except the two /oauth endpoints (form data)Send JSON.
Request-Id-Optional in the request (your own id, at most 64 characters); always in the replyNothing. Quote it to support.
Blocked address403 with error 990403 with code 10003Nothing.

Function by function

Where each function went

v3 qtype v5 Note
LoginGET /oauth/authorize in the browser, then POST /oauth/token (grant_type=authorization_code), then GET /meThe member logs in on myWebLog's page; the tokens from the exchange, the member from GET /me.
GetUserdataGET /me
GetBalanceGET /meThe balance is in the member.
orgData (with every reply)GET /organizationOnce per session.
GetOrgSettingsGET /organization/settings
GetObjectsGET /objects, GET /objects/{id}, GET /objects/{id}/thumbnailBlocks with include=.
GetBookingsGET /bookings, GET /bookings/{id}
GetBookings with includeSun=1GET /sun-timesA resource of its own.
CreateBookingPOST /bookings201 with the booking.
CutBookingPOST /bookings/{id}/cut
DeleteBookingPOST /bookings/{id}/cancel
GetTransactionsGET /transactions, GET /transactions/{id}
GetFlightLogGET /flightlogs, GET /flightlogs/{id}
GetFlightLogReversedGET /flightlogs?order=desc
RegisterCaptureNot in version 5Write to support if your app uses it.
-GET /clientNew: the app's own rights and limits.

Login, GetUserdata and GetBalance

The login gives tokens, not the member: fetch the member with GET /me after it. GetUserdata and GetBalance both became GET /me.

v3 v5
userData.userTokenThe tokens of POST /oauth/token (see Authentication)
userID / IDid
usernameusername
medlemsnrmember_number (text)
Fornamn / fornamn, Partikel / efternamn_partikel, Efternamn / efternamn, fullname / fullNamename.first, name.prefix, name.last, name.full
user_categoryuser_category
locked (1/0)locked (true/false): the member may read but not book or change anything; such a call gives 403 with code 10302
hidden, lockedLoginRemoved. Such a member cannot log in: POST /oauth/token gives 400 invalid_grant with the reason in error_description.
orgID / club_id, orgName, orgHomepageorganization.id, organization.name, organization.homepage
Balance / saldo (text)balance (number)
currency_symbol, int_curr_symbolcurrency.symbol, currency.code
password (hashed, in GetUserdata)Removed.

orgData

v3 sent the organization with every reply as orgData. v5 gives it once, with GET /organization, with the fields an app needs and nothing else.

v3 orgData v5 GET /organization
ID, name, homepageid, name, homepage
timeZonelocation.timezone, and location.utc_offset (the offset right now)
The reference airport (as sunData.refAirport)location.reference_icao, location.latitude, location.longitude, location.country
format_ymddate_format
clubLocalelocale
currency_symbol, int_curr_symbolcurrency.symbol, currency.code
hide_bookingbooking.enabled (the opposite). When false, only the booking endpoints refuse (403, code 10301); v3 refused every function with error 101.
stdlength (text, hours)booking.standard_length_hours (number)
force_cnl_commentbooking.cancel_reason_required
use_expected_airbornebooking.use_expected_airborne
Everything else in orgDataRemoved. Write to support if your app used a field that is not here.

GetOrgSettings

GET /organization/settings. The same settings, those the organization has marked "show in the app" on the website, as a list under data instead of orgSettings. Each has id (the setting type, as before), value, value_type and the website's own fields for it.

GetObjects

GET /objects gives the same objects in the same order. v3 gave everything it had about every object, including the status, the remarks and the picture; v5 gives the basic fields and adds the rest only when asked, with include=. That is what makes the list fast.

v3 v5
Input ac_id (one object)GET /objects/{id}
Input includeObjectThumbnail=1, output objectThumbnail (base64)GET /objects/{id}/thumbnail: the JPEG itself. has_thumbnail in the object says whether there is one.
ObjectIds (a list of the ids beside the objects)Removed. The ids are in the items.
IDid
regnrregistration
bobject_catcategory.id (0 Aircraft, 1 Equipment, 2 Premises) and category.name
modelmodel.designator, and model.name (both null for equipment and premises)
club_id, clubnameorganization.id, organization.name
commentcomment (null when empty; v3 left the key out)
objectStatusstatus.total_id, with include=status; also status.maintenance_id and status.remarks_id (0 OK, 1 yellow, 2 red)
maintTimeDate.hoursToGoValue, hoursToGoText, flightStop_hoursToGoValue, flightStop_hoursToGoTextstatus.remaining_time.soft_airborne, soft_airborne_formatted, hard_airborne, hard_airborne_formatted, with include=status
activeRemarks[]: remarkID, remarkDate, remarkBy, remarkText, remarkCategoryactive_remarks[]: id, created_date, created_by, description, category_id, with include=active_remarks
flightData.total: airborne, airborneText, block, blockText, tachtime, tachtimeText, landingstime_summary.total: airborne, airborne_formatted, block, block_formatted, tach, tach_formatted, landings, with include=time_summary
ftData (flight-time totals per type)Removed. Write to support if your app showed it.
disclaimerHeadline, disclaimerTextdisclaimer.headline, disclaimer.text (disclaimer is null when there is none), with include=disclaimer
use_expected_airborne, force_expected_time (keys missing when 0)settings.use_expected_airborne, settings.force_expected_time (true/false), and settings.max_booking_length, with include=settings

GetBookings

GET /bookings gives what the booking calendar on the website shows the member, with the bookings in the queue last. The filters have new names; the sun times are a resource of their own.

v3 v5
Input ac_idobject_id
Input mybookings=1mine=true
Input from_date, to_datedate_from (today when left out, as before), date_to
Input booking_id (one booking)GET /bookings/{id}
Input includeSun=1, output sunDataGET /sun-times?date_from=&date_to=: airport, timezone and days[] with date, dawn, sunrise, sunset and dusk, each in UTC (at) and local (local). At most 62 days.
IDid
bStart, bEnd (Unix timestamps)start_at, end_at (UTC, ISO 8601), start_local, end_local (the organization's zone, with offset) and timezone
ac_id, regnr, bobject_cat, club_idobject.id, object.registration, object.category.id and .name, object.organization.id
user_id, fullname, medlemsnr, email, completeMobileuser.id, user.name, user.member_number, user.email, user.mobile. Email and mobile are null when the member hides them on the website, as before.
elevuseridstudent: id, name, member_number, email, mobile, or null when the booking has no student
typtype (the same values, such as PRIV and SKOL)
primary_bookingis_primary, and is_queue (its opposite)
isOwnBooking, isStudent (1, or the key missing)is_own, is_student (true/false)
fritextcomment
platserkvarseats_left
expected_airborne (text)expected_airborne (number, hours)
hide_details and the names left emptydetails_hidden: true when the organization hides who has booked from other members; user and student then have null names and contact details
thisUserCanEdit, thisUserCanDelete, thisUserCanCut, queueAllowedpermissions.can_edit, permissions.can_cancel, permissions.can_cut, permissions.queue_allowed
-New: is_active_now, is_passed, created_at, created_by
Everything else the row had (bStartLT, bClass, b_start, ...)Removed. Write to support if your app used a field that is not here.

CreateBooking

POST /bookings with a JSON body. The same rules and the same messages as before, since the booking is made by the website's booking class as in v3.

v3 v5
Input ac_idobject_id (number)
Input bStart, bEnd: yyyy-mm-ddThh:mm+hh:mm, or a Unix timestampstart_at, end_at: ISO 8601 with offset or Z, the seconds optional (2026-10-10T10:00+02:00, 2026-10-10T08:00:00Z). A Unix timestamp is not accepted. Both times are converted to the organization's zone alike (v3 did not convert the end time).
Input fritextcomment
Input expectedAirborneexpected_airborne (number, hours)
Input platserkvarseats_left (number)
Input languageThe Accept-Language header: sv, en, nb or da
Output BookingID, Result201 with the booking as GET /bookings/{id} gives it under data, and a Location header with its address
Output infoMessageTitle, infoMessage (the texts joined with blank lines)messages[], each { "kind": "info", "text": "..." }. There is no title: show the texts.
Output errorMessage (the rule texts, with status 200)409 with code 10300 and the rule texts in errors[].message
Error 300 "Invalid booking time"400 with code 10006 and the field named in errors
Error 201 for a locked member403 with code 10302
Error 101 when the organization has switched booking off403 with code 10301

CutBooking and DeleteBooking

v3 v5
qtype=CutBooking, input bookingIDPOST /bookings/{id}/cut, no body
qtype=DeleteBooking, input bookingID, reasonPOST /bookings/{id}/cancel, body { "reason": "..." } or {}. A reason is required when booking.cancel_reason_required on /organization is true, as before.
Output infoMessageTitle, infoMessage, Result200 with the booking under data (after the cut as it is then; after the cancel as it was) and messages[]
Output errorMessage with status 200409 with code 10300 and the texts in errors; 404 when the booking is not one the member may see
Whether the member mayKnown beforehand: permissions.can_cut and permissions.can_cancel on the booking

GetTransactions

GET /transactions, newest first as before, 20 per page unless limit says otherwise, and a year back unless date_from says otherwise.

v3 v5
Input limit (cut the list at 20)limit and offset page through the list; links.next gives the next page
Input from_date, to_datedate_from, date_to
Input transaction_idGET /transactions/{id}
Balancemeta.balance: the balance now, over all transactions
InitialBalancemeta.opening_balance: the balance before the oldest transaction on the page
BalanceAtToDate, UsedCountLimitRemoved (both were always empty).
format_ymd, locale, currency_symbol, int_curr_symbolOn GET /organization: date_format, locale, currency.symbol, currency.code
TransactionData[]data[]
IDid
datedate
created (server time, yyyy-mm-dd hh:mm:ss)created_at (UTC, ISO 8601)
amount (text)amount (number)
commentcomment: a "#" and what follows it is left out, as before
bookedby_fullnamebooked_by
balance (the balance after the transaction)balance_after (number)

GetFlightLog and GetFlightLogReversed

GET /flightlogs, oldest first; order=desc is what GetFlightLogReversed was. The fields are those of the Main API v5's flight log, so the clock times are points in time with a zone, not bare clock times, and the totals are numbers.

v3 v5
Input limit (cut the list at 50)limit (100) and offset page through the list
Input from_date, to_datedate_from, date_to
Input myflights=1mine=true
Input ac_idobject_id
qtype=GetFlightLogReversedorder=desc
FlightLog[]data[]
rowIDid
flight_datumdate
ac_id, regnrobject.id, object.registration, and object.organization
user_id, fullnamepilot.id, pilot.name (and member_number, organization)
instructor_user_id, instructor_fullnameinstructor (the same fields), or null
scout_user_id, scout_fullnamescout (the same fields), or null
The names shown whatever the organization's settingcrew_hidden: true when the organization hides the pilot in its log; pilot, instructor and scout are then null, as on the website
departure, arrival (ICAO codes)departure.airport.icao and .name, arrival.airport.icao and .name
viavia_route
block_start, block_end (hh:mm)departure.blockoff_at, arrival.blockon_at (UTC, ISO 8601)
airborne_start, airborne_end (hh:mm)departure.takeoff_at, arrival.landing_at
tach_start, tach_endRemoved. totals.tach keeps the tach time.
block_total, airborne_total, tach_total (text, "1.0000")totals.block, totals.airborne, totals.tach (numbers, hours) and totals.block_minutes, airborne_minutes, tach_minutes. A total is null when one of its two readings is missing.
flightsnumber_of_flights
nature_beskrflight_type.name, and flight_type.id (flight_type is null when the flight has none)
commentcomment
distanceRemoved. It is not kept for a flight.
-New: is_solo_instruction (true for a student's solo flight, false with an instructor, null when not filled in)

Error codes

v3 answered every error with status 200 and a list of Error items. v5 answers with the status code and one code. The full v5 table is on the documentation page.

v3 v5
0 SSL CONNECTION NOT DETECTED403, code 10000
100 INVALID CLUB OR APP TOKEN401 invalid_client from /oauth/token when the client id or key is wrong; 401, code 10001, from every other endpoint when the access token is missing, expired or no longer valid. 400 invalid_grant at the login when the app is not allowed for the member's organization.
101 BOOKING DISABLED IN SETTINGS403, code 10301, from the booking endpoints only. Everything else works.
200 INVALID USERNAME OR PASSWORDHandled on myWebLog's login page: the member sees the message there and the app only gets a code, or error=access_denied if the member gives up.
201 LOCKED OR INACTIVE400 invalid_grant at the login for a member who cannot log in; 403, code 10302, when a locked member tries to book, cut or cancel
300 Invalid date format / Invalid booking time400, code 10005 (a query parameter) or 10006 (a body field), with the name in errors
900 INVALID QTYPE404, code 10100 (no such address) or 405, code 10050 (the address exists, not with that method)
990 IP BLACKLISTED403, code 10003
999 Incorrect versionGone; the version is in the address
-New: 400, code 10007 (an unknown parameter or field); 409, code 10300 (a booking rule); 429, code 10013 (too many calls); 403, code 10002 (the app lacks the right); 403, code 10305 (the app's own token on a member endpoint); 404, code 10304 (no picture)

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.