docs
/
Courses — AppEngine

AppEngine Data Synchronization and Scheduled Jobs

Watch on YouTube ↗

Video chapters
  1. AppEngine Scheduling and Sync APIs
  2. Schedule, queued job and observed result
  3. Prepare staff access and a working scheduler
  4. Create an isolated target record
  5. Configure the start and end actions
  6. Inspect the actual queued work
  7. Verify both boundaries and the final target
  8. Stop, change the time and start again
  9. Run now and cancel one queued entry
  10. Repeat with a timezone and an end boundary
  11. Inspect schedules and target records in Studio
  12. Discover the platform’s registered jobs
  13. Configure jobs and understand run controls
  14. Connect a provider before claiming synchronization
  15. Diagnose the right layer
  16. Scheduled work you can verify

Written guide

Companion source code

Get the course source code on GitHub. The folder includes the supplied examples and setup instructions. Keep credentials in your own environment; use the maintained API reference when adapting recorded request examples.

Build timed actions, inspect queued work, verify the changed record, and distinguish application schedules from registered platform jobs and provider synchronization.

This course includes a real two-boundary schedule, stop/reschedule/start controls, an immediate run, exact queue-entry cancellation, and a bounded repeating schedule. All use isolated internal training records. Platform-job settings, cancellation/retry and external provider triggering are documented contract examples; the recording does not execute organization-wide sync changes or claim an external import.

Video review · Complete request examples · Download observed JSON and examples · AppEngine course catalogue

1. What AppEngine schedules

A schedule record stores the instruction: action, target and time. A queued job represents an execution waiting for its time. A worker consumes the job and performs the action. The target record is a separate object whose state must be read to establish the result.

A simple schedule can have a start action and a later end action. They become separate queue entries. A cron schedule repeats an action, optionally bounded by an end date. Registered platform jobs are different: their implementations and timing kinds are supplied by AppEngine, and organization settings select permitted configuration. Provider sync adds a connected external account and provider-specific permissions to that machinery.

Our target changes waiting → ready → finished. Stopping after the first action prevents future work but does not change ready back to waiting. In the uninterrupted path, the later end action sets finished. These are alternative paths, not a claim that a stopped job still executes.

Normal two-boundary execution: end job reaches the worker and updates the marker to finished

Concept illustration. The timer represents progression; the actual UTC instants are shown in the recorded API responses.

2. Prerequisites: identity, Redis and worker configuration

Open Appmint Studio and select an organization you may use for development. Obtain the organization's AppEngine address, organization ID and authorized staff session using Database and Repository APIs, chapter 2. The written Database course supplies the staff-sign-in request and repository prerequisites. Customer sign-in is a different identity path; do not substitute a customer token for these staff administration operations.

Staff sign-in uses POST /profile/user/signin, an orgid header and a JSON body with email and password. Keep the returned token outside shared screenshots and examples. Subsequent calls use orgid: YOUR_ORGANIZATION and Authorization: Bearer YOUR_STAFF_TOKEN. Use Content-Type: application/json for JSON bodies. base in the examples is your assigned AppEngine endpoint, not the Studio website address.

Worker prerequisites for a deployment you operate

Saving a schedule needs the API and database. Executing it additionally needs Redis-backed queues and a process running the schedule consumer. Current AppEngine source conditionally registers the consumers when ENABLE_SYNC is the exact string true. The deployment operator must provide the application's existing database/organization configuration and the matching Redis connection to the worker process. The relevant Redis settings include REDIS_HOST, REDIS_PORT (default 6379), and credentials when the Redis service requires them: REDIS_USERNAME and REDIS_PASSWORD. These are server environment settings, never browser request headers or values embedded in a customer application.

Use your deployment's existing process/service configuration to run its worker-enabled AppEngine instance. Do not start a second broad scheduler, restart a shared service, or change shared queue state merely to reproduce this example. A hosted user should ask the deployment operator about a missing worker rather than placing these settings in a web client. This lesson does not supply a one-size-fits-all deployment command or expose server credentials.

Inspect GET /sync. Our actual response reports:

{"jobs":{"ENABLE_SYNC":"true"},"queue":["datatype-queue","schedule-queue","platform-sync-queue"]}

This reports configured capability; it is not itself proof that the consumer completed work. The complete check is a future schedule, a matching queue entry, a run record, and a fresh target readback. When an accepted schedule remains queued after its due time, an operator should check worker process health, Redis connectivity and the worker logs for that specific schedule.

Choose one client

The video displays TypeScript, JavaScript, Python, curl, Postman and JSON together. They are equivalent forms of each request, not instructions to create the same schedule six times. Use one client, define base, orgId, token and route, and load the exact JSON body for the current step. For Postman, choose the shown method and path, set the organization and Bearer headers, and use Body → raw → JSON for requests with a body. Inspect the HTTP status and returned JSON. Downloaded observed examples contain historic timestamps: replace them with future instants before creating a schedule.

Generate future timestamps for replay

The download includes make-schedule.py. It creates JSON only; it does not send a request. Give it your actual datatype and saved target ID:

python3 make-schedule.py --datatype YOUR_TYPE --target YOUR_RECORD_ID > request.json

The simple request starts two minutes from the helper's current UTC time and ends three minutes from it. Inspect the generated JSON, then submit promptly to POST /repository/create. Run the helper again if that lead time has passed. Add --cron for an every-minute repeat bounded three minutes from generation. That example still changes data.status to ready; it requires a suitable isolated target.

For Postman, paste postman-pre-request.js into the create request's Pre-request Script and use postman-simple-body.json as its raw JSON body. Set your private targetDatatype and targetId variables. The script sets futureStart, futureEnd and a fresh schedule name at send time. Review the resolved request in Postman. Do not run the date generator on a later inspection request and mistake those newly generated dates for an already saved schedule's dates.

3. Create the target collection and record

Choose the thing AppEngine should act on before creating the schedule. The demonstration creates a small custom collection with a reference and status. The collection identifier is schedule_marks_1010; its display title is Schedule markers — API course. A collection schema and a record inside it are different objects. Use a unique identifier in your own organization if this example already exists.

POST /repository/create

{
  "datatype": "collection",
  "isNew": true,
  "data": {
    "name": "schedule_marks_1010",
    "title": "Schedule markers \u2014 API course",
    "schema": {
      "type": "object",
      "properties": {
        "reference": {
          "type": "string",
          "title": "Reference"
        },
        "status": {
          "type": "string",
          "title": "Status",
          "enum": [
            "waiting",
            "ready",
            "finished",
            "manual"
          ]
        }
      },
      "required": [
        "reference"
      ]
    }
  }
}

Create one marker in that collection:

POST /repository/create

{
  "datatype": "schedule_marks_1010",
  "isNew": true,
  "data": {
    "reference": "SCHEDULE-1010-A",
    "status": "waiting"
  }
}

The returned sk is the target record ID. In the recorded run it is 6aca1344f5d6b011f6e12028. Keep your own returned ID instead of copying that value. The schedule targets the datatype and ID together; a matching title or reference alone is insufficient.

The action update-status writes data.status; update-state addresses the record's lifecycle state. They are not interchangeable. Other actions can publish/unpublish, delete, send through configured channels or control another schedule. Their business and access prerequisites remain necessary; the harmless status example demonstrates scheduling without pretending to verify every possible business action.

4. Configure the two-boundary schedule

Create a schedule through Repository. Give it a distinct name/title, type simple, status active, and the exact target. Put the first action inside start and the later action inside end:

POST /repository/create

{
  "datatype": "schedule",
  "isNew": true,
  "data": {
    "name": "course-status-window-1010",
    "title": "Course status window \u2014 API course",
    "type": "simple",
    "status": "active",
    "target": [
      {
        "datatype": "schedule_marks_1010",
        "id": "6aca1344f5d6b011f6e12028"
      }
    ],
    "start": {
      "date": "2026-10-10T10:29:20.490Z",
      "action": "update-status",
      "actionArgs": "ready"
    },
    "end": {
      "date": "2026-10-10T10:30:20.490Z",
      "action": "update-status",
      "actionArgs": "finished"
    }
  }
}

This is the complete observed body. Its dates are historical evidence, not reusable future dates. Choose two future UTC instants with enough time to submit and inspect the queue. A Z suffix identifies UTC. Convert from the intended local time deliberately, then verify the returned instant. The end must occur later than the start. actionArgs supplies the new status string for these actions.

The saved schedule has its own sk: 6aca1344f5d6b011f6e12029 in this run. Keep target ID and schedule ID separate. The target identifies the object to change; the schedule ID identifies the instruction and its run history.

5. Inspect the queue, then verify execution

GET /queue-manager/org-schedules

No request body.

The organization queue can contain unrelated schedules. Find entries whose data carries your exact schedule ID. Our run produced a delayed start and delayed end, each with a distinct ID and scheduledFor instant. Queued work has not necessarily changed the target yet. Do not clear, pause or purge a shared queue to troubleshoot one record.

Read the target after each boundary using GET /repository/get/schedule_marks_1010/YOUR_TARGET_ID. Read the instruction using GET /repository/get/schedule/YOUR_SCHEDULE_ID. The observed sequence was:

PointTarget data.statusSchedule data.statusFuture work
Before startwaitingactivestart and end
After startreadyactiveend
After endfinisheddoneno nextRun

Read its history:

GET /queue-manager/schedule-runs/6aca1344f5d6b011f6e12029?limit=10

No request body.

Compare the history's action and timestamp with the saved target. A log entry alone is weaker evidence than the target result. In the current consumer, some action-level failures can be logged while the outer execution continues, especially with several targets. Check every intended target when building a multi-record operation.

6. Stop, reschedule and restart

Use a separate marker for the control exercise so you do not disturb the completed demonstration. Create its schedule in stop status with a future start and actionArgs manual. The downloaded 16-create-stopped example contains the complete body.

Start the saved instruction:

POST /schedule-control/start/6aca13c6f5d6b011f6e12052

No request body.

The response returns the schedule status and actual queued entry. Stop the same instruction:

POST /schedule-control/stop/6aca13c6f5d6b011f6e12052

No request body.

The observed response has status stop and queued: []. It removes that schedule's future work; it preserves its record and completed effects. While stopped, change its saved date with the dotted partial-update contract:

POST /repository/update-partial/schedule/6aca13c6f5d6b011f6e12052

{
  "data.start.date": "2026-10-10T13:30:30.387Z"
}

Choose your own future date. Start again and verify the returned scheduledFor; an accepted partial update does not itself prove that a new job was queued. In our run, the new date replaced the prior start time in the queued entry.

A past start was rejected with HTTP 400, reason run_at_in_past, field start.date. Missing targets were rejected with target_required. Correct the precise field, use a new future instant and retry the intended operation. Do not assume Start replays missed boundaries.

7. Run now and cancel a queued entry

POST /schedule-control/run/6aca13c6f5d6b011f6e12052

No request body.

Run now returns a new immediate job ID. It adds an execution; it does not replace the previously queued future start. Our separate marker changed to manual, which was confirmed by a fresh Repository read. Running an operation that cannot safely repeat would therefore need additional care: cancel the unwanted future entry and inspect what already happened before retrying.

POST /schedule-control/cancel-job

{
  "jobId": "video-lab-mut4jkwd-6aca13c6f5d6b011f6e12052-start"
}

The body takes a queued job ID, including its suffix, not just a schedule ID. Cancelling this start entry marked the instruction cancelled, cleared its next run and retained the already changed target. Cancelling an end entry removes that end action rather than undoing the start. Repeat-job cancellation has different queue identity; use the actual returned queue information and stop a repeat when the intention is to remove future repeats.

If the job is already absent, check run history and target state before deciding whether another execution is required. Cancellation is not rollback.

8. Repeat with cron, timezone and an end boundary

A cron instruction places its expression, timezone and action inside cron, not inside start:

POST /repository/create

{
  "datatype": "schedule",
  "isNew": true,
  "data": {
    "name": "course-cron-1010",
    "title": "Course bounded repeat \u2014 API course",
    "type": "cron",
    "status": "active",
    "target": [
      {
        "datatype": "schedule_marks_1010",
        "id": "6aca13c6f5d6b011f6e12058"
      }
    ],
    "cron": {
      "value": "* * * * *",
      "timezone": "America/Chicago",
      "action": "update-status",
      "actionArgs": "ready"
    },
    "end": {
      "date": "2026-10-10T10:32:10.387Z"
    }
  }
}

The test uses * * * * * (every minute) and a separate marker. A later end.date bounds the test. Read the organization queue and inspect the repeat entry and next instant; then read the marker and history. The actual target changed to ready. At the end boundary, the schedule became done, nextRun became null, and its repeat entry disappeared from the queue.

For a weekly timetable, choose the intended named timezone and verify the calculated next instant. Do not assume the time on your laptop is the worker's scheduling timezone. Daylight-saving transitions can change the UTC instant associated with a local wall-clock time. A five-field expression is a timetable, not a guarantee that unavailable infrastructure can execute at the intended moment.

9. Inspect the same objects in Appmint Studio

In Studio, open Schedule. Search for Course status window. Open View Details. The current editor shows the target, the start's Update Status → Ready, and the end's Update Status → Finished. This recording inspects the completed saved instruction and leaves it unchanged.

Actual schedule editor with target ID, ready start action and finished end action

Choose Cancel to close without saving, then Runs. Locate this schedule's end and start entries; other schedules can appear in the history even when the overview search is filled. Match the exact schedule title, action and timestamps.

Actual schedule run history showing the completed end action

Open Database → All Collections → Custom, search for Schedule markers, open the collection and select Data. Find reference SCHEDULE-1010-A, open its Edit control and maximize the form. The saved status is Finished. Close without changing or saving it. This connects the API's record ID with the visible collection and outcome.

Actual saved marker form showing reference SCHEDULE-1010-A and Finished status

10. Discover registered platform jobs

GET /jobs/scheduled is a staff-level registry view. It is not the same route as the organization queue used earlier. Jobs can have kinds interval, daily or exact; the kind determines the configurable timing and how the job is registered.

Inspect job, kind, enabled, inUse, queued, intervalMinutes where applicable, nextRun, lastRun and warning. Our social-sync-content example was enabled but not in use and not queued. A server implementation being available does not mean the current organization has the connected account or data needed to use it. A calculated time alone does not prove that Redis holds a job.

Do not create a duplicate generic schedule simply because a purpose-built platform job already exists. Use the registered capability's own configuration when that is the intended operation. AppEngine's registry includes provider, operational and maintenance jobs with different scopes; discover the actual keys in your deployment instead of guessing from a name.

11. Configure and monitor registered jobs — contract examples

This section is documented, not an executed provider demonstration. Changing these settings can affect the entire organization's scheduled work. Use it only for a job you are authorized to configure; preserve existing settings outside the intended change.

For an interval job, PUT /jobs/scheduled/JOB_KEY accepts explicit settings such as:

{"enabled":false,"intervalMinutes":10}

The actual job definition constrains allowed intervals. A daily job instead uses a time and named timezone, for example {"time":"09:00","timezone":"America/Chicago"} with a real daily key from discovery. Exact jobs are tied to their registered events or instants; do not assign interval semantics to them. Read the returned configuration and queue view after any authorized change.

POST /jobs/scheduled/JOB_KEY/run accepts {"args":{}} and creates an observable run for the registered implementation. A running-job lock can refuse a concurrent run with JOB_RUNNING; repeatedly submitting it is not recovery. GET /jobs?kind=scheduled:JOB_KEY lists matching run records; GET /jobs/JOB_ID inspects a specific record. Check status, progress/result and error information supplied by that job, then inspect the actual affected data.

POST /jobs/JOB_ID/cancel requests cooperative cancellation. It does not rewind completed work, and an implementation must reach its cancellation checks. POST /jobs/JOB_ID/retry is for failed or cancelled supported jobs and creates a new run. Inspect the returned run rather than assuming the old one was modified into a new success. Neither operation was executed against shared provider jobs in this course.

12. Provider synchronization

Start with GET /sync/platforms. Distinguish the platform's enabled capability from an organization's connected account. Our actual Facebook entry was enabled and not connected. GET /sync/status/facebook returned never-synced. These are valid discovery outcomes, not evidence of imported posts or comments.

After completing the provider's own connection procedure and obtaining its required scopes, the trigger contract is:

POST/sync/trigger
{"platform":"facebook","syncType":["posts","comments"]}

syncType accepts a supported string or array. Available combinations differ by provider; use current discovery and API documentation. Optional options can carry start/end dates or a force choice when supported by the handler. A force flag does not create missing provider credentials or permissions. No external provider request was executed for the recording.

A successful queue response is only the beginning of verification. Follow the job ID or platform status, inspect reported errors, and confirm the exact imported records in the module consuming them. Do not promise that every provider implements the same entities, synchronization direction, deduplication or conflict policy. Those belong to the provider's actual contract and follow-up course.

13. Diagnose the layer that failed

When the schedule is saved but no job appears, inspect the schedule's status, valid future time, target and action arguments. When the job exists but remains waiting past its instant, inspect the worker and Redis connection for that deployment. When a run is recorded but the target did not change, inspect the action, exact datatype/ID, permissions and action-level logs. When provider sync is available but disconnected, complete the provider setup; creating another timer cannot supply missing credentials.

The recorded recovery checks include run_at_in_past, target_required, stopping before editing a date, reading the new queued instant, and verifying the target after Run now. Preserve the smallest failing request and its HTTP status when reporting an issue. Avoid whole-organization purge, queue pause/resume or broad retry as a substitute for finding the failing layer.

Low-level /sync/queue, pause/resume and purge endpoints exist for operational use, but they are not the routine controls for one application schedule. The course uses /schedule-control to target one instruction. No destructive queue operation is demonstrated.

14. Completion and verification boundaries

You should now be able to create an isolated target, configure a timed start/end pair, find both queued entries, verify their results, stop and reschedule, run an additional execution, cancel the correct job and bound a repeating schedule. You can also distinguish the registered job catalogue, its settings/run lifecycle and provider connection status.

The automatic start/end, control and bounded cron paths were executed against current AppEngine and read back from Repository. Studio shows the same stored instruction, runs and final record. Platform settings/run/cancel/retry and connected provider import remain clearly marked contract examples; the training organization had no connected Facebook provider. No public posting or external message was sent. The isolated schedules were left completed or cancelled, with no course-owned future queue entry remaining.

Source review covered sync/schedule-control.service.ts, sync/queue-consumer/schedule.consumer.ts, sync/sync.controller.ts, sync/sync.module.ts, registered jobs, job run controls and the current Schedule Studio components. The downloadable sanitized JSON records the executed requests and outcomes. Concept illustrations explain those contracts; they do not replace execution evidence.