The Yallapp API reference
Every endpoint your app calls, for developers who serve the Yallapp API from their own website. Start with the step-by-step guide, then use this reference.
- 364 endpoints in 43 groups, version 1.0.0. The file is OpenAPI 3.1: open it in any OpenAPI viewer for every field and error code.
- JSON everywhere, dates in ISO 8601 UTC, errors as
{ code, message }, lists paged withpageandper_page. - Signed-in calls send
Authorization: Bearerwith the access token. See the sign-in flow.
App
App configuration and the home screen.
- GET
/configNo sign-inGet app configurationBranding, detected features and app rules set in the plugin settings. Called on every app start. - GET
/homeSign-in optionalGet home screen sectionsSections in the order chosen in the plugin settings. Signed-out users get the public sections only. - GET
/strings/{language}No sign-inGet the owner's translations of the app's own texts in one languageServed by the Yallapp platform for every app, also those on WordPress or their own API: the app takes the address from `Config.languages.list[].strings.url` and never builds it. The pack has the owner's translations of the app's interface strings (the keys of the app's string files); the app shows the owner's string, else its own in that language, else English. Keep the last pack for offline use and download it again when `strings.version` in `/config` changes. Strings with placeholders use the same ICU format as the app's string files (`{name}`, `{count, plural, =1{…} other{…}}`), checked when the owner saves them. With `draft=1` and `X-Yallapp-Preview`, the app preview gets the Pages & look draft's strings.
Pages
Pages and bottom tabs built in the client dashboard.
- GET
/pagesSign-in optionalGet the bottom tabs and the list of pagesThe navigation and pages the client published in the dashboard. When nothing is published yet, the server sends default pages and tabs for the app type, built from the site's content, so the answer is always a full navigation. Servers without pages (older plugins) return 404; the app then uses its built-in tabs and `/home`. The default pages follow `app_type`, the type of the app asking, so they match its screens from the first start. Without it the server uses the type the owner chose in the dashboard, else a guess from the site's plugins. Published pages don't depend on it. Send `If-None-Match` with the last `version` to get 304 when nothing changed. - GET
/pages/{slug}Sign-in optionalGet one page with its blocksBlocks in the order set in the dashboard, with lists (courses, products, services, posts, listings) already resolved. Blocks meant only for signed-in or signed-out users are filtered here. On iOS, blocks and items never carry purchase actions for paid content. 99.3 So are blocks the owner shows only between two dates, on the other platform or in other languages (than the one asked for with `Accept-Language`): the app never gets them. - POST
/forms/{slug}/{block_id}Sign-in optionalSend the answers to a form block by emailRoadmap 99.4. For a `form` block whose `send_to` is `email`: the server checks the answers against the published block (each field it knows, required fields filled, an email or phone that looks like one, a choice among its options, at most 2,000 characters each) and emails them to the business: the hosted API to the app's support email (else its owner's), the plugin to the support email it has from Yallapp (else the site's admin email). With a signed-in person's token, their name and email go with the answers. A form whose `send_to` is `chat` is sent by the app as a chat message instead (`POST /chat/conversations/{conversation_id}/messages`), which needs the person signed in. At most 5 answers a minute and 30 a day from one address, counting only answers that pass the checks.
Auth
Sign-in with a one-time code, tokens and sign-up.
- POST
/auth/otp/requestNo sign-inSend a sign-in codeSends a 6-digit code to the phone number. Rate limited per number and per IP. On a WordPress site the plugin's settings choose who sends it: Yallapp (the default: the plugin makes the code and Yallapp delivers it on WhatsApp) or Digits (through the gateway set up in Digits; needs Digits' REST API add-on). Either way the account is the one with that number on the site, so people who signed up with Digits keep their accounts. The hosted API sends codes itself. Demo apps on the hosted API (the apps on yallapp.com.tr/demo) send no code: only the public demo number signs in, with the fixed code shown on that page. Any other number is refused with 403 `demo_number_only`. The demo number is limited per IP and per app, and works in demo apps only. The app preview in the Yallapp dashboard sends `preview: true`, and only when `Config.auth.preview_sign_in` is true. Such a request never sends a code: only the store review number signs in, with its fixed code. Any other number is refused with 403 `preview_review_number_only`. On the hosted API the store review number (and address) skips the per-number and per-IP limits, as reviewers sign in again and again, but is limited per app: 30 requests an hour for the number and the address together, then 429, so its fixed code can't be guessed. The plugin applies its usual limits to them. - POST
/auth/email/requestNo sign-inSend a sign-in code by emailSends a 6-digit code to the email address, when `Config.auth.methods` has `email` and `Config.auth.email_sign_in` is `code` (the hosted API, and WordPress sites whose owner chose email codes). Same rules as phone codes: valid for 5 minutes, 5 attempts, a new code after a minute, limited per address and per IP. Verify it with `POST /auth/otp/verify`; an address with no account gets a `signup_token` (when sign-up from the app is allowed), and `POST /auth/register` then asks the same details as a phone sign-up (the email is already known). The email comes from the app's own email address when its owner set up their own email server in the Yallapp dashboard, else from Yallapp's address with the app's name. On WordPress the plugin makes the code and Yallapp delivers it; an account that can edit the site never signs in by code (it gets no email and the code is refused), so staff sign in on the website. Refused with 403 `method_not_allowed` when the app doesn't offer email sign-in. Demo apps sign in only by phone: 403 `demo_number_only`. Store review: the app's store review email address (for apps that sign in by email only, where reviewers can't use the review number) gets no email and signs in with the fixed review code. The app preview (`preview: true`) never sends a code: only the store review address signs in there, and any other address is refused with 403 `preview_review_number_only`. - POST
/auth/passwordNo sign-inSign in with email and passwordFor servers whose `Config.auth.email_sign_in` is `password` (a WordPress site: its own accounts and passwords, checked by WordPress with the site's own rules and security plugins). Only existing accounts sign in here; people without an account sign up by phone or on the website (or, where the owner chose email codes, with `POST /auth/email/request`). A wrong address and a wrong password get the same 400 `invalid_credentials`, so the answer never tells whether an account exists. Limited per address and per IP. Devices and the device limit work as for phone sign-in. Refused with 403 `method_not_allowed` when the app doesn't offer email sign-in. In the app preview only the store review email address signs in; anything else gets 403 `preview_review_number_only`. Store review on WordPress: with the plugin's store review access on, its test email address signs in with the fixed code as the password, to the site's account with that address (made when there is none and sign-up is allowed; never an account that can edit the site). - POST
/auth/otp/verifyNo sign-inVerify a sign-in codeVerifies a code from `POST /auth/otp/request` or `POST /auth/email/request`. Returns tokens for an existing account. For a phone number (or email address) with no account, returns a one-time `signup_token` to use with `POST /auth/register` (only when sign-up from the app is allowed). A new phone number is refused with 403 `app_user_limit` when the app has as many users as its plan allows (apps on the hosted API only; a WordPress site keeps its users and has no limit). The error carries `contact`, so the app can show how to reach the app's owner. People who already have an account keep signing in. - POST
/auth/registerNo sign-inCreate an account after verificationFields required here follow the "Required at sign-up" plugin setting. Refused with 403 `app_user_limit` (with `contact`) when the app reached its plan's number of app users since the code was verified. - POST
/auth/refreshNo sign-inRefresh tokensExchanges a refresh token for new tokens. The old refresh token stops working (rotation). Roadmap 68: also while the app is paused, so a signed-in person can still delete their account. - POST
/auth/logoutSigned inSign out this deviceAlso while the app is paused (roadmap 68).
Yallapp sign-in
For a client's own API whose owner chose "Yallapp signs people in" on the dashboard's App sign-in page (roadmap 34). Yallapp's hosted API for the app (`https://yallapp.com.tr/api/app/{app_id}`, `Config.auth.base_url`) serves `GET /config` (only its `auth` counts), the Auth endpoints (`/auth/otp/request`, `/auth/email/request`, `/auth/otp/verify`, `/auth/register`, `/auth/refresh`, `/auth/logout`), `GET`, `PATCH` and `DELETE /me`, `GET /me/devices` and `DELETE /me/devices/{device_id}`, with the sign-in methods, codes, store review access and device limit the owner set in Yallapp. The client's API serves everything else, including `/auth/checkout-link`, `PUT /me/push-token` and the notifications, and never the sign-in endpoints. The access token the app sends to the client's API is a JWT signed with ES256 (header `kid`) by a key pair of that app only, valid for 15 minutes. Claims: `iss` (`https://yallapp.com.tr`), `aud` (the app's ID), `sub` (the person's Yallapp user ID, a string), `phone` (E.164) and `email` only when proven by a code, `name`, `iat`, `exp`; `did` is Yallapp's own. The client's API checks the signature with the keys at `GET /.well-known/jwks.json`, and `iss`, `aud` and `exp`, then finds or creates its own user by `sub`, or by the proven phone or email. A missing, expired or invalid token answers 401, and the app refreshes it at Yallapp. Refresh tokens are opaque and work once; signing out, removing a device and deleting the account end them. Before deleting the account at Yallapp, the app calls the client's `DELETE /me`, so the client can delete its own data (404 or 405 when it keeps none). Roadmap 49: whatever that call answers (an error, no answer), the app then deletes the account at Yallapp, and Yallapp sends the client's API a signed notice, the `accountDeleted` webhook (`POST <API address>/webhooks/yallapp`), retried until the API answers 2xx. The client deletes the person by `sub` from either, whichever comes first.
- GET
/.well-known/jwks.jsonNo sign-inGet the keys that sign access tokensServed only by Yallapp's hosted API, for an app on its own API that Yallapp signs people in to (404 otherwise): the app's public ES256 keys as a JSON Web Key Set, the one signing now first. When the owner replaces the key, the old one stays here for a day, so tokens it signed keep working until they expire. Cache it for up to 5 minutes (`Cache-Control`), and fetch it again when a token names a `kid` you don't have. - POST
/webhooks/yallappNo sign-inA person deleted their account (sent to the client's API)Roadmap 49. Sent by Yallapp to an app on its own API that Yallapp signs people in to, at `<API address>/webhooks/yallapp`, when a person deletes their account in the app. Yallapp has deleted the account already; the client deletes what it keeps about `sub`. The body is a Security Event Token (RFC 8417): a JWT signed with the app's ES256 key, the one that signs its access tokens, checked with the same key set (`GET /.well-known/jwks.json`). Its header has `typ` `secevent+jwt`; its `aud` is `<app ID>:events`, never the access tokens' audience, so a notice never passes as an access token. Each try is signed again (`exp` 5 minutes after `iat`); `jti` stays the same on every try and is also in `Yallapp-Delivery`. Answer 2xx once it's received; repeats of a `jti` should answer 2xx too. A 5xx, 408, 425 or 429 answer, no answer within 10 seconds (5 for the first try) or an API that can't be reached is tried again after 1, 5 and 30 minutes, 2, 6, 12 and 24 hours (8 tries); any other answer stops it (404 or 405: the API doesn't take notices). Redirects aren't followed, and an address on a private network isn't called. The notices of the last 30 days and how each went are on App sign-in › For your developer, where a failed one can be sent again: it starts over with the same `jti` and the same tries.
Account
The signed-in student's profile, devices and push token.
- GET
/meSigned inGet the signed-in student - PATCH
/meSigned inUpdate profileIn a demo app everyone shares one account, which can't be changed (403 `feature_disabled`). - DELETE
/meSigned inDelete accountRequired by Apple and Google. Deletes or anonymises the WordPress user according to the site's policy and signs out every device. In a demo app (one shared account) it signs out this device only. Roadmap 68: it keeps working while the app is paused (its plan ended, or the owner's account is suspended), together with `POST /auth/refresh` and `POST /auth/logout`, on WordPress (plugin 0.4.6) and on the hosted API. The app offers it on its paused screen to someone who is signed in. - GET
/me/devicesSigned inList signed-in devicesIn a demo app, only the current device, and `limit` 0 (no limit). - DELETE
/me/devices/{device_id}Signed inSign out a device - PUT
/me/push-tokenSigned inRegister this device for push notifications - PUT
/me/cartSigned inSay how many items the cart holdsRoadmap 85.3: the app keeps the cart on the phone and says how many items it holds whenever that changes (0 once it's empty or ordered), so a shop on Yallapp's hosted API can send "You left something in your cart" once, 3 hours after the last change, when the owner hasn't turned it off. Nothing else about the cart is sent. Servers that don't need it answer 404, and the app doesn't ask again until it restarts.
Catalog
Categories, courses and course details.
- GET
/categoriesNo sign-inList course categories - GET
/coursesSign-in optionalList coursesPublished courses. A private course (WordPress's Private visibility, which the LMS uses to hide a course from its catalog) is never listed, not even to its students: they find it in `GET /me/courses` (50). - GET
/courses/{course_id}Sign-in optionalGet a courseA private course opens only for the students enrolled in it (bought, free or added by an administrator) and for whoever may read it on the website (its author, editors and administrators); anyone else gets `404 not_found`, as for a course that doesn't exist (50). A course with a password on the website (roadmap 62) shows in the catalog and opens as `password_protected`; see `CourseDetail`. - GET
/bundlesSign-in optionalList course bundlesRoadmap 113.1. Courses managed in Yallapp: the academy's bundles (several courses sold together at one price the academy sets), in the academy's order. Only bundles with at least two published courses are listed (`features.bundles`). 404 on other sources. - GET
/bundles/{bundle_id}Sign-in optionalGet a course bundleRoadmap 113.1. The bundle's page in the app, its courses, and how it's bought on this platform (as a course is). - GET
/courses/{course_id}/curriculumSign-in optionalGet a course's topics and itemsThe course's topics (Tutor LMS topics, LearnPress, LearnDash and MasterStudy sections, LifterLMS sections, Sensei modules...) in the order the LMS shows them, each with its lessons, quizzes, assignments and live classes in order. Items the student can't open are returned with `locked` set and no content. A private course's curriculum is `404 not_found` for whoever can't open the course (`GET /courses/{course_id}`, 50). A course with a password on the website (62) has no topics for a student who isn't in it.
Learning
Enrolled courses, lessons and progress.
- POST
/courses/{course_id}/restartSigned inStart a course againRoadmap 73. Clears the signed-in student's progress in the course (lessons completed, where each video stopped, and the quiz results the LMS keeps with the course, where it does) so they can take it again from the first lesson. Their enrollment, orders, reviews and questions stay. The app offers it ("Start the course again", after asking to confirm) on a finished course whose `CourseDetail.can_restart` is true, then opens the first lesson. `403 not_enrolled` for a student who isn't in the course, `403 restart_disabled` when `can_restart` is false (the owner turned it off for the course, or the LMS has no way to reset a student's progress). A certificate already issued is the LMS's: some keep it, some take it back. - GET
/me/coursesSigned inList the student's coursesPrivate courses the student is enrolled in are listed too (50). - GET
/me/wishlistSigned inList wishlisted coursesEmpty when the LMS has no wishlist (`features.wishlist` is false). Roadmap 62: a private course the student may open (enrolled in it, or may read it on the website) is listed like any other; one they can't open isn't. - PUT
/me/wishlist/{course_id}Signed inAdd a course to the wishlistFails with `feature_disabled` when the LMS has no wishlist (`features.wishlist` is false). A course the student can't open (a private one they aren't in) is `404 not_found` (62). - DELETE
/me/wishlist/{course_id}Signed inRemove a course from the wishlistFails with `feature_disabled` when the LMS has no wishlist (`features.wishlist` is false). - GET
/lessons/{lesson_id}Sign-in optionalGet a lessonFree-preview lessons are public; everything else requires enrollment. A lesson of a private course is `404 not_found`, free previews included, for whoever can't open the course (`GET /courses/{course_id}`, 50); so is a lesson of a course with a password on the website for a student who isn't in it (62). - PUT
/lessons/{lesson_id}/progressSigned inSave video position - POST
/lessons/{lesson_id}/completeSigned inMark a lesson as completeFails with `watch_requirement_not_met` when the site requires a share of the video to be watched first. - POST
/lessons/{lesson_id}/downloadSigned inLinks to save a lesson for offline viewingRoadmap 45. When `features.downloads` is true and the lesson's `offline.available` is true, the app asks for the lesson's files here and keeps them on the phone, encrypted, in its own storage. Each link is for this student only and works for a short time (`expires_at`): the app downloads at once, and asks again to resume a download after that. A lesson whose video plays from a video site (YouTube, Vimeo, Bunny Stream and other players) can't be saved: 409 `not_downloadable`, with `details.reason` as in `LessonOffline`. 403 `feature_disabled` when downloads are off for the app, 403 `not_enrolled` when the student can't open the lesson. - POST
/me/downloads/checkSigned inWhether the student may keep their saved lessonsRoadmap 45. The app sends the lessons it keeps for offline viewing when it starts with a connection. A lesson is `refused` when the student can't open it any more (their enrollment ended, was refunded or expired, or the lesson is gone); the app deletes it. The app also deletes everything when `downloads` is false (the owner turned downloads off or the plan no longer includes offline mode), and a lesson it hasn't been able to check for `keep_days` days. - GET
/me/subscriptionsSigned inList the student's memberships and subscriptions on the websiteRoadmap 90.3. When `features.memberships` is true: the student's current Paid Memberships Pro levels, WooCommerce Subscriptions (active, on hold or cancelled but running until the end of the paid period) and LearnDash groups. Managed on the website: the app shows them and never sells them (no purchase buttons on iOS). - GET
/me/certificatesSigned inList the student's certificatesEmpty when the LMS has no certificate feature active.
Quizzes
Quiz questions, attempts and results.
- GET
/quizzes/{quiz_id}Signed inGet quiz details and the student's attempts - POST
/quizzes/{quiz_id}/attemptsSigned inStart an attemptReturns the questions. Questions of a type the app can't show natively are returned with `render_mode` `webview` and a signed `webview_url`. - POST
/quizzes/{quiz_id}/attempts/{attempt_id}/submitSigned inSubmit answers - GET
/me/quiz-attemptsSigned inList the student's quiz results
Assignments
Roadmap 112.5. Assignments of courses managed in Yallapp, the student's work handed in and its grade, and the files sent with it.
- GET
/assignments/{assignment_id}Signed inAn assignment, with what the student handed in and its gradeRoadmap 112.5. For the course's students: `not_enrolled` (403) otherwise, `access_ended` (403) once their access ended, and `previous_not_done` (403) in a course taken in order until the item before it is done. - PUT
/assignments/{assignment_id}/submissionSigned inHand in an assignmentRoadmap 112.5. Some text, a photo or a PDF (sent with `/course-uploads`), or both. It can be handed in again until it's graded (also when the teacher sent it back, `returned`); then `already_graded` (409), roadmap 113.7 unless the assignment allows another try (`attempts_allowed`). After its due date it's marked `late`. The student is told in the app when it's graded or sent back (`assignment_graded`). - POST
/course-uploadsSigned inStart sending a file for an assignmentRoadmap 112.5. A photo (JPEG, PNG, WebP) up to 20 MB or a PDF up to 200 MB, by a student of one of the app's courses, into the academy's Media library (413 `storage_full` when its storage is full). Sent in chunks to `upload_url` as chat files are, then `/complete`; its `id` is the `upload_id` handed in. - POST
/course-uploads/{upload_id}/resumeSigned inA fresh link to carry on with an upload - POST
/course-uploads/{upload_id}/completeSigned inFinish an upload409 `incomplete` with the offset reached when part of the file is missing.
Live classes
Roadmap 112.5. Live classes of courses managed in Yallapp, with their meeting links and recordings.
- GET
/live-sessions/{live_id}Signed inA live class of one of the student's coursesRoadmap 112.5. `join_url` from 15 minutes before it starts until it ends; `recording_url` once it's over and the academy added one. For the course's students only (`not_enrolled`, 403). - GET
/me/live-sessionsSigned inThe live classes of the student's coursesRoadmap 112.5. `upcoming` (the default) gives those coming and on now, soonest first; `past` those over, latest first. Courses whose access ended are left out.
Commerce
Free enrollment, checkout hand-off and the student's orders.
- POST
/auth/checkout-linkSigned inGet a one-time signed-in checkout linkAndroid only, and only when the "Enroll now on Android" setting is on. The link signs the student in on the website and opens the checkout for the course. It works once and expires after 5 minutes. Roadmap 113.1: courses managed in Yallapp also sell bundles (`bundle_id` instead of `course_id`); paying enrols the student in every course of the bundle, and a course they already have stays theirs. A coupon that takes the whole price (100%) enrols them at once, without a payment: the answer has `enrolled` true with the courses, and its `url` shows the order as received (the app doesn't open it). - POST
/courses/{course_id}/couponSigned inCheck a coupon for a paid courseRoadmap 112.5. What the coupon takes off the course's price, or why it doesn't apply (`status` `invalid` with `message`). Up to 20 tries an hour. Roadmap 113.1 A coupon that takes the whole price applies (`free` true) and enrols the student without a payment when they check out with it. - GET
/me/pointsSigned inThe customer's loyalty points91.4 Loyalty points of shops without a website (`StoreConfig.loyalty`): the balance, what it is worth, and the latest changes, newest first. Points are earned when an order is completed (what was paid for the products, after coupons and points), used in the cart (`CartInput.use_points`), given back when a paid order that used them is cancelled or refunded, and taken back when an order that earned them is refunded or its return approved. `feature_disabled` (403) when the shop has no loyalty points. - GET
/me/ordersSigned inList the student's ordersOrders from the site's checkout (the LMS's own or WooCommerce, or the hosted API's payment page for shops without a website), newest first. Empty when the site doesn't sell (`features.orders`). The Academy app on iOS doesn't show orders, as it doesn't sell courses; Store apps show them on both platforms. 96.3 Apps without a website that sell tickets (Events), paid courses (Academy) or memberships (gyms' Bookings) list those orders here too, each with its `receipt_url`, and the app shows them under My orders (Events and gyms on both platforms). - GET
/me/orders/{order_id}Signed inGet one of the customer's ordersOne order of the signed-in customer, for following it: a restaurant's order shows `fulfilment` and `fulfilment_status`, which change as the kitchen works on it (received, preparing, ready, out for delivery, completed). Each change also reaches the customer as a notification, whose push `data` carries the `order_id`. 91.2 Shop orders too: a shipment with its tracking, completed, cancelled or refunded is an `order_update` notification (unless the phone turned `orders` off) that opens this; on Yallapp's hosted API, shops on Shopify and the other connected platforms too (their orders are looked at for 30 days after a checkout from the app, every 15 minutes). `not_found` for someone else's order. - POST
/me/orders/{order_id}/returnSigned inAsk to return an order91.3 The customer asks the shop to take back an order it completed, within `StoreConfig.returns.days` (`Order.can_return`). The shop approves or declines it (with a reply) on its side; the customer is told with an `order_update` notification. Once asked, it can't be asked again (`conflict`); `forbidden` when the shop takes no returns from the app or the order can't be returned (not completed, too late, nothing physical). - POST
/bundles/{bundle_id}/couponSigned inCheck a coupon for a course bundleRoadmap 113.1. As `POST /courses/{course_id}/coupon`, for the bundle's price. Up to 20 tries an hour. - POST
/courses/{course_id}/enrollSigned inEnroll in a free courseA course with a password on the website (62) is `403 enrollment_disabled`: the student joins it on the website, where its password is entered. A course that needs a membership the student doesn't have (90.3, `CourseDetail.membership`) is `403 membership_required`.
Notifications
The notifications inbox.
- GET
/me/notificationsSigned inList the student's notificationsEvery notification the site sent to the student, newest first, whether or not push reached a device: to everyone, to the students of a course they're enrolled in, or to them alone. Kept for 90 days. - POST
/me/notifications/readSigned inMark every notification as read
Store billing
Roadmap 100. Courses and memberships bought in the app through Google Play Billing (Android) and the App Store's In-App Purchase (iOS), on Yallapp's hosted API for every app (`Config.store_billing.base_url`), as the stores ask for digital content. The app buys with the store's own SDK, then sends what the store gave it to `POST /billing/purchases`; Yallapp checks it with Google or Apple, records it once and gives the access where the course lives (the hosted API, the WordPress site through the plugin's `POST /billing/access`, Teachable or Thinkific through their enrolment APIs, or an app's own API through the `purchase.completed` notice). Refunds and ended subscriptions take it back. Roadmap 100.7: for an app on its own API, and for Teachable or Thinkific courses the platform's API doesn't price, the owner lists what the app sells on Dashboard › In-app purchases (each course's ID, title and price; each membership plan's ID, title, price, period and the course IDs it opens). Physical goods and services (shop products, orders, appointments, tickets to a venue, gym memberships, donations) keep their payment pages.
- POST
/billing/sessionSigned inA store billing token for the signed-in person (WordPress)Roadmap 100. Served by the Yallapp plugin (0.4.23 and later) when `Config.store_billing.session` is `site`, like `POST /chat/session`: the plugin gets the token from Yallapp (`POST /billing/sessions` in the platform API) for the signed-in person's WordPress user ID, display name and email. The app sends it as `Authorization: Bearer` to `Config.store_billing.base_url` until `expires_at` (one hour), and asks again on 401 `invalid_token`. - GET
/billing/productsSign-in optionalWhat the app sells through the store on this platformRoadmap 100. Served by the hosted API at `Config.store_billing.base_url` for every app that sells in the app. The store product IDs of the paid courses and membership plans the owner sells, ready in the store. The app asks the store for each product's price in the person's currency and shows that price, never the site's. Public: no token needed. - POST
/billing/purchasesSigned inSend a purchase the store made, for the access it buysRoadmap 100. After the store's purchase flow says the purchase went through (Android: `purchased`; iOS: a verified transaction), the app sends the Google purchase token or the App Store's signed transaction. Yallapp checks it with the store (that it is this app's, for this product, paid and not refunded), records it once, gives the access (enrolment in the course, or the membership until `expires_at`) and acknowledges it with the store. Only then does the app finish the transaction with the store. Sending the same purchase again answers the same grant. A purchase the store marks pending (`status` `pending`, Android) gives nothing yet: Yallapp gives the access when Google tells it the purchase completed. `invalid_receipt` (400): not a valid purchase of this app's product. `purchase_in_use` (409): the purchase gives access to another account of this app already (a store account buys for one app account). `store_unavailable` (503): the store couldn't be reached; the app keeps the transaction unfinished and sends it again later (at the next start too). A purchase never acknowledged is refunded by Google after three days. - POST
/billing/purchases/restoreSigned inRestore purchasesRoadmap 100. "Restore purchases" (which Apple asks for): the app sends every purchase the store has for the person (Android: the purchase tokens of `queryPurchases`; iOS: the signed transactions of `Transaction.currentEntitlements`), and gets back the grants still active for this account. Purchases already in use by another account of the app are left out, never moved. - POST
/webhooks/yallappNo sign-inA purchase in the store gives or takes access (sent to the client's API)Roadmap 100. Sent to an app on its own API that Yallapp signs people in to and that sells in the app, at the same address and signed the same way as `account.deleted` (a Security Event Token with the app's key), once Yallapp has checked a store purchase (`purchase.completed`), and when Google or Apple says it was refunded, revoked or ended (`purchase.revoked`) or renewed (`purchase.completed` again, with a later `expires_at`). The client gives or takes the access in its own system. Retries and `Yallapp-Delivery` are as for `account.deleted`; the delivery log, with "Send again", is on Dashboard › In-app purchases. Roadmap 100.7. `jti` (and `Yallapp-Delivery`) is the same for every try of one change of one purchase, and a new one for its next change (a renewal, a refund): give or take the access once per `jti`, and answer 2xx to repeats. Answer 2xx only once the access is given or taken: until a `purchase.completed` is delivered, the app hears `store_unavailable` and Yallapp doesn't acknowledge the purchase with Google (which refunds a purchase not acknowledged within three days). A notice still waiting for another try when the purchase changes again is dropped, never sent late: the newer notice says how things stand.
Chat
Roadmap 77. A person and the business (`direct`), and course groups in academies (`group`), on Yallapp's hosted API (`Config.chat.base_url`). People never talk to each other in private. Text, photos, videos, voice messages and files; files count in the app's storage and show in the business's Media library. Messages are kept a year, and longer when the business chooses; the business moderates (reports, blocking, deleting) and is responsible for what is said in its chat. Deleting one's account (`DELETE /me`, on the hosted API or a WordPress site) deletes the person's messages and files too.
- POST
/chat/sessionSigned inA chat token for the signed-in person (WordPress)Roadmap 77. Served by the Yallapp plugin only (0.4.9 and later): the chat lives on Yallapp, and a WordPress app's people sign in on the website, so the app asks its site for a chat token, which the plugin gets from Yallapp (`POST /chat/sessions` in the platform API) for the signed-in person: their WordPress user ID, display name and picture, and for an academy the courses they're enrolled in (their groups). The app then calls `Config.chat.base_url` with this token until `expires_at` (one hour), and asks again on 401 `invalid_token`. The hosted API and Yallapp sign-in don't need it: the app uses its own access token on `Config.chat.base_url` (`Config.chat.session` is `access_token` there). - GET
/chat/conversationsSigned inThe person's conversationsServed by the hosted API (`Config.chat.base_url`) for every app with chat. The conversation with the business (`kind: direct`) always comes first, made on the first call; then, in an academy that turned on course groups, the group of each course the person is enrolled in, most recent message first. Students never talk to each other in private. 113.6 In a school that allows messages, a teacher's conversations with a parent or a student (`kind: school`) follow, each with what the school allows in `school`: always a teacher and one parent or student, never two students or two parents. - GET
/chat/unreadSigned inHow many messages are unreadA cheap call for the badge, made when the app opens and comes back to the front. - GET
/chat/conversations/{conversation_id}/messagesSigned inMessages of a conversationNewest first with `before` (older pages), or oldest first with `after` (what arrived since the last message the app has). While the conversation is on screen the app asks with `after` every `poll_after_seconds` (3 while messages come, up to 15 when it's quiet); a push brings it back when the app isn't open. Messages a person blocked (`ChatMember.blocked`) are left out for them; a deleted message stays in place as `deleted: true` without its text or file. - POST
/chat/conversations/{conversation_id}/messagesSigned inSend a messageText (up to 4,000 characters), a finished upload (`upload_id`, from `POST /chat/uploads`), or both. `client_id` makes it safe to send again after a network error: the same `client_id` in the same conversation answers the message already made. Members the business blocked can read but not send (403 `chat_blocked`). 60 messages a minute per person. 113.6 In a `school` conversation the school's rules apply: 403 `conversation_closed` (the school closed it), `messages_not_allowed` (the school doesn't allow messages between them now), `teacher_first` (only teachers write first and this one hasn't), `attachments_not_allowed` (text only); 409 `outside_hours` outside the school's messaging hours, with when one can write again in `message` (the message isn't kept to be sent later). - POST
/chat/conversations/{conversation_id}/readSigned inMark a conversation read up to a message - GET
/chat/conversations/{conversation_id}/membersSigned inThe people in a groupNames and pictures only, never a phone number or an email address. The business's team shows as the business. - DELETE
/chat/messages/{message_id}Signed inDelete one's own messageDeleted for everyone, with its file. Others see that a message was deleted. - POST
/chat/messages/{message_id}/reportSigned inReport a message to the businessThe business sees the report in its dashboard and Yallapp Business and decides (delete the message, block the sender). Once per person and message; a second report answers 204 too. - PUT
/chat/members/{member_id}/blockSigned inStop seeing a member's messagesFor the person asking only, in every group. The business itself can't be blocked. - DELETE
/chat/members/{member_id}/blockSigned inSee a member's messages again - POST
/chat/uploadsSigned inStart sending a photo, video, voice message or fileChecks the type and size (pictures 20 MB, videos 200 MB, voice messages 20 MB, files 50 MB: PDF, Word, Excel, PowerPoint, text) and the app's storage, where chat files count with the Media library's (413 `storage_full`). The app sends the file in chunks to `upload_url` exactly as Yallapp Business does for Media (the media gate's protocol), then calls `/complete`, then sends the message with `upload_id`. An upload not sent in a message within a day is deleted. - POST
/chat/uploads/{upload_id}/resumeSigned inA fresh link to carry on with an upload - POST
/chat/uploads/{upload_id}/completeSigned inFinish an upload409 `incomplete` with the offset reached when part of the file is missing.
Community
Course reviews and questions and answers. On Yallapp's hosted API (roadmap 63) the business answers questions and may reply to a review (`Review.reply`) or hide it from others (`hidden`, shown to its author only). Roadmap 64: the business may also hide a question or a single answer (`CourseQuestion.hidden`, `QuestionAnswer.hidden`); a hidden one reaches its author only, never other students' lists or counts.
- GET
/courses/{course_id}/reviewsSign-in optionalList a course's reviewsApproved reviews, newest first, with the rating summary. Signed-in students also get their own review, approved or not. - PUT
/courses/{course_id}/reviews/mineSigned inWrite or edit the student's reviewOne review per student and course. Enrolled students only. The site may hold it for approval (`status`). On Yallapp's hosted API (roadmap 63) a student writes or edits at most 20 reviews an hour in an app (429). - GET
/courses/{course_id}/questionsSigned inList a course's questionsQuestions students asked in the course, newest first, without their answers. Enrolled students only. A question the business hid (roadmap 64) is listed only for its author, with `hidden`, and isn't in `total` for others. Roadmap 113.7, pinned questions first; with `lesson_id`, a lesson's discussion only (404 when the lesson isn't in the course). - POST
/courses/{course_id}/questionsSigned inAsk a questionRoadmap 113.7. With `lesson_id`, under one of the course's lessons (its discussion). - GET
/questions/{question_id}Signed inGet a question with its answersA question the business hid (roadmap 64) is `404` to everyone but its author. Hidden answers are listed only for their author. - DELETE
/questions/{question_id}Signed inDelete a questionRoadmap 113.7. Its author, or the course's teacher (`CourseQuestionThread.can_delete`); its answers go with it. - PUT
/questions/{question_id}/pinSigned inPin a question, or unpin itRoadmap 113.7. The course's teacher (an academy with many teachers) pins a question first in its list; others get `not_teacher` (403). The academy pins from its dashboard. - DELETE
/questions/{question_id}/answers/{answer_id}Signed inDelete an answerRoadmap 113.7. Its author, or the course's teacher (`QuestionAnswer.can_delete`). - POST
/questions/{question_id}/answersSigned inReply to a question
Store
WooCommerce products, the cart and the checkout hand-off, for Store apps. The cart lives in the app; the order is placed on the site's own checkout, opened with a one-time link that fills the WooCommerce cart.
- GET
/store/homeSign-in optionalGet the store home screen sectionsSections in the order chosen in the plugin settings. Only sections with content are returned. - GET
/store/categoriesNo sign-inList product categoriesCategories with products, in the site's own order. `parent_id` builds the tree. - GET
/store/productsSign-in optionalList productsPublished products the shop shows (catalog visibility "shop" or "shop and search"; "search" when `search` is given). - GET
/store/products/{product_id}Sign-in optionalGet a product - GET
/store/products/{product_id}/reviewsSign-in optionalList a product's reviewsApproved reviews, newest first, with the rating summary. Fails with `feature_disabled` when the site has reviews turned off. - POST
/store/cartSign-in optionalPrice the app's cartChecks the app's cart against the shop: current prices, stock and quantity limits. Nothing is saved. Shipping, taxes and coupons are added on the site's checkout. - POST
/store/checkout-linkSign-in optionalGet a one-time checkout link for the cartOn WordPress, the link fills the site's WooCommerce cart with these lines, keeps the delivery date and gift message for the order, signs the customer in when they are signed in to the app, and opens the checkout; it works once. On the hosted API it opens the shop's checkout (Shopify's or another connected platform's, or our payment page for shops without a website). The link expires at `expires_at`. Signed-out customers get a link only when the shop allows guest checkout (`store.guest_checkout`). The checkout's last page has `order-received` or `thank_you` in its address, so the app knows the order was placed. A `coupon` (91.1) goes with the order: applied on the site's checkout (WooCommerce, or a connected platform's discount link) or on our payment page; one that doesn't apply any more is left out, never an error. With `use_points` (91.4, shops without a website), the signed-in customer's loyalty points take their value off the order on our payment page: as many as they have then, up to what the products cost after the coupon. They are used when the order is paid. Fails with `cart_changed` (409) when a line is no longer available, with `one_product_at_a_time` (409) when the shop's checkout can only take one product from the app (show the message), and with `purchase_not_allowed` (403) for digital products on iOS. Shops with `store.fulfilment` (restaurants and cafés) also take how the order is collected: `fulfilment` (`pickup` or `delivery`, one the shop offers), a `delivery_address` for delivery, and `scheduled_for` (one of the `slots` of `GET /store/order-times`; null or missing for "as soon as possible"). They fail with `kitchen_closed` (409) for an order as soon as possible while closed (show the message; a scheduled time may still work), `below_minimum` (409) when a delivery's subtotal is under `store.fulfilment.min_order`, `outside_delivery_area` (400) when the district isn't one of `store.fulfilment.delivery_areas`, and `invalid_request` (400) for a missing address or a time that isn't offered. The delivery fee is added to the order on our payment page. Prices, options and fees are always computed by the server. - GET
/store/order-timesSign-in optionalWhen the kitchen takes orders nowFor shops with `store.fulfilment` (restaurants and cafés): whether they're open now, when they open or close next, whether an order "as soon as possible" can be placed now, and the times a scheduled order can be for. Computed in the shop's time zone at the time of the request, so the app asks again before checkout. `not_found` for shops without `store.fulfilment`.
Listings
Property listings, for Real estate apps: search with filters, a listing's details, favorites and messages to the office. Listings are managed in the Yallapp dashboard and served by the hosted API.
- GET
/listings/filtersSign-in optionalGet the filters the search offersThe property types, places and ranges that exist in the office's listings, for the search screen's filters. - GET
/listingsSign-in optionalSearch listingsPublished listings matching the filters. Signed-in users get `favorited` on each. - GET
/listings/{listing_id}Sign-in optionalGet a listing - POST
/listings/{listing_id}/inquiriesSigned inMessage the office about a listingStarts a conversation with the office, or adds to the user's open one about this listing. The office answers from the dashboard. - GET
/me/favoritesSigned inList the user's favorite listings - PUT
/me/favorites/{listing_id}Signed inAdd a listing to favorites - DELETE
/me/favorites/{listing_id}Signed inRemove a listing from favorites - GET
/me/inquiriesSigned inList the user's conversations with the office - GET
/me/inquiries/{inquiry_id}Signed inGet a conversation with its messagesMarks the office's messages as read. - POST
/me/inquiries/{inquiry_id}/messagesSigned inReply in a conversationWriting again reopens a conversation the office closed. Refused with 403 while the office has messages turned off.
News
Articles and categories, for News & blog apps (roadmap 23). Read-only and free to read: there is no paid content. WordPress serves the site's published posts (never private or password-protected ones); the hosted API serves articles written in the Yallapp dashboard or read from the site's RSS or Atom feed. An article's body is processed like lesson content (`render_mode`, `html` with embed placeholders, `embeds`).
- GET
/news/homeSign-in optionalGet the news home screenFeatured articles (sticky posts on WordPress, featured in the dashboard; the newest on a feed), the latest articles (without repeating those) and a rail per category with articles, newest first. Empty lists are sent as empty arrays; rails without articles are left out. - GET
/news/categoriesSign-in optionalList article categoriesCategories with published articles, in the site's own order. - GET
/news/articlesSign-in optionalList articlesPublished articles, newest first (or in the order of `ids`). - GET
/news/articles/{article_id}Sign-in optionalGet an articleA published article with its body, processed like a lesson's content.
Events
Events and tickets, for Events & tickets apps (roadmap 28): events with their days and times, a venue or an online link, and ticket types. Free tickets are taken in the app; paid tickets are bought on the web payment page and issued only once the payment is confirmed. Each ticket has a random code and a signed QR payload, checked once at the door. Tickets to events at a venue are physical-world services, bought on Android and iOS; tickets to online-only events (no venue) are digital content, so iOS never offers buying them (free tickets work everywhere). Served by the hosted API.
- GET
/events/filtersSign-in optionalGet the filters the events list offersThe categories of upcoming published events, with how many each has, and the days of the first and last upcoming event, for the list's filters and the calendar. - GET
/eventsSign-in optionalList eventsPublished events, soonest first. Without `from`, events that haven't ended yet; a cancelled event stays in the list with `status: cancelled` until it would have ended. `from` and `to` are days in `EventsConfig.time_zone`, both included. - GET
/events/{event_id}Sign-in optionalGet an eventA published event with its description (processed like a lesson's content), pictures, venue, ticket types and what each ticket type's button does on this platform (`TicketType.action`). The online link is sent only to people holding a ticket. Signed-in users get `my_tickets`. - POST
/events/{event_id}/registerSigned inGet free ticketsFree ticket types only, on every platform. One ticket per name in `attendee_names` (the signed-in user's name for the rest), at most the ticket type's `max_per_order`. Places are taken atomically: fails with `sold_out` (409) when there aren't that many left, with `sales_ended` (409) after the ticket type's sales end or once the event started, with `event_cancelled` (409), and with `purchase_required` (403) for a paid ticket type. - POST
/events/checkout-linkSigned inGet a one-time link to buy paid ticketsHolds the places and opens the web payment page; the tickets are issued only once the payment is confirmed, and show in `GET /me/tickets` then. Places that aren't paid for are freed after the link expires. Fails with `purchase_not_allowed` (403) on iOS for online-only events (no venue: digital content) and when the organiser can't take card payments, and with `sold_out`, `sales_ended` or `event_cancelled` (409) like `register`. The payment page's last page has `order-received` in its address. - GET
/me/ticketsSigned inList the user's ticketsEvery ticket issued to the signed-in user, upcoming events first (soonest first), then past ones. The app keeps the last answer on the device, so tickets and their QR codes show without a connection. - POST
/me/tickets/{ticket_code}/cancelSigned inCancel one of the user's ticketsFree tickets only, when the organiser allows it (`EventsConfig.allow_cancel_hours`) and the event starts at least that many hours later; the place goes back on sale. Fails with `cancel_not_allowed` (403) otherwise, and with a conflict (409) for a ticket already used or cancelled. Paid tickets are refunded by the organiser.
Donations
Campaigns and donations, for Charity & donations apps (roadmap 29). Campaigns are managed in the Yallapp dashboard and served by the hosted API. The donation itself is paid on a web page, into the charity's own payment account: in the iPhone app always in the system browser (App Store guideline 3.2.2), on Android in the app or in the browser as `open_in` says. A donation counts towards a campaign only after the payment is confirmed.
- GET
/donations/campaignsSign-in optionalList campaignsOpen campaigns first (featured ones first, then the newest), then campaigns that have ended. Drafts are never listed. - GET
/donations/campaigns/{campaign_id}Sign-in optionalGet a campaignAn open or closed campaign with its story, processed like an article's body, and its most recent donations (anonymous ones without a name). - POST
/donations/checkout-linkSign-in optionalGet a one-time link to the donation pageStarts a donation and returns the page where the donor pays. The donor's name and email for the receipt are asked on that page. `open_in` is always `browser` when `platform` is `ios`: an iPhone app may not take donations itself (App Store guideline 3.2.2), so the app opens the link in the system browser, never in a web view inside the app, and words the button "Donate on our website". On Android it is `in_app` (Google Play's billing rules don't cover donations to charities; the app may still open it in the browser). Fails with `invalid_request` (400) for an amount under the campaign's `min_amount`, a custom amount the campaign doesn't allow, or `recurring: monthly` when `DonationsConfig.monthly` is false; with `campaign_closed` (409) when the campaign has ended; with `purchase_not_allowed` (403) on iOS when `DonationsConfig.ios_mode` is `off`; and with `donations_unavailable` (503) while the charity's payments aren't set up. - GET
/me/donationsSigned inList the signed-in user's donationsConfirmed donations started from the app while signed in, newest first, each with its receipt; 47 refunded ones too, with `refunded_total`.
Delivery
Roadmap 104. Delivery and courier companies, on Yallapp's hosted API. The customer sends a parcel: picks a service, the pickup and drop-off places, sees the price before sending, and pays cash to the courier or by card on the company's payment page (its own iyzico, Kashier, Fawaterk, Stripe or PayPal account), as the company allows (`DeliveryConfig.payment`). Delivery is a service in the physical world, so the card payment page opens in the app on Android and iOS alike. Prices follow the company's choice (`DeliveryConfig.pricing`): by distance (a straight line between the two places, so both need their coordinates), by the drop-off zone, or one fixed price; a service may add its own amount. The customer follows the shipment by its stages, or also sees the courier on a map while it is on the way when the company chose live tracking (`DeliveryConfig.tracking`).
- GET
/delivery/servicesSign-in optionalList the company's delivery servicesThe services a customer can choose (same-day, express, documents...), in the company's order. Empty when the company has none; the shipment then has no service. - POST
/delivery/quoteSign-in optionalPrice a shipment before sending itThe price of sending a parcel between two places with a service. Fails with `invalid_request` (400) when a place needs its coordinates (pricing by distance), the zone is missing or unknown (pricing by zone), or the places are further apart than the company delivers (`DeliveryConfig.max_distance_km`). - POST
/delivery/locateSigned inRead a place's coordinates from a maps linkFor a place shared as a Google Maps or Apple Maps link (a WhatsApp location, a short `maps.app.goo.gl` link): the coordinates it points at. Only links of those maps are followed. Fails with `invalid_request` (400) when the link has no coordinates. - POST
/delivery/shipmentsSigned inSend a parcelCreates the shipment at the price `POST /delivery/quote` gives for the same places and service. Paid in cash (`payment: cash`), it waits for a courier at once (`pending`). Paid by card (`payment: card`), it waits for the payment (`awaiting_payment`) and the answer has `pay_url`, the company's payment page, opened in the app on both platforms (a service in the physical world); it waits for a courier once the payment is confirmed. Fails with `invalid_request` (400) as the quote does, or for a payment method the company doesn't take (`DeliveryConfig.payment`); with `payments_unavailable` (503) for a card payment while the company's payments aren't set up. - GET
/me/shipmentsSigned inList the signed-in customer's shipmentsNewest first; `active` only those not yet delivered, failed or cancelled. - GET
/me/shipments/{shipment_id}Signed inFollow a shipmentThe shipment with its stages, its courier and, with live tracking while the courier has it (`picked_up`, `on_the_way`), where the courier is. The app asks again every 15 seconds or so while the screen is open and the shipment is active. - POST
/me/shipments/{shipment_id}/cancelSigned inCancel a shipmentOnly before a courier has it (`awaiting_payment`, `pending`). A shipment paid by card is refunded by the company. Fails with `not_cancellable` (409) once a courier has it. - POST
/me/shipments/{shipment_id}/paySigned inGet a new payment link for a shipmentFor a shipment still `awaiting_payment`, when the first link was closed or ran out. Fails with `not_payable` (409) for any other.
Courier
Roadmap 104. The courier's screens. A courier is a person the company added on the dashboard's Couriers page by mobile number (or by email, for apps that sign in by email only); whoever signs in to the app with that number, proven by its code, is that courier (`User.roles` has `courier`). Every endpoint here answers `not_courier` (403) to anyone else, and to couriers the company paused. Couriers see only the jobs given to them, and, when the company lets couriers take jobs (`DeliveryConfig.assignment` is `open`), the jobs waiting for a courier, without the customer's and recipient's numbers until they take one. The courier's position is sent while the app is open and they have a job on the way, kept only while it is, and shown only to the customer of that job. Roadmap 112.3: these screens are in the company's second app made for couriers (build config `role: courier`) only; its customers' app has none. `DeliveryConfig.courier_app` says where they were before: `same`, in the company's app, shown only to couriers, which apps built before 112.3 still do; `bundle`, in the second app; `yallapp_courier`, in the shared Yallapp Courier app, while the company's app has none. Roadmap 104.8: `yallapp_courier` is Yallapp Courier, Yallapp's own app for the couriers of every delivery company that picks it in Delivery settings, with its own sign-in on the platform. The courier signs in to it once with the number (or email) the company added, picks the company, and Yallapp Courier opens a session at the company's hosted API for them: the same tokens `POST /auth/otp/verify` gives, used with these endpoints. Pushes to the company's couriers on Yallapp Courier's phones (`courier_job`) come from Yallapp Courier's own Firebase project, and their data also carries `app_id`, the company it is from. Such a session ends (401) once the company stops using Yallapp Courier, or pauses or removes the courier.
- GET
/courier/meSigned inThe signed-in courier - PATCH
/courier/meSigned inGo on or off dutyOnly couriers on duty are offered new jobs and told of them. - GET
/courier/jobsSigned inThe courier's jobs`active`: the courier's jobs not yet delivered, oldest first. `available`: shipments waiting for a courier, oldest first, only when the company lets couriers take jobs and the courier is on duty (else empty). `done`: the courier's delivered, failed and cancelled jobs of the last 30 days, newest first. - GET
/courier/jobs/{shipment_id}Signed inA jobOne of the courier's jobs, or one waiting for a courier that they could take. - POST
/courier/jobs/{shipment_id}/acceptSigned inTake a jobOnly when the company lets couriers take jobs and the courier is on duty. The first courier to take it gets it; the others get `job_taken` (409). - POST
/courier/jobs/{shipment_id}/statusSigned inMove a job to its next stageIn order: `picked_up` (the parcel is with the courier), `on_the_way`, then `delivered`, or `failed` when it couldn't be delivered (with a `note`). A cash shipment is paid once delivered: the courier collected `CourierJob.collect`. `released` gives a job that isn't picked up back to the company (to other couriers when they take jobs themselves). Fails with `invalid_status` (409) for any other step. - PUT
/courier/locationSigned inSend the courier's positionSent every 20 seconds or so while the app is open and the courier has a job picked up or on the way (no position is taken in the background). Kept only while they have one; answered 204 and dropped otherwise. At most one every 5 seconds is kept.
Food
Roadmap 105.1. Food delivery from many restaurants, on Yallapp's hosted API. The company adds its restaurants with their menus; the customer picks a restaurant (`GET /food/restaurants`), fills one restaurant's cart, sees the total before ordering (`POST /food/quote`: the dishes, their choices, the delivery fee by the company's delivery prices from the restaurant to the customer, and the restaurant's smallest order), and orders, paying cash to the courier or by card on the company's payment page as the company allows (`FoodConfig.payment`). The restaurant accepts the order with the time it needs, or turns it down; the company's couriers deliver it (the Courier tag: each order's delivery is a courier job, picked up at the restaurant). The customer follows the order's stages, and the courier on a map while it is on the way when the company chose live tracking, then rates the restaurant and the courier.
- GET
/food/restaurantsSign-in optionalList the restaurantsThe company's active restaurants: open ones first, then by `sort`. With the customer's position (`lat`, `lng`) each has its distance and whether it delivers there (`delivers`); restaurants that don't are listed last. `cuisine` keeps one cuisine (`FoodConfig.cuisines`), `q` searches restaurant and dish names. - GET
/food/restaurants/{restaurant_id}Sign-in optionalA restaurant with its menuThe restaurant and its menu in sections, the company's order. Sold-out dishes are listed with `available` false. - POST
/food/quoteSign-in optionalPrice one restaurant's cartThe cart's lines with their prices, the subtotal, the delivery fee to the drop-off and the total. Fails with `invalid_request` (400) for a dish or choice the restaurant doesn't have (or a missing required choice), with `unavailable_item` (409) for a sold-out dish (`field` names the line), `restaurant_closed` (409) while the restaurant is closed or busy, `below_minimum` (409) under the restaurant's smallest order, and `outside_delivery_area` (400) when the restaurant doesn't deliver to the drop-off. - POST
/food/ordersSigned inOrder from a restaurantPlaces the order at the prices `POST /food/quote` gives for the same cart. Paid in cash, it goes to the restaurant at once (`placed`). Paid by card, it waits for the payment (`awaiting_payment`) and the answer has `pay_url`, the company's payment page, opened in the app on both platforms (food delivered is a service in the physical world); it goes to the restaurant once the payment is confirmed. Fails as the quote does, for a payment method the company doesn't take, and with `payments_unavailable` (503) for a card payment while the company's payments aren't set up. - GET
/me/food-ordersSigned inList the signed-in customer's food ordersNewest first; `active` only those not yet delivered, turned down or cancelled. - GET
/me/food-orders/{order_id}Signed inFollow a food orderThe order with its stages, the courier and, with live tracking while the courier has it (`picked_up`, `on_the_way`), where the courier is. The app asks again every 15 seconds or so while the screen is open and the order is active. - POST
/me/food-orders/{order_id}/cancelSigned inCancel a food orderOnly before the restaurant accepts it (`awaiting_payment` or `placed`); then `not_cancellable` (409). A card payment already made is refunded by the company. - POST
/me/food-orders/{order_id}/paySigned inA new payment page for an order waiting for its payment - PUT
/me/food-orders/{order_id}/ratingSigned inRate a delivered orderOnce, after it is delivered, within 7 days. The restaurant's rating counts in its average; the courier's is seen by the company.
Restaurant
Roadmap 105.1. A restaurant's screens. Its staff are people the company added to the restaurant on the dashboard by mobile number (or by email, for apps that sign in by email only); whoever signs in with that number, proven by its code, works for that restaurant (`User.roles` has `restaurant`). Every endpoint here answers `not_restaurant` (403) to anyone else, and to staff of a restaurant the company paused. Staff see their restaurant's orders only: new ones (with a push and a sound), accept each with the minutes it needs or turn it down with a reason, mark it ready for the courier, and set the restaurant busy for a while (no new orders) or open again.
- GET
/restaurant/meSigned inThe restaurant the signed-in person works for - PATCH
/restaurant/meSigned inBusy for a while, or open again`busy_minutes` 15 to 240 stops new orders for that long; 0 opens again. Opening hours still apply. - GET
/restaurant/ordersSigned inThe restaurant's orders`new`: waiting to be accepted, oldest first; `active`: accepted and not yet picked up, the soonest ready first; `done`: picked up, delivered, turned down or cancelled in the last 2 days, newest first. - GET
/restaurant/orders/{order_id}Signed inOne of the restaurant's orders - POST
/restaurant/orders/{order_id}/acceptSigned inAccept a new order with the minutes it needsFrom `placed` only; then `invalid_status` (409). The customer is told when it will be ready, and, when the company makes courier jobs on acceptance (`FoodConfig.courier_job`), a courier job is made at once. - POST
/restaurant/orders/{order_id}/rejectSigned inTurn down a new orderFrom `placed` only. The customer is told the reason; a card payment is refunded by the company. - POST
/restaurant/orders/{order_id}/readySigned inThe order is ready for the courierFrom `accepted` only. Makes the courier job when the company makes them once orders are ready (`FoodConfig.courier_job`).
Market
Roadmap 115. The marketplace: many restaurants, supermarkets and shops in one app (a Yallapp Studio project with the `marketplace` plan feature, growing 105.1's food delivery). Vendors are of three kinds (`restaurant`, `grocery`, `shop`); restaurants have menus, groceries and shops have departments, aisles and products, some sold by weight. The customer finds vendors (`GET /market/vendors`, `GET /market/search`), fills one vendor's cart, sees every line of the price before ordering (`POST /market/quote`: items before and after their discounts, the coupon, the club's discount, delivery, taxes, the wallet), and orders for now or for later, delivered or to pick up, paying cash, by card, with Apple Pay or Google Pay (`MarketConfig.payment`), or partly from the wallet. Each vendor chooses who delivers: the app's couriers (the order is followed on the map) or its own drivers (followed by its stages; on the map when the driver uses the courier app). Every amount is in `MarketConfig.currency`, as a decimal string with that currency's decimals (`MarketConfig.currency_decimals`), and formatted. Vendors never reach customers and customers never reach vendors: no vendor's phone, email or chat is ever shown, and support is the app's owner's (the Chat tag, and complaints here). A complaint can be opened on an order from when it is placed until 48 hours after it arrives; the owner's support team decides it.
- GET
/market/vendorsSign-in optionalList the vendorsThe app's active vendors: open ones first, then by `sort`. With the customer's position (`lat`, `lng`) each has its distance, delivery time and fee, and whether it delivers there; vendors that don't are listed last (they can still be ordered from for pick-up when they allow it). `kind` keeps one kind, `category` one cuisine or department (`MarketConfig.categories`), `q` searches vendors and their items, and the flags keep vendors with free delivery, an offer, the club's benefit or pick-up. - GET
/market/vendors/{vendor_id}Sign-in optionalA vendorThe vendor with its hours, delivery terms, offers and the sections of what it sells: a restaurant's menu sections with their dishes, or a grocery's or shop's departments with their aisles (products come from `GET /market/vendors/{vendor_id}/products`, as there may be many). - GET
/market/vendors/{vendor_id}/productsSign-in optionalA vendor's productsPaged, in the vendor's order. `department` and `aisle` keep one, `q` searches names, brands and descriptions, `barcode` finds one by its barcode or SKU. Out-of-stock products are listed with `in_stock` false after the others. - GET
/market/searchSign-in optionalSearch vendors and what they sellVendors whose name or category matches, and the best matching dishes and products with their vendor, near the customer when `lat` and `lng` are given. - GET
/market/offersSign-in optionalOffers and coupons the customer can useThe app's public offers (with their vendor, or the whole app), and, signed in, the coupons the customer holds (given after a complaint, or for a first order). Codes the owner keeps private are not listed. - POST
/market/quoteSign-in optionalPrice one vendor's cartEvery line of the price, as the order would be charged: each item before and after its discount, the coupon (and whether the owner or the vendor pays it), the club's discount, the delivery fee (the app's courier prices or the vendor's own), taxes added or included, the wallet's part, and the total. Fails with `invalid_request` (400) for an item or choice the vendor doesn't have, `unavailable_item` (409) for one out of stock (`field` names the line), `vendor_closed` (409) while the vendor is closed, busy or paused (unless `scheduled_for` is within its hours), `below_minimum` (409) under the vendor's smallest order, `quantity_limit` (409) outside its fewest or most items, `outside_delivery_area` (400), `pickup_unavailable` (409), and `coupon_invalid` (409) with the reason in `message`. - POST
/market/ordersSigned inOrder from a vendorPlaces the order at the prices `POST /market/quote` gives for the same cart (the answer's `quote_id` keeps them for 10 minutes). Paid in cash, or fully from the wallet, it goes to the vendor at once (`placed`; a scheduled order at its time). Paid by card, Apple Pay or Google Pay, it waits for the payment (`awaiting_payment`): the answer has `pay_url`, the gateway's payment page (which offers Apple Pay and Google Pay where the gateway does), opened in the app on both platforms (food and goods delivered are services in the physical world, not in-app purchases); it goes to the vendor once the payment is confirmed. Fails as the quote does, with `quote_expired` (409), for a payment method the app doesn't take, and with `payments_unavailable` (503) while the app's payments aren't set up. - GET
/me/market-ordersSigned inThe signed-in customer's ordersNewest first; `active` keeps those not yet delivered, picked up, turned down or cancelled, scheduled ones included. - GET
/me/market-orders/{order_id}Signed inOne of the customer's ordersThe order with its stages, and `tracking` while it is on its way. - POST
/me/market-orders/{order_id}/cancelSigned inCancel an orderUntil the vendor accepts it (a scheduled order until 30 minutes before its time). An online payment is refunded to it, a wallet part to the wallet. `not_cancellable` (409) after that; the customer can open a complaint instead. - POST
/me/market-orders/{order_id}/paySigned inA new payment page for an order waiting for its payment - POST
/me/market-orders/{order_id}/ratingSigned inRate a delivered or picked-up orderOnce, within 7 days. The vendor (and the courier, when the app's courier delivered it), 1 to 5, with words for the vendor. - POST
/me/market-orders/{order_id}/complaintsSigned inOpen a complaint on an orderFrom when the order is placed until 48 hours after it was delivered or picked up (`complaint_closed` (409) after that); one open complaint per order at a time (`complaint_open` (409)). Photos are added next with `POST /me/complaints/{complaint_id}/photos`. - GET
/me/complaintsSigned inThe customer's complaintsNewest first. - GET
/me/complaints/{complaint_id}Signed inA complaint with its messages and decision - POST
/me/complaints/{complaint_id}/messagesSigned inWrite to support on a complaintUntil it is closed (`complaint_closed` (409)). - POST
/me/complaints/{complaint_id}/photosSigned inAdd a photo to a complaintA photo sent with `/market/uploads` (JPEG, PNG or WebP up to 8 MB) joins an open complaint, at most 5 a complaint (`too_many_photos` (409); a closed complaint is `complaint_closed` (409)). Seen by support and, when support shares it, by the vendor. - POST
/market/uploadsSigned inStart sending a complaint photoA photo (JPEG, PNG or WebP) up to 8 MB, by a customer with a complaint still open (`no_complaint` (403) otherwise), into the app's Media library (413 `storage_full` when its storage is full). Sent in chunks to `upload_url` as chat files are, then `/complete`, then added with `/me/complaints/{complaint_id}/photos`. - POST
/market/uploads/{upload_id}/resumeSigned inA fresh link to carry on with a complaint photo - POST
/market/uploads/{upload_id}/completeSigned inFinish sending a complaint photo409 `incomplete` with the offset reached when part of the photo is missing. - GET
/me/walletSigned inThe customer's walletIts balance and history (refunds, credit from support, paid orders). The wallet is in `MarketConfig.currency` and can't be withdrawn as cash. - GET
/market/clubSign-in optionalThe delivery club's plansThe app owner's club plans (monthly or yearly, at the owner's prices) and their benefits; `MarketConfig.club` false when the app has no club (404). - GET
/me/clubSigned inThe customer's club membershipNull `membership` when not a member. - POST
/me/clubSigned inJoin the clubStarts a membership on a plan, waiting for its payment (`pay_url`, card, Apple Pay or Google Pay on the gateway's page); the membership is active once the payment is confirmed. A new period is paid the same way, by joining again once the paid period ends, or by a cancelled member taking the same plan back. `already_member` (409) while a period is paid. - POST
/me/club/cancelSigned inStop the club from renewingThe membership stays until the end of the period it is paid for.
Vendor
Roadmap 115. The vendor app's screens (and the vendor's web panel on Yallapp's site, which serves the same data). A vendor's staff are the people the app's owner or the vendor added by mobile number or email; whoever signs in with it works for that vendor (`User.roles` has `vendor`). Every endpoint here answers `not_vendor` (403) to anyone else and to staff of a vendor the owner paused. Staff see their vendor's orders only, with the customer's first name and the order's lines; a vendor that delivers itself also gets the drop-off address and the customer's notes for the door, never a phone number (its driver asks the app's support, which reaches the customer). Staff accept an order with the minutes it needs or turn it down, pick a grocery order (weighed amounts, substitutes, missing items), mark it ready, hand it over, and, when delivering themselves, mark it on the way and delivered; they set the vendor busy or paused, and items in or out of stock.
- GET
/vendor/meSigned inThe vendor, as its staff see it - PATCH
/vendor/meSigned inSet the vendor busy, paused or taking orders`busy_minutes` stops new orders for that long (scheduled ones still come); `paused` true stops them until set false again. Staff with the `orders` role or more. - GET
/vendor/ordersSigned inThe vendor's orders`status` `new` (waiting for an answer, oldest first), `active` (accepted, not yet handed over or delivered), `scheduled`, or `done` (the last 7 days, newest first). - GET
/vendor/orders/{order_id}Signed inOne of the vendor's orders - POST
/vendor/orders/{order_id}/acceptSigned inAccept an order with the minutes it needs - POST
/vendor/orders/{order_id}/rejectSigned inTurn an order downThe customer is told and refunded in full (to the payment, or the wallet for its wallet part). - PATCH
/vendor/orders/{order_id}/linesSigned inPick a grocery or shop orderFor each line: the amount picked (a weighed amount for items sold by weight, within the customer's agreed limit, `MarketConfig.weight_tolerance_percent`), a substitute (only as the customer allowed, `MarketOrderLine.if_missing`), or missing. The order's total is worked out again (a lower total refunds the difference when it was paid online; it is never charged more than the customer agreed). Accepted orders only. - POST
/vendor/orders/{order_id}/readySigned inThe order is readyFor the app's courier or the vendor's driver to take it, or for the customer to pick up (told with the pick-up code). - POST
/vendor/orders/{order_id}/handoverSigned inHand the order over`to` `driver`: the vendor's own driver took it (`on_the_way`); `delivered`: the vendor's driver delivered it; `customer`: picked up, with the customer's 4-digit `pickup_code` (`wrong_code` (409)). With the app's couriers, the courier's own screens move the order instead (`invalid_request` (400) here). - GET
/vendor/productsSigned inThe vendor's items, for stockDishes or products, paged; `out_of_stock` keeps those out; `q` and `barcode` find them. - PATCH
/vendor/products/{product_id}Signed inPut an item in or out of stock`in_stock`, and for products that count it, `stock` (out of stock at 0 by itself).
Rides
Roadmap 105.2. Ride hailing, on Yallapp's hosted API. The rider sets the pickup and the drop-off, sees each of the company's vehicle types with its fare (`POST /rides/estimate`) and asks for a ride now or, when the company allows it, at a later time (`POST /rides`). The company's drivers get the request one at a time, nearest first, each with a few seconds to accept (`RidesConfig.matching`), or the company gives it to a driver from the dashboard. The rider follows the driver to the pickup and along the ride on the map, can share the ride with a link, call the company's emergency number, cancel (with the company's fee once the free minutes after a driver accepted are over), pay the fare in cash to the driver or by card on the company's payment page, and rate the driver. Fares, fees and the company's share are the company's: Yallapp sets none of them.
- POST
/rides/estimateSigned inThe fare of each vehicle type between two placesBoth places need their coordinates. The distance and time are estimated from the straight line between them (the road is taken as a third longer, at the company's usual speed), so the fare is `upfront` (the one the rider pays, plus waiting) or an estimate when the company charges what the meter says (`RidesConfig.fare_mode`). Vehicle types without an online driver nearby are listed with `available` false (asking for one still waits for a driver). Fails with `outside_service_area` (400) when the pickup is outside the company's areas, with `invalid_request` (400) for a place without coordinates or an unknown promo code (`field` names it), and with `rides_unavailable` (503) while the company has no vehicle type with fares. - POST
/ridesSigned inAsk for a rideAt the fare `POST /rides/estimate` gives for the same places, vehicle type and promo code. Now: it is offered to the drivers at once (`searching`). Later (`scheduled_at`, when `RidesConfig.scheduling` allows it, 30 minutes to 7 days ahead): it waits (`scheduled`) and is offered 15 minutes before. A card ride is paid once it ends, on the company's payment page (`POST /me/rides/{ride_id}/pay`). An unpaid cancellation fee from the rider's last ride is added to this one's fare. Fails as the estimate does; with `ride_in_progress` (409) while the rider has another ride that isn't over (scheduled ones aside); with `invalid_request` (400) for a payment method the company doesn't take or a time it doesn't allow; with `payments_unavailable` (503) for a card ride while the company's payments aren't set up. - GET
/me/ridesSigned inList the signed-in rider's ridesNewest first; `active` only those not yet completed or cancelled (scheduled ones too). - GET
/me/rides/{ride_id}Signed inFollow a rideThe ride with its stages, its driver and car and, from the driver's accepting until the ride ends, where the driver is. The app asks again every 5 seconds or so while the screen is open and the ride is active. - POST
/me/rides/{ride_id}/cancelSigned inCancel a rideBefore it starts (`scheduled`, `searching`, `accepted`, `arrived`); then `not_cancellable` (409). Free while no driver has it, and for `RidesConfig.cancellation.free_minutes` after a driver accepted; after that the company's fee (`cancellation.fee`) is charged, added to the rider's next ride. - POST
/me/rides/{ride_id}/paySigned inPay a card rideFor a card ride `completed` and not paid yet, the company's payment page (good for 60 minutes), opened in the app on both platforms (a service in the physical world). Fails with `not_payable` (409) for any other. - POST
/me/rides/{ride_id}/ratingSigned inRate the driverOnce, after the ride is completed, within 7 days; then `already_rated` or `not_ratable` (409). The rating counts in the driver's average; the comment is seen by the company.
Driver
Roadmap 105.2. The driver's screens. A driver is a person the company added on the dashboard's Drivers page by mobile number (or by email, for apps that sign in by email only), with their car; whoever signs in with that number, proven by its code, is that driver (`User.roles` has `driver`). Every endpoint here answers `not_driver` (403) to anyone else, and to drivers the company suspended. A driver goes online to get ride requests; while online, the app sends the driver's position (only while it is open), which is used to offer rides nearest first and shown to the rider of their ride; it is dropped when they go offline. The rider's number is shown once the driver accepted, until the ride ends. Roadmap 112.3: these screens are in the company's second app made for drivers (build config `role: driver`) only; its riders' app has none. `RidesConfig.driver_app` says where they were before: `same`, in the company's app, shown only to drivers, which apps built before 112.3 still do; `bundle`, in the second app.
- GET
/driver/meSigned inThe signed-in driver - PATCH
/driver/meSigned inGo online or offlineOnly online drivers are offered rides. Going offline drops their position; it is refused with `ride_in_progress` (409) while they have a ride that isn't over. - PUT
/driver/locationSigned inSend the driver's positionSent every 5 to 10 seconds while the app is open and the driver is online (no position is taken in the background). Kept only while they are online; answered 204 and dropped otherwise. At most one every 3 seconds is kept. During a ride, the positions also measure the distance the meter charges (`RidesConfig.fare_mode` `metered`). - GET
/driver/offersSigned inThe ride offered to the driver nowA ride offered to this driver, with when the offer ends (`offer_expires_at`); at most one at a time; empty when none is or the driver is offline. The app asks every 3 seconds or so while online and free, and on a `ride_offer` push. The rider's number is not shown until the driver accepts. - GET
/driver/ridesSigned inThe driver's rides`active`: the driver's ride that isn't over (at most one). `done`: their completed and cancelled rides of the last 30 days, newest first. - GET
/driver/rides/{ride_id}Signed inA rideOne of the driver's rides, or the one offered to them now. - POST
/driver/rides/{ride_id}/acceptSigned inAccept the ride offeredWhile it is offered to this driver; then `offer_expired` (409). The rider is told the driver is on the way. - POST
/driver/rides/{ride_id}/declineSigned inLet the ride go to the next driverThe offer ends at once and isn't made to this driver again. Answered 204 also when it had already ended. - POST
/driver/rides/{ride_id}/statusSigned inMove a ride to its next stageIn order: `arrived` at the pickup, `in_progress` once the rider is in (with the rider's `pin` when `RidesConfig.pin` is true; a wrong one is `wrong_pin`, 400), then `completed` at the drop-off, which works out the final fare (shown on the answer's `fare`); a cash ride is paid once completed: the driver collected `collect`. `cancelled` before the ride starts, with a `reason`: `rider_no_show` (only `RidesConfig.cancellation.no_show_minutes` after arriving; the rider is charged the cancellation fee) or `other` (the ride goes back to the next driver). Fails with `invalid_status` (409) for any other step. - GET
/driver/earningsSigned inWhat the driver madeThe driver's completed rides over today, this week (from Monday) or this month, in `RidesConfig.timezone`, with each day's totals.
Taxi
Roadmap 105.9. A live taxi app (`Config.taxi`): the rides of the Rides and Driver tags, on a full-screen map where the free taxis around the rider and the rider's driver move as they move. The map is the company's choice: Google Maps with the company's own key, or OpenStreetMap from the company's tile server (`TaxiConfig.map`). Place search and routes go through Yallapp (`/maps/places`, `/maps/route`; 105.9's `/taxi/places` and `/taxi/route` are the same) when the company set them up. Roadmap 112.1. Every ride app has `Config.taxi` and these endpoints: ride hailing apps (vertical not `taxi`) too, on the company's map when it set one on Maps & live, else on the platform's map (tiles, and its search and routing servers when set). `tiles_url` is null when there's no map at all: the app keeps the same map-first screens on a plain background. Live positions are in the company's own Firebase Realtime Database (`TaxiConfig.live`). The app signs in to Firebase with the custom token of `POST /taxi/live-token`, whose claims say what it may read and write, under `root` (`yallapp/<app_id>`): - `cells/<cell>/<driver_key>`: `{lat, lng, heading, type, at}`, a free online driver, in the 5-letter geohash cell of their position. Any signed-in person of the app reads them; the driver writes their own (every 2 seconds while online and free; removed when they go offline, take a ride or move to another cell, and on disconnect). `type` is the driver's vehicle type ID, `at` milliseconds since 1970. Riders read the cell they are in and the eight around it and drop drivers whose `at` is over a minute old. - `drivers/<driver_key>`: `{lat, lng, heading, at}`, written by the driver every 2 seconds while online; read by the driver and by the rider whose token follows them (claim `follow`). - `rides/<ride_id>`: `{status, driver_key, at}`, written by Yallapp at each stage of the ride; read by its rider (claim `ride`) and its driver. Without a live database (`live` null), the app asks `GET /taxi/nearby` and `GET /me/rides/{ride_id}` every `nearby_every_seconds`. The driver's app also sends `PUT /driver/location` as in the Driver tag: rides are offered from those positions.
- POST
/taxi/live-tokenSigned inA token to sign in to the live databaseA Firebase custom token for the company's Firebase project (sign in with it, then use `database_url`), valid for an hour. Its claims: `app` (the app's ID), `role` (`rider` or `driver`), `driver_key` (a driver's own), `ride` and `follow` (the rider's ride that isn't over and its driver's key). Ask for a new one when it expires and when the ride changes (a driver accepted it, it ended). `feature_disabled` (403) when the app has no live database. - GET
/taxi/nearbySign-in optionalThe free taxis around a placeThe online drivers with no ride, whose position is under a minute old, within the company's matching distance of the place, nearest first, at most 30. For apps without a live database, and for the first look before the live one answers. Positions are rounded to about 10 metres. - GET
/taxi/placesSign-in optionalPlaces for where toThe same as `GET /maps/places`, kept for the taxi apps built before roadmap 112. Places matching what the rider typed, near them first, from the company's Google Maps (Places) or OpenStreetMap search server (`TaxiConfig.map.search`). A Google place comes without its position: ask `GET /taxi/places/{place_id}` when the rider picks it. `feature_disabled` (403) when the company has no search. - GET
/taxi/places/{place_id}Sign-in optionalA place's position - POST
/taxi/routeSign-in optionalThe route between two placesThe driving route, from the company's Google Maps (Routes) or OpenStreetMap routing server (`TaxiConfig.map.routes`), to draw on the map and to tell how long. `feature_disabled` (403) when the company has none: the app draws a straight line.
Maps
Roadmap 112. Place search and routes on the map an app draws, through Yallapp, for ride, taxi, delivery and food apps: the company's own map (`TaxiConfig.map`, set on Maps & live) or, for apps without one, the platform's (ride hailing apps' `TaxiConfig.map`, `DeliveryConfig.map`, `FoodConfig.map`). `search` and `routes` on those say whether `GET /maps/places` and `POST /maps/route` work; without them the app chooses places by the phone's position, a maps link or on the map, and draws a straight line. Roadmap 112.6. With the platform's MapTiler tiles (or a company's) and no search server, the search is MapTiler's geocoding with the tiles' key, from Yallapp's servers. `GET /maps/address` names the place at the phone's position, for "My location", wherever `search` is true.
- GET
/maps/placesSign-in optionalPlaces by namePlaces matching what the person typed, near them first, on the app's map: the company's Google Maps (Places) or OpenStreetMap search server, or the platform's search server, or 112.6 MapTiler's geocoding with the tiles' key (`search` in `TaxiConfig.map`, `DeliveryConfig.map`, `FoodConfig.map`). A Google place comes without its position: ask `GET /maps/places/{place_id}` when the person picks it. `feature_disabled` (403) when the app's map has no search. - GET
/maps/addressSign-in optionalThe address at a positionRoadmap 112.6. The place at the phone's position, named in the person's language, so "My location" fills the address: the company's Google Maps (Geocoding API), its or the platform's Nominatim server (`/reverse`), or MapTiler's geocoding with the tiles' key. Works wherever `search` is true; `feature_disabled` (403) otherwise, and `not_found` (404) when the map names nothing there. The app keeps its field as it is on any error. - GET
/maps/places/{place_id}Sign-in optionalA place's position - POST
/maps/routeSign-in optionalThe route between two placesThe driving route on the app's map: the company's Google Maps (Routes) or OpenStreetMap routing server, or the platform's routing server (`routes` in `TaxiConfig.map`, `DeliveryConfig.map`, `FoodConfig.map`), to draw on the map and to tell how long. `feature_disabled` (403) when the map has no routes: the app draws a straight line.
Teachers
Roadmap 105.3. Academies with many teachers, on Yallapp's hosted API (`Config.instructors`). Each course may belong to one of the academy's teachers (`CourseSummary.instructor.id` is the teacher's ID; 0 for a course of the academy itself). Students browse the teachers, open a teacher's page with their courses and reviews, follow a teacher (a push when a new course of theirs is published) and rate a teacher once they are a student in one of their courses. When the academy lets people apply (`InstructorsConfig.applications`), a signed-in person can apply to teach and waits for the academy's answer. Teachers' shares are the academy's: Yallapp sets none.
- GET
/teachersSign-in optionalThe academy's teachersActive teachers, the academy's featured ones first, then by rating. `feature_disabled` (403) when the app has no teachers (`Config.instructors` null). - GET
/teachers/{teacher_id}Sign-in optionalA teacher's page - PUT
/teachers/{teacher_id}/followSigned inFollow a teacherThe student gets a push (`new_course`) when a new course of the teacher is published. - DELETE
/teachers/{teacher_id}/followSigned inStop following a teacher - GET
/teachers/{teacher_id}/reviewsSign-in optionalStudents' reviews of a teacherNewest first. A review the academy hid is sent to its author only. - PUT
/teachers/{teacher_id}/reviews/mineSigned inRate a teacherOne review per student and teacher, changed by sending it again. Only a student enrolled in one of the teacher's courses may rate them (`not_student`, 403); a teacher can't rate themselves. - GET
/teachers/applicationSigned inThe signed-in person's application to teach404 when they never applied. - POST
/teachers/applicationSigned inApply to teachOnly when `InstructorsConfig.applications` is true (`feature_disabled`, 403, otherwise). The academy sees the application on its Teachers page and approves or declines it; once approved, the person is a teacher. Sending it again replaces a declined application; a pending or approved one answers `already_applied` (409).
Teacher
Roadmap 105.3. The teacher's screens. A teacher is a person the academy added on the dashboard's Teachers page by mobile number (or by email, for apps that sign in by email only), or whose application it approved; whoever signs in with that number is that teacher (`User.roles` has `teacher`). Every endpoint here answers `not_teacher` (403) to anyone else, and to teachers the academy suspended. A teacher makes courses (title, description, price, level), adds sections and lessons (a video or a PDF sent from the phone, or a video address) and sends a course for review; the academy publishes it or sends it back with a note. Lessons added to a published course are live at once. A teacher answers their students' questions through `POST /questions/{question_id}/answers` (the answer's author has `instructor` true) and sees what their courses sold and their share.
- GET
/teacher/meSigned inThe signed-in teacher - PATCH
/teacher/meSigned inChange the teacher's bio and subjects - GET
/teacher/coursesSigned inThe teacher's coursesEvery course of the teacher, whatever its stage, newest first. - POST
/teacher/coursesSigned inStart a courseThe course starts as a draft, which only the teacher and the academy see. - GET
/teacher/courses/{course_id}Signed inOne of the teacher's courses, with its sections and lessons - PATCH
/teacher/courses/{course_id}Signed inChange a draft or returned courseOnly a `draft` or `returned` course (`not_editable`, 409, otherwise); the academy changes published ones. - POST
/teacher/courses/{course_id}/submitSigned inSend a course for reviewA `draft` or `returned` course with at least one lesson goes to the academy (`in_review`); `no_lessons` (409) without one, `not_editable` (409) at another stage. - POST
/teacher/courses/{course_id}/sectionsSigned inAdd a section to a courseNot while the course is `in_review` (`not_editable`, 409). - POST
/teacher/courses/{course_id}/lessonsSigned inAdd a lessonA lesson in one of the course's sections: a video (`upload_id` of a finished upload of a video, or `video_url`, a YouTube, Vimeo or https video address), a PDF (`upload_id` of a PDF, attached to the lesson) or text only. Uploads go to the academy's Media library and count in its storage. In a published course the lesson is live at once (and students who follow the course are told, like any new lesson). Not while the course is `in_review` (`not_editable`, 409). - DELETE
/teacher/courses/{course_id}/lessons/{lesson_id}Signed inRemove a lessonFrom a `draft` or `returned` course only (`not_editable`, 409); the academy removes lessons of published courses. - POST
/teacher/uploadsSigned inStart sending a lesson's video or PDFVideos (MP4, MOV, WebM) up to 5 GB and PDFs up to 200 MB, into the academy's Media library (413 `storage_full` when its storage is full), named with the teacher's name. The app sends the file in chunks to `upload_url` as for chat uploads, calls `/complete`, then adds the lesson with `upload_id`. A file not used in a lesson stays in the Media library, where the academy can delete it. - POST
/teacher/uploads/{upload_id}/resumeSigned inA fresh link to carry on with an upload - POST
/teacher/uploads/{upload_id}/completeSigned inFinish an upload409 `incomplete` with the offset reached when part of the file is missing. - GET
/teacher/questionsSigned inStudents' questions in the teacher's coursesWaiting for an answer first, then the newest. The teacher answers with `POST /questions/{question_id}/answers`, which marks the question answered. - GET
/teacher/submissionsSigned inAssignments handed in on the teacher's coursesRoadmap 112.5. `submitted` (the default) waits for a grade, the first handed in first; `returned`, `graded` and `all` the newest first. - PUT
/teacher/submissions/{submission_id}/gradeSigned inGrade an assignment, or send it back for another tryRoadmap 112.5. `grade` with a score from 0 to the assignment's highest (and a note), or `return` with a note saying what to change. The student is told in the app (`assignment_graded`). Roadmap 113.7 With a rubric, `rubric` gives the level chosen for each criterion and the score is their points added up. - GET
/teacher/earningsSigned inWhat the teacher's courses sold and the teacher's shareSales paid on the academy's payment page over this month, last month or this year, in `InstructorsConfig.timezone`, less refunds, with the teacher's share (`share_percent`, set by the academy) and each course's totals. The academy pays its teachers itself.
School
Roadmap 105.4. A school's app for its students and their parents, on Yallapp's hosted API. The school adds its students, parents and teachers on the dashboard (by mobile number, or by email for apps that sign in by email only), links each parent to their children, and puts the students in classes. Whoever signs in with a student's number is that student; with a parent's number, that parent (`User.roles` has `student` or `parent`). `GET /school/me` lists whom the person may see: a student themselves, a parent each of their children. Every `/school/students/{student_id}` endpoint answers `not_found` (404) for a student the person may not see, and `not_school_member` (403) to anyone the school didn't add. A student sees their timetable, attendance, homework (and hands it in), grades, upcoming exams and fees; a parent sees the same for each child and pays fees by card through the school's own payment page. Every amount is the school's: Yallapp sets none. 113.8 Everyone sees the school's calendar, the exams' timetable once published and each student's behaviour record. 105.4.7b The store review account (App sign-in's review number or email with its fixed code) is, the first time it signs in, a student of a class of samples, that student's parent and the class's teacher (and a member of staff where the school has staff).
- GET
/school/meSigned inWhom the signed-in person may see at the schoolA student sees themselves; a parent each of their children, in the order the school added them. A teacher who is also a parent sees their children here too. - GET
/school/messagesSigned inMessages with teachers, as the school allows113.6 For every school person (student, parent, teacher): what the school allows (whom teachers write to, who writes first, the hours, files, a class at once, whether the administration reads them) and whom they can write to: a teacher the parents (and students, when allowed) of the classes they teach; a parent the teachers of their children; a student their own teachers. A conversation is opened with `POST /school/messages/conversations` and then works as any Chat conversation (`Config.chat.base_url`). Everyone also has their conversation with the school's administration (`office_conversation_id`). - POST
/school/messages/conversationsSigned inOpen a conversation with a contact113.6 The conversation of the person and one of their contacts (`SchoolContact.person_id`), made the first time; anyone else is 404. Writing in it may still wait for the teacher to write first (`SchoolChatInfo.note`). - GET
/school/students/{student_id}/timetableSigned inThe student's class timetable for the week - GET
/school/students/{student_id}/attendanceSigned inThe student's attendance over a periodDays a teacher took attendance, newest first, with how many of each. The current term (`SchoolConfig.term`) by default, else the last 90 days. - GET
/school/students/{student_id}/homeworkSigned inThe homework of the student's class`due`: not handed in yet, soonest due first; `done`: handed in or past due, newest first. - PUT
/school/students/{student_id}/homework/{homework_id}/submissionSigned inHand in homeworkBy the student only (a parent gets `not_student`, 403): some text, a file (a photo or a PDF sent with `/school/uploads`), or both. It can be handed in again until the teacher marks it; after the due date it is marked late. Refused with `already_marked` (409) once marked. - GET
/school/students/{student_id}/gradesSigned inThe student's scores by subject, and the exams coming up - GET
/school/students/{student_id}/report-cardSigned inThe student's report cardRoadmap 112.5. Over the school's current term (else the year so far), or `from` to `to`: the student's percent in each subject (the share of the marks of the subject's scored exams, quizzes and assignments in the period) with each score, overall, and their attendance. - GET
/school/students/{student_id}/reportsSigned inThe teachers' reports the school shared with the student's parentsRoadmap 113.5. For the student's parents only (`not_parent`, 403, for anyone else, the student too): the reports teachers wrote on the student (their level, behaviour, attendance, what to do next) that the school approved and chose to share, newest first. The parents get a push (`school_report_shared`) when one is shared. - GET
/school/students/{student_id}/feesSigned inThe student's fees, due and paidFor the student's parents (`not_parent`, 403, for anyone else, the student too); at a school in university mode (113.9) the student sees and pays their own as well. `school_fee` pushes go to the same people. - POST
/school/students/{student_id}/fees/{fee_id}/paySigned inPay a fee by card on the school's payment pageWhen the school takes fees by card (`SchoolConfig.fees_by_card`) and has set up its payment gateway; `payments_unavailable` (503) otherwise, and `already_paid` (409) for a fee paid. The app opens `payment_url`; once paid the fee is `paid` with its receipt. For the same people as the fees (`not_parent`, 403, otherwise). - GET
/school/calendarSigned inThe school's calendar for the personRoadmap 113.8. The events, holidays and breaks (no school that day) and exam days from `from` to `to` (this month and the next weeks by default, up to 120 days), with the published exams of the timetable: everyone's, the grade's and the class's of the person's students (a teacher's: of their classes), and the staff's for those who work at the school. With `student_id`, one of the person's students only (a parent's child); `not_found` (404) for anyone else. - GET
/school/students/{student_id}/examsSigned inThe student's exams' timetableRoadmap 113.8. The published exams of the student's class or grade: those coming (soonest first) and the last 50 done (latest first). The student and their parents get a push when exams are published and the day before each (`school_exam`). - GET
/school/students/{student_id}/behaviourSigned inThe student's behaviour recordRoadmap 113.8. Merits and notes, newest first, with the record's points total and counts. Parents get a push (`school_behaviour`) for all of them, only negative ones or none, as the school chooses. - GET
/school/announcementsSigned inThe school's news for the personNewest first. To the whole school, to the class of one of the person's students, or to one of them (and their family). A teacher also sees what they wrote. - POST
/school/uploadsSigned inStart sending a file for homeworkA photo (JPEG, PNG, WebP) up to 20 MB or a PDF up to 200 MB, by a student handing in homework or a teacher giving it, into the school's Media library (413 `storage_full` when its storage is full). Sent in chunks to `upload_url` as chat files are, then `/complete`. - POST
/school/uploads/{upload_id}/resumeSigned inA fresh link to carry on with an upload - POST
/school/uploads/{upload_id}/completeSigned inFinish an upload409 `incomplete` with the offset reached when part of the file is missing. - GET
/school/students/{student_id}/busSigned inThe student's school bus, live while it runsRoadmap 113.10. Each trip (morning, afternoon) the student rides: the route and its bus, the stops in the order the bus reaches them and the student's stop; `run` is today's trip once the driver started it, with the bus's position while it runs (`bus`, recent ones only, drawn on `SchoolConfig.map`) and how soon it reaches the student's stop (`eta_minutes`, `distance_m`: before they get on in the morning, while they're on the bus in the afternoon), and how the driver or the supervisor marked the student. Ask again every 15 seconds or so while the screen is open. Parents are pushed (`school_bus`) when the bus is near the stop and when the child gets on and off. - GET
/school/bus/routesSigned inThe routes the person drives or supervises, with today's tripsRoadmap 113.10. For the school's people who drive or supervise one of its buses (`User.roles` has `bus`); `not_bus_crew` (403) for anyone else. `drives`: the person starts the route's trips and their phone sends the bus's position (the driver; without one, the supervisor). - POST
/school/bus/routes/{route_id}/runsSigned inStart the route's morning or afternoon trip todayRoadmap 113.10. By whoever `drives` the route. A trip started again the same day goes on where it was (200; 201 when it is new); `trip_running` (409) while the bus is on its other trip. While it runs, the driver's phone sends its position (`PUT /school/bus/runs/{run_id}/location`) as long as the trip's screen is open; nothing runs in the background. - GET
/school/bus/runs/{run_id}Signed inA trip as its crew sees it, the stops with their students - PUT
/school/bus/runs/{run_id}/locationSigned inThe bus's position, from the driver's phone while the trip runsRoadmap 113.10. At most every 5 seconds (more often is accepted and left out). The stops the bus comes within about 120 m of are reached; the parents of the stops it is now near (as the school chose: a distance or minutes) are pushed once each. `trip_ended` (409) once it ended. - PUT
/school/bus/runs/{run_id}/riders/{student_id}Signed inMark a student on the trip as on the bus, off it, or absentRoadmap 113.10. By the route's driver or supervisor, for a student of the trip. The parents are pushed (`school_bus`) when the child gets on and off. - POST
/school/bus/runs/{run_id}/endSigned inEnd the tripRoadmap 113.10. By whoever `drives` the route; the bus's position is kept only while a trip runs. A trip left running ends on its own the next day. - GET
/school/library/booksSigned inThe school library's books, searched and by categoryRoadmap 113.11. For everyone the school added. `q` matches the title, the author or the ISBN; `categories` are the library's, to browse by. - GET
/school/library/books/{book_id}Signed inA book, its copies on the shelf, its e-book and the person's holds of itRoadmap 113.11. `file`: the e-book (a PDF) to read in the app, by a link that lasts a few hours. `borrowers`: whom the person may reserve it for (themselves, a parent's children). - POST
/school/library/books/{book_id}/holdSigned inReserve the bookRoadmap 113.11. Held now when a copy is free (`ready`, until `expires_at`: the school's days), else `waiting` until one comes back, in the order people reserved; they're pushed (`school_library`) when it is ready. `person_id`: whom it is for, when the person borrows for more than one (a parent's children). `already_held`, `on_loan_to_you`, `too_many_holds` and `no_copies` (409). - DELETE
/school/library/holds/{hold_id}Signed inCancel a hold; its copy goes to the next one waiting - GET
/school/library/loansSigned inThe books out now, borrowed before and reservedRoadmap 113.11. Of whoever the person follows: themselves (a student, a teacher), a parent's children; one of them with `person_id`. Books are lent and returned at the library's desk; the student and their parents are pushed (`school_library`) before the due date and when a book is overdue.
Classroom
Roadmap 105.4. The school's teachers' screens. A teacher is a person the school added as a teacher on the dashboard; whoever signs in with their number is that teacher (`User.roles` has `teacher`). Every endpoint here answers `not_teacher` (403) to anyone else. A teacher sees the classes and subjects they teach and their timetable, takes attendance in a tap (the parents of a student marked absent or late are told), gives homework and marks what was handed in, enters the scores of exams and quizzes, and writes to a class (its students and their parents).
- GET
/classroom/meSigned inThe signed-in teacher, their classes and today's lessons - GET
/classroom/timetableSigned inThe teacher's lessons for the week - GET
/classroom/classes/{class_id}/studentsSigned inThe students of a class the teacher teaches - GET
/classroom/classes/{class_id}/gradebookSigned inA class's gradebookRoadmap 112.5. For the class's teachers, over the current term (else the year so far) or `from` to `to`: each student's percent in each subject taught, overall and attendance. - GET
/classroom/classes/{class_id}/syllabusSigned inThe class's progress through the syllabusRoadmap 113.5. The syllabus of each subject the teacher teaches in the class, for the class's grade (as the school wrote it: units, and their lessons or topics with the days the plan gives each), with the topics the class covered and where the plan says it should be today (a topic is due once its last day has passed; `behind` when fewer are covered than are due). - PUT
/classroom/classes/{class_id}/syllabus/{topic_id}Signed inMark a topic coveredRoadmap 113.5. On a day (today by default, up to a year back) with an optional note; marking it again changes the day and the note. Of a subject the teacher teaches in the class. - DELETE
/classroom/classes/{class_id}/syllabus/{topic_id}Signed inMark a topic not covered - GET
/classroom/classes/{class_id}/attendanceSigned inA class's attendance on a dayEvery student of the class, with their mark that day (null when not taken). Today in the school's time zone by default. - PUT
/classroom/classes/{class_id}/attendanceSigned inTake a class's attendanceFor today or a day in the last 7 (`date_out_of_range`, 400, else). Students left out keep their mark. When the school tells families (`SchoolConfig.absence_alerts`), the parents of a student newly marked absent or late get a push (`school_absence`). - GET
/classroom/homeworkSigned inThe homework the teacher gaveNewest first, of every class they teach or one (`class_id`). - POST
/classroom/homeworkSigned inGive homework to a classFor a subject the teacher teaches in that class (`not_your_class`, 403, else). The class's students and their parents get a push (`school_homework`). - GET
/classroom/homework/{homework_id}Signed inHomework with what each student handed in - PUT
/classroom/homework/{homework_id}/submissions/{student_id}Signed inMark a student's homeworkAlso when nothing was handed in. The student and their parents get a push (`school_grade`). - GET
/classroom/assessmentsSigned inThe exams, quizzes and assignments the teacher setComing up first (soonest first), then past ones (newest first), of every class they teach or one (`class_id`). - POST
/classroom/assessmentsSigned inSet an exam, a quiz or an assignment for a classFor a subject the teacher teaches in that class. It shows to the class's students and parents among the exams coming up. - GET
/classroom/assessments/{assessment_id}/scoresSigned inEvery student of the class with their score - PUT
/classroom/assessments/{assessment_id}/scoresSigned inEnter scoresFrom 0 to the assessment's `max_score`; null clears one. Students left out keep theirs. A student given a score for the first time, and their parents, get a push (`school_grade`). - POST
/classroom/classes/{class_id}/messagesSigned inWrite to a whole class at once113.6 When the school allows it (`SchoolMessages.class_messages`): the same text in each one-to-one conversation of the teacher with the class's parents, students or both (`families`, as the school allows), so each answer comes back to the teacher alone; each of them gets a push. People who never signed in to the app can't be written to yet (`not_in_app`). Within the school's hours (409 `outside_hours`). The same `client_id` again sends nothing twice. - GET
/classroom/examsSigned inThe exams of the teacher's classesRoadmap 113.8. The published exams of the teacher's classes (or of `class_id`), coming and done, as students see them. - GET
/classroom/behaviour/categoriesSigned inThe school's behaviour categoriesRoadmap 113.8. The categories the school uses, positive (points of 0 or more) and negative (0 or less), in the school's order. - POST
/classroom/behaviourSigned inWrite a merit or a noteRoadmap 113.8. On a student of one of the teacher's classes (`not_your_class`, 403, else), of one of the school's categories with its points. The student's parents get a push (`school_behaviour`) as the school chooses. - DELETE
/classroom/behaviour/{note_id}Signed inTake back a merit or a noteThe teacher's own, within a day of writing it (`too_late`, 409, after). - GET
/classroom/classes/{class_id}/behaviourSigned inA class's behaviourRoadmap 113.8. Each student's points and counts, and the class's latest 50 merits and notes (one student's with `student_id`). - POST
/classroom/announcementsSigned inWrite to a classTo a class the teacher teaches; its students and their parents see it with the school's news and get a push (`school_announcement`). - GET
/classroom/report-templatesSigned inThe school's report templatesRoadmap 113.5. The templates the school wrote for teachers' reports on a student or on a class, by kind and name. - GET
/classroom/reportsSigned inThe teacher's reportsRoadmap 113.5. The teacher's own reports to the school, the last changed first: drafts, sent, sent back for changes and approved; a student's (their history) or a class's. - POST
/classroom/reportsSigned inStart a report (a draft)Roadmap 113.5. On one of the school's templates: a student template needs `student_id` (a student of a class the teacher teaches), a class template `class_id` (a class they teach; the period is the school's term, else the year so far, when not given). Answers are checked against the template's fields (text up to 2000 characters, one of a choice's choices, a rating from 1 to 5); required ones are needed only to send it. - GET
/classroom/reports/{report_id}Signed inOne of the teacher's reports, with the school's comments - PUT
/classroom/reports/{report_id}Signed inChange a draft, or a report sent backRoadmap 113.5. A report with the school (sent or approved) can't be changed (`report_sent`, 409). - DELETE
/classroom/reports/{report_id}Signed inDelete a draft - POST
/classroom/reports/{report_id}/sendSigned inSend a report to the schoolRoadmap 113.5. A draft, or a report the school sent back, with every required field filled in (`incomplete_report`, 400, else) and an optional note; the school's administration hears of it on Yallapp Business. The teacher gets a push (`school_report`) when the school comments on it, sends it back for changes or approves it.
Check-in
Roadmap 113.2. Students, teachers and staff check in and out at the school from the app: with the gate's code (a QR code and its six digits on the school's Gate screen, new every 30 seconds and signed by the platform, so an old or made-up code fails; nothing secret is in the app), inside the school's area (the phone's `lat` and `lng` against the school's place and radius), either one, or both at once, as the school chooses (`methods`). Checking in after the school's `late_from` is late; checking out before `early_before` is leaving early. A teacher checks in as a teacher, a member of staff as staff, a student as a student (with the school's student times); a parent doesn't check in (`not_checkin_member`) and follows each child's days with `GET /school/students/{student_id}/checkins`, and is pushed (`school_checkin`) when the school wants. The school's office corrects a day in the dashboard with a reason, kept on the day (`corrections`). The teacher's class register (`/classroom/classes/{class_id}/attendance`) stays apart.
- GET
/school/students/{student_id}/checkinsSigned inA student's month at the gate, for them and their parents - GET
/school/checkinSigned inToday at the gate for the signed-in student, teacher or member of staffWhether the school takes check-ins, how, its place and radius (for the location method), the person's times and today's record. - POST
/school/checkinSigned inCheck in or out`action` `in` or `out`, with what the school's `methods` ask: the gate's `code` (the six digits typed, or what its QR code holds), the phone's `lat` and `lng`, or both. Errors: `checkin_off` (403, the school doesn't take check-ins), `outside_area` (403, with `details.distance_m`), `code_needed`, `invalid_code` (an old or another school's code) and `location_needed` (400), `not_school_day`, `too_early` (check-in opens two hours before the day starts), `already_in`, `not_in` and `already_out` (409). A student's parents get `school_checkin`. - GET
/school/checkin/historySigned inThe signed-in person's month at the gate
Staff
Roadmap 113.3. Everyone who works at the school, teachers and staff (`User.roles` has `teacher` or `staff`); every endpoint here answers `not_staff` (403) to anyone else. Their job, department, working days and hours and duties as the school set them, their leave requests (sick, annual or other, with a note and a photo or PDF of a document sent with `/school/uploads`), approved or refused by the school in the dashboard with a note (`school_leave`), and the staff's own news, which students and parents never see (`school_staff`). Roadmap 113.12. Their own payslips once the school made them final (the base, allowances, bonuses, deductions, advances taken back, the net and the payments the school wrote down) with each one's PDF from a signed link that lasts 15 minutes, and their advances still being taken back. Salaries are recorded by hand: nothing here moves money, and no one sees another person's payslips.
- GET
/staff/meSigned inThe signed-in teacher or member of staff at workTheir job, department, working days and hours (else the school's times for teachers and staff), duties, and a teacher's lessons in the week. - GET
/staff/leaveSigned inThe person's leave requestsNewest first. - POST
/staff/leaveSigned inAsk for leaveFrom up to 30 days back to a year ahead, up to 90 days at a time; `leave_overlaps` (409) when a waiting or approved request has some of the days. A document is a photo or a PDF sent with `/school/uploads`. - DELETE
/staff/leave/{leave_id}Signed inWithdraw a leave request the school hasn't answered - GET
/staff/announcementsSigned inThe staff's own newsNewest first; for teachers and staff only. - GET
/staff/payslipsSigned inThe person's own payslipsRoadmap 113.12. Final payslips only (a draft the school is still writing is never shown), the newest month first; `advances` are the person's advances still being taken back. `pdf_url` is a signed link to the payslip's PDF in the app's language, good for 15 minutes: ask again for a new one. - GET
/staff/payslips/{payslip_id}Signed inOne of the person's payslips, with a new PDF link
Parent
Roadmap 113.4. The parent's area: an app's Parent tab, for the school's parents only (`User.roles` and `SchoolMe.roles` have `parent`); every endpoint here answers `not_parent` (403) to anyone else, a student, a teacher or a member of staff without children at the school, and `not_found` (404) for a child who isn't theirs. One account for all their children (`GET /parent/me`), each child's day at a glance (checked in and out, today's lessons, homework due, the latest grades, behaviour, the bus, library books, the reports the school shared, fees, slips to answer, an excuse, who picks the child up and the next days on the calendar), the permission slips the school sends a class, a grade or the whole school (signed yes or no with the parent's name by the due date; `school_slip` when one comes and the day before it is due), excuses for an absence (a day or several, a reason and a photo or PDF sent with `/school/uploads`; accepted or declined by the school, `school_excuse`; an accepted one makes those days `excused` in the attendance and at the gate), and who may pick the child up (name, relation, phone and a photo sent with `/school/uploads`) with a one-day note ("today X picks up") the school sees on the day's board. Fees, the reports, the calendar and messages stay on their School endpoints.
- GET
/parent/meSigned inThe signed-in parent and their childrenTheir children in the order the school added them (never the parent themselves), the slips each has to answer, and whether the school lets parents write to their children's teachers (113.6). - GET
/parent/children/{student_id}/daySigned inA child's day at a glance - GET
/parent/slipsSigned inThe permission slips for the parent's childrenOpen ones first (the soonest due first), then those closed in the last 60 days; each with the parent's children it is for and their answers. `student_id` for one child's. - POST
/parent/slips/{slip_id}/answersSigned inSign a slip for a child, yes or noWith the parent's name. Answering again replaces the answer, until the end of the due date (`slip_closed`, 409, after it). - GET
/parent/children/{student_id}/excusesSigned inThe excuses sent for a childNewest first, up to 50. - POST
/parent/children/{student_id}/excusesSigned inExcuse a child's absenceFor a day or several, from 30 days back to 60 days ahead, up to 60 days at a time; `excuse_overlaps` (409) when a waiting or accepted excuse has some of the days. A photo or PDF is sent with `/school/uploads`. The school's administration is told on Yallapp Business. - DELETE
/parent/excuses/{excuse_id}Signed inWithdraw an excuse the school hasn't answered`already_answered` (409) once the school accepted or declined it. - GET
/parent/children/{student_id}/pickupSigned inWho may pick a child upThe list the parents keep (any of the child's parents changes it; the school adds to it in the dashboard), today's one-day note and the next days'. - PUT
/parent/children/{student_id}/pickupSigned inChange who may pick a child upThe whole list, up to 10 people; it replaces the one before. A photo is an image sent with `/school/uploads` (`photo_id` is its id); a photo already on the list stays when its `photo_id` is sent again. - PUT
/parent/children/{student_id}/pickup/noteSigned inSay who picks the child up on a day"Today X picks up": for today (when `date` is left out) or one of the next 30 days, one per day (sending again replaces it); the school sees it on that day's board. - DELETE
/parent/children/{student_id}/pickup/noteSigned inTake back a day's pick-up note
University
Roadmap 113.9. Schools in university mode (`SchoolConfig.university`): faculties, departments and programs; each term's sections of the courses with their lecturer, seats and weekly lectures. A student registers for the term in the app while its registration is open (seats left, lectures that don't clash, prerequisites passed, the program's credits per term; a full section takes a waiting list when the university has one), sends the registration to their advisor (a lecturer), who approves it or sends it back; a section is dropped until the term's drop date. Lecturers take each lecture's attendance, give the grades of the university's scale and publish a section's results; the student sees each term's GPA, the cumulative GPA (credit-weighted, the latest attempt of a course counting), the transcript (also as a PDF) and their program's study plan. A parent sees their child's part, read only. Endpoints answer `not_university` (404) when the school isn't a university.
- GET
/school/students/{student_id}/universitySigned inThe student's program, advisor, GPA and this term's registration - GET
/school/students/{student_id}/university/registrationSigned inThe student's registration for a term, and the sections offeredThe sections the student holds (asked for, enrolled or on a waiting list) and the term's other sections, each with its seats left and whether it can be added (`problems` says why not). - POST
/school/students/{student_id}/university/registration/sectionsSigned inAdd a section (the student only)While the term's registration is open and the registration isn't with the advisor. The section waits for the advisor (`pending`, its seat held), or joins the waiting list when it is full and the university has one (`waitlisted`). `not_allowed_clash`, `not_allowed_prerequisites`, `not_allowed_credits`, `not_allowed_full`, `not_allowed_taken`, `registration_closed`, `waiting_for_advisor` and `already_added` answer 409. - DELETE
/school/students/{student_id}/university/registration/sections/{section_id}Signed inDrop a section (the student only)Until the term's drop date (`drop_closed`, 409, after it). The first on its waiting list who fits takes the seat and is told (`uni_seat`). - POST
/school/students/{student_id}/university/registration/submitSigned inSend the registration to the advisor (the student only)With a section waiting for approval and at least the program's credits for a term (`credits_low`, 409). The advisor is told (`uni_advisee`). - GET
/school/students/{student_id}/university/timetableSigned inThe student's week of lectures in a term - GET
/school/students/{student_id}/university/resultsSigned inResults by term, each term's GPA and the cumulative GPAOnly the grades of sections whose results are published. - GET
/school/students/{student_id}/university/transcriptSigned inThe student's transcript, with its PDFThe results with the student and their program; `pdf_url` is the PDF's address, working for an hour. - GET
/school/students/{student_id}/university/planSigned inThe program's study plan, done, in progress and left - GET
/classroom/universitySigned inA lecturer's sections in a term, and their advisees waiting - GET
/classroom/university/sections/{section_id}Signed inOne of the lecturer's sections, its students and their grades - GET
/classroom/university/sections/{section_id}/attendanceSigned inA lecture's attendance (today by default) - PUT
/classroom/university/sections/{section_id}/attendanceSigned inSave a lecture's attendanceFor today and the 14 days before (`date_out_of_range`, 400). - PUT
/classroom/university/sections/{section_id}/gradesSigned inGive the section's gradesA letter of the university's scale, or a percent (`mark`) the scale's letter comes from; null clears it. For the section's enrolled students. - POST
/classroom/university/sections/{section_id}/publishSigned inPublish the section's resultsIts graded students (and their parents) see their grade and are told (`uni_results`). `nothing_graded` (409) without any grade. - GET
/classroom/university/adviseesSigned inThe students the lecturer advises, and their registration for a termThose waiting for an answer first. - GET
/classroom/university/advisees/{student_id}Signed inAn advisee's registration for a term - POST
/classroom/university/advisees/{student_id}/approveSigned inApprove an advisee's registrationIts sections waiting are enrolled; the student (and their parents) are told (`uni_registration`). `not_submitted` (409) when nothing waits. - POST
/classroom/university/advisees/{student_id}/returnSigned inSend an advisee's registration back, with a note
Bookings
Services, staff, free times and appointments, for Bookings apps. The plugin reads them from the site's booking plugin (Amelia, BookingPress or LatePoint); the booking plugin's own rules decide the free times and whether a new appointment is approved or waits for the business. Appointments are paid at the business.
- GET
/bookings/homeSign-in optionalGet the bookings home screen sectionsSections in the order chosen in the plugin settings. Only sections with content are returned. - GET
/bookings/categoriesNo sign-inList service categoriesCategories with services customers can book, in the booking plugin's order. - GET
/bookings/servicesNo sign-inList servicesServices customers can book online, in the booking plugin's order. - GET
/bookings/services/{service_id}No sign-inGet a service and the staff who provide it - GET
/bookings/availabilitySign-in optionalFree times for a serviceFree start times from `from` for `days` days, by the business's local date, as the booking plugin computes them (working hours, days off, breaks, existing appointments, buffers and how far ahead customers may book). Without `staff`, a time is free when any staff member who provides the service is free; `staff_ids` says who. - POST
/bookings/appointmentsSigned inBook an appointmentBooks a free time for the signed-in customer, through the booking plugin, so its emails, calendar sync and approval rules apply. The appointment is `approved` or `pending` as the plugin decides. Fails with `slot_taken` (409) when the time is no longer free and with `invalid_request` (400) for a service, staff member or time that can't be booked. When `bookings.intake_fields` lists questions (a clinic's intake form), `answers` holds the customer's answers by field ID. Fails with `invalid_answers` (400) when a required answer is missing or an answer doesn't fit its field; `details.fields` then maps field IDs to messages safe to show. Answers are health data: only the customer (in their appointments) and the business (in its dashboard) see them, and no push, email or text message includes them. - GET
/me/appointmentsSigned inList the customer's appointmentsUpcoming ones soonest first, past ones (and cancelled ones) newest first. - GET
/me/appointments/{appointment_id}Signed inGet one of the customer's appointments - POST
/me/appointments/{appointment_id}/cancelSigned inCancel one of the customer's appointmentsFails with `too_late` (409) when it starts sooner than `bookings.cancel_before_minutes` allows, or when the business doesn't let customers cancel.
Classes
Group classes and memberships, for gyms, studios and coaches (Bookings apps; the hosted API only for now). Classes come from the business's weekly timetable; each has a capacity, and a spot is booked only while one is left. When `bookings.classes.membership_required` is true, booking needs an active membership, and a plan with credits uses one per class (given back when the customer cancels in time). Memberships are paid on a web page outside the app, on Android and iOS alike: they are for a service used in person (App Store Review Guideline 3.1.3(e)).
- GET
/bookings/classesSign-in optionalList classes on the timetableClasses from `from` to `to` (both included, the business's local dates), soonest first, also full and cancelled ones. Signed-in customers get `booked` for their own spots. Answers `not_found` (404) when the app has no classes. - POST
/bookings/classes/{class_id}/bookSigned inBook a spot in a classBooks one spot for the signed-in customer, only while one is left (no overbooking, also when many book at once). Booking a class already booked answers it again. Fails with `class_full` (409), `too_late` (409) once it has started or is closer than `min_minutes_ahead`, `class_cancelled` (409), `membership_required` (403) without an active membership when the business asks for one, `membership_frozen` (403, roadmap 64) when the active memberships are frozen on the class's day, and `no_credits` (403) when the membership's credits are used up. With a credit plan, one credit is used. - POST
/bookings/classes/{class_id}/cancelSigned inCancel the customer's spot in a classGives the spot back. A credit used for it comes back too. Fails with `too_late` (409) when the class starts sooner than `bookings.classes.cancel_before_minutes` allows, or when customers can't cancel in the app, and with `not_found` (404) when the customer has no spot in it. - GET
/memberships/plansSign-in optionalList the membership plans on salePlans in the business's order. Empty when it sells none in the app. - POST
/memberships/checkout-linkSigned inGet a payment page link for a membershipOpens the business's payment page for one plan; paying starts the membership at once. The page's last address has `order-received` in it. Offered on Android and iOS: a membership is for a service used in person, not digital content. Fails with `feature_disabled` (403) when the business can't take card payments, and with `not_found` (404) for a plan not on sale. - GET
/me/membershipsSigned inList the customer's membershipsActive ones first (the one that ends first), then ended ones, newest first.
Platform
Calls from the Yallapp platform to a client's WordPress site (roadmap 31), never from the app. Only the Yallapp plugin serves them; the hosted API and a client's own API don't.
- POST
/chat/notifyNo sign-inPush a new chat message to the site's app usersRoadmap 77. Sent by the Yallapp platform to a WordPress site when a message arrives for its app's people: the site keeps their push tokens, so its plugin sends the push (Firebase or Huawei Push Kit), with the notification preferences the person chose. Nothing about the message is stored on the site. Signed like `POST /license/refresh`: `signature` is the lowercase hex HMAC-SHA256 of `yallapp-chat-notify:<timestamp>:<sha256 of the notification JSON, hex>` keyed with the license key in capitals; a wrong signature, a site without a license key or a timestamp more than 5 minutes away is 401 `invalid_signature`. - POST
/campaigns/sendNo sign-inSend a campaign from the Yallapp dashboard to the app's usersRoadmap 85.4. Sent by the Yallapp platform to a WordPress site when the owner sends a campaign from Dashboard › App notifications › Campaigns (now, at the time chosen, or each week): the site keeps its people's push tokens, so the plugin adds the notification to everyone's inbox (with its picture and what it opens) and pushes it to the phones that allow announcements, in batches. When the last batch is sent, the plugin reports how many phones it reached and missed with `POST /campaigns/report` (docs/api/platform.yaml). Signed like `POST /chat/notify`: `signature` is the lowercase hex HMAC-SHA256 of `yallapp-campaign:<timestamp>:<sha256 of the campaign JSON, hex>` keyed with the license key in capitals; a wrong signature, a site without a license key or a timestamp more than 5 minutes away is 401 `invalid_signature`. The same `send_id` twice is sent once (200 with the first answer). - POST
/billing/accessNo sign-inGive or take access bought through a storeRoadmap 100. Sent by the Yallapp platform to the WordPress site (plugin 0.4.23 and later) once it has checked a store purchase, and again when Google or Apple says it was refunded, revoked, renewed or ended. The plugin gives the access (`grant`): enrols the user in the course (the site's LMS), or gives the membership until `expires_at` (Paid Memberships Pro level, LearnDash group, or the site's WooCommerce Subscriptions product), and records a WooCommerce order marked paid by the store when WooCommerce is active; or takes it back (`revoke`). `grant_id` is the same on every try of one change: a repeat answers as the first time did. The request is signed like `POST /license/refresh`: the headers `Yallapp-Timestamp` (Unix seconds) and `Yallapp-Signature`, the lowercase hex HMAC-SHA256 of `yallapp-billing-access:<timestamp>:<the raw body>`, keyed with the license key in capitals. A wrong signature, a site without a license key or a timestamp more than 5 minutes away is refused (`invalid_signature`, 401). Only sent to the https address of the site the license is active on; tried again after 1, 5 and 30 minutes and 2, 6 and 24 hours when the site doesn't answer 2xx. - POST
/license/refreshNo sign-inCheck the license nowSent by the Yallapp platform, a few seconds after the app's owner saves something the plugin reads from its license status (the app type, how people sign in, the plan) or publishes the app's pages. The plugin then checks its license with the platform and fetches the published pages, as it does on its own twice a day and every 15 minutes. It trusts nothing in this request: it only learns that it should ask now. The request is signed with the site's license key, which both sides know: `signature` is the lowercase hex HMAC-SHA256 of `yallapp-license-refresh:<timestamp>` keyed with the license key in capitals (`YALL-XXXX-XXXX-XXXX`). The plugin refuses a wrong signature, a site without a license key and a timestamp more than 5 minutes away from its clock (`invalid_signature`, 401). It checks at most once every 30 seconds: a nudge within 30 seconds of the last one gets 429 and the plugin checks once when the 30 seconds are over, so the last change still arrives. Reachable during maintenance and while the app API is paused, so a renewed or reactivated plan reaches the site.
Links
Links that open the app (roadmap 46): Android App Links and iOS Universal Links. A shared course, product, service, listing, article, event, campaign or page is a link `<base>/<kind>/<id>`. It opens that screen in the app when the app is installed, and the website otherwise. - On Yallapp's link domain, for every app: `<base>` is `https://yallapp.com.tr/a/<app_id>`. Without the app, that address shows a page in the app's branding with its store links; for a WordPress app it sends the visitor on to the site's own link. - On a WordPress site with the Yallapp plugin: `<base>` is `<site>/a` (`Config.links.share_base_url`). Without the app, the plugin sends the visitor to the item's own page on the site (its permalink), or to the home page when the item has none or isn't public. | Kind | Link | Opens in the app | `id` | |---|---|---|---| | `course` | `<base>/course/12` | the course (Academy) | a number | | `product` | `<base>/product/12` | the product (Store) | a number | | `service` | `<base>/service/12` | the service (Bookings) | a number | | `listing` | `<base>/listing/12` | the listing (Real estate) | a number | | `article` | `<base>/article/431` | the article (News & blog) | letters, digits, `-` and `_` | | `event` | `<base>/event/12` | the event (Events & tickets) | a number | | `campaign` | `<base>/campaign/12` | the campaign (Charity & donations) | a number | | `page` | `<base>/page/offers` | a page built in the dashboard | its slug | The same table, with examples, is `docs/api/app-links.json`; the app, the platform and the plugin follow it. A link of a kind the app type doesn't have, or that doesn't match, opens the app's home. The phone checks that the domain trusts the app with two files the domain serves at its root, as JSON (`application/json`), without a redirect: `/.well-known/assetlinks.json` (Android: the package name and the SHA-256 fingerprints of the Play App Signing key and the upload key) and `/.well-known/apple-app-site-association` (iOS: `<Team ID>.<bundle ID>` with the paths `/a/*`, or `/a/<app_id>/*` on Yallapp's domain). The plugin builds them from its license status (`LicenseStatus.app_links` in `docs/api/platform.yaml`). Push notifications and notifications in the inbox may carry a `link` in the same format. **The site's own pages** (roadmap 48). On a WordPress site, the plugin also lists in `apple-app-site-association` the paths of the site's own pages for what the app shows: courses and lessons, products and product categories, posts, and the pages with a booking plugin's booking form, as the site's permalinks make them. It leaves out, before them, what must stay on the website: `wp-admin`, `wp-login.php`, the REST API, feeds, `xmlrpc.php`, `wp-content` and `wp-includes`, the cart, checkout, account and payment pages of WooCommerce and the course plugins, and addresses with `add-to-cart`, `wc-api`, `rest_route` or `preview` in their query. Android's App Links have no exclusions, and the app's intent filters are fixed when it's built: the plugin reports the permalink bases that start with a fixed folder (`/courses/`, `/product/`, `/product-category/`, `/blog/`) with its license check (`SiteInfo.link_paths`), and the next build lists them. Posts whose permalinks start at the root of the site (`/%postname%/`) open the app on iPhone only. The app asks `GET /links/resolve` what a page of the site is. When the answer is an item it shows, it opens that screen; otherwise (`kind` null, an error, an older plugin) it opens the address in its own in-app browser, which never hands the address back to the app, so a link never loops. A site in a folder (`example.com/learn`) can't serve the two files at its domain's root: the plugin serves them at `<site>/.well-known/…` too, for the domain's own server to copy, and until it does only Yallapp's links open the app.
- GET
/links/resolveNo sign-inWhat a page of the site is in the appRoadmap 48. The app asks what an address on the site that opened it is: a course, a lesson, a product, a product category, a post, or a page with a booking form. `kind` is null when the address is something else (the home page, a page, the cart, an account page, another site): the app then opens it in its in-app browser. Only public, published items are named. Served by the Yallapp plugin; the hosted API answers 404 (its links are always `<base>/<kind>/<id>`), and so do plugins released before this endpoint: the app then opens the in-app browser too.
Crash reports
Crash reports (roadmap 46). Served by Yallapp for every app, at `https://yallapp.com.tr/api/app/{app_id}`, also when the app's content comes from WordPress or an own API: the app sends its crashes to Yallapp, never to the site. Off when `Config.app.crash_reports` is false.
- POST
/crashesNo sign-inSend crash reportsThe app sends what went wrong (an uncaught error in the app's code), at most 10 reports per call, each one at most once per app run, with the sessions it counted since it last sent. Reports that couldn't be sent are kept on the phone (at most 10) and sent on the next start. A report carries no personal data: no names, phone numbers, emails, tokens, codes or addresses with query strings. The app removes them before sending, and Yallapp removes what still looks like one before it stores the report. Yallapp groups reports by `fingerprint` (the error's type and its first frames in the app's own code), counting them, the first and last time seen and the app versions. Rate limited per app and per address (`rate_limited`, 429 with `retry_after_seconds`). Answers 204 also when crash reports are off for the app, so the app doesn't keep them.