Video chapters
- AppEngine CRM APIs: leads, bookings, forms and support
- Built-in records and business actions
- Prepare the API address and caller identity
- Create a pipeline with real stage identifiers
- Capture and retrieve a lead
- Assign, follow up, qualify and convert
- Configure a bookable service
- Discover available times in the business timezone
- Book the slot and handle a conflict
- Reschedule, cancel and inspect the retained record
- Define the intake schema and access mode
- Validate and read back a form submission
- Create a ticket as a signed-in customer
- Add a staff note, assign and resolve
- Read customer context and pipeline measurements
- Choose the next CRM capability deliberately
- Find the same CRM records in Appmint Studio
- Connect each CRM action to its actual result
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.
documented and rehearsed developer course · 18 chapters · Current API and Studio evidence
Watch the review video · Download runnable examples and observed JSON
How to use this course
Choose the operation you need. JSON bodies are shown completely in the downloadable examples; the video displays equivalent TypeScript, JavaScript, Python, curl and Postman requests alongside JSON. Observed responses are evidence from the controlled training deployment, not templates to post back. Historical IDs and dates must be replaced. The examples README includes exact connection setup, sequence and expected failures.
1. AppEngine CRM APIs: leads, bookings, forms and support
Your application can capture a lead, connect that person to a customer, offer bookable times, collect a form, and open a support ticket. AppEngine supplies the records and the actions behind those screens. We'll follow real API requests through their saved results, then inspect the same information in Appmint Studio. You'll see the required setup, the different calling identities, and how to handle a request that cannot be completed.
This course is for developers adding AppEngine CRM features to their own applications. It teaches the platform contracts and setup rather than how to sell or operate a fictional company. Four independent, short examples cover a lead, a booking, an intake form and a support ticket. You can complete one without implementing the others. The advanced section identifies additional CRM capabilities and gives exact starting contracts and linked lessons; those alternatives were documented, not executed as external transactions.


2. Built-in records and business actions
A lead, customer, reservation, and ticket are built-in record types. A business action does more than change a field. Converting a lead links its customer. Booking checks the requested time and derives the end time from its service. Resolving a ticket records the resolution and updates its workflow. Use the action for that transition, and read the resulting record to confirm what happened. Repository remains useful for schema extensions, configuration and data inspection; it is not a replacement for the booking or conversion operation.
A built-in record has a datatype, an identifier (sk) and business values under data. A CRM action can change several related values or records. Lead conversion can connect a customer and change a pipeline stage; booking checks a slot and records a reservation; resolving a ticket updates its resolution and workflow. A Repository write used to configure a definition is not evidence that the business action has run. Use the action endpoint, preserve its returned identifiers, then inspect its specific result. Repository readback is useful for authorized staff inspection.


3. Prepare the API address and caller identity
Start with your organization's API address, organization ID, and an authorized staff session. Database and Repository APIs, chapter two, shows the staff sign-in and returns the token used here. Keep orgid and the bearer token on each protected request. Public booking and the open form in this example use the organization header without a staff token. Customer ticket intake uses a customer session from API Foundations. Do not put a staff credential into a public website. Training email delivery is disabled in this organization; queued notices will not be presented as delivered messages.
- Open Appmint Studio in a training organization. Keep its organization ID and assigned AppEngine API origin. The Studio website address is not a replacement for your backend API origin.
- Follow Database & Repository APIs — staff setup, video chapter 2 at 1:02 and its written setup. Obtain a valid staff token from
POST /profile/user/signinwithorgidand your staff email/password. Handle any challenge returned by your deployment. Keep that token on a trusted server or development tool. - For the ticket chapter only, use the customer sign-in demonstrated in API Foundations.
POST /profile/customer/signinproduces the customer session used in that chapter. A staff token and a customer token are not interchangeable. - Use controlled training email addresses. Inspect your organization's delivery configuration before writes. This rehearsal verified inactive SMTP with
sendOutgoing: false; it did not prove delivery was disabled for every possible channel. Do not assume your own organization has the same configuration. - Download the examples. Set
APPENGINE_ENDPOINT,ORG_ID,STAFF_TOKEN, and, for customer-only requests,CUSTOMER_TOKEN. Choose one client implementation; do not create the same records once per language. Node 20+ and Python 3.10+ runners use standard libraries. The TypeScript equivalent targets an existing Node TypeScript project. Postman variables stay in your local environment. - Replace every recorded identifier with your returned ID, and replace historical booking and follow-up dates.
future-slots.py YOUR_DEFINITION_IDgenerates a future weekday request in America/Chicago without making a booking. Read the returned available slot and use that exact timestamp; do not convert a displayed wall-clock time by guessing the UTC offset.
Public definitions, slot lookup and booking in this lesson use orgid without a staff token. No application credential is a prerequisite for those demonstrated routes. The runner's route.json explicitly identifies staff, customer or public. The public form in this lesson intentionally uses authenticationType: none.


4. Create a pipeline with real stage identifiers
First create the pipeline the lead will belong to. The request contains its name and stages, each with a stable ID, label, order and probability. Our final stages identify won and lost outcomes. This is a separate training pipeline, not a change to your organization's default. Save the returned pipeline ID. When creating a lead, send that ID and the intended starting stage ID. Read the pipeline back to confirm the saved stages. Assignment rules, stage requirements and scoring choices belong to the pipeline you actually use; a label typed on your own board does not configure them.
Create a non-default training pipeline first. The complete request below includes ordered stage IDs, probability, Won/Lost flags and an empty assignment-rules list. Save the returned sk as your pipeline ID. The stage ID is new, not the label New. Read the saved pipeline and compare all stages before creating a lead. Do not overwrite a business's existing default pipeline to follow this example.


Create pipeline
POST /crm/leads/pipelines · Identity: staff · Observed HTTP 201.
{
"name": "CRM API course",
"description": "Controlled developer course pipeline",
"isDefault": false,
"stages": [
{
"id": "new",
"name": "New",
"order": 0,
"probability": 10
},
{
"id": "qualified",
"name": "Qualified",
"order": 1,
"probability": 50
},
{
"id": "won",
"name": "Won",
"order": 2,
"probability": 100,
"isWon": true
},
{
"id": "lost",
"name": "Lost",
"order": 3,
"probability": 0,
"isLost": true
}
],
"assignmentRules": []
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Pipeline read
GET /crm/leads/pipelines/6aca82e237ff863efff476aa · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
5. Capture and retrieve a lead
Lead capture uses CRM leads detail, with fields directly in the request body. Here we send the person's name, email, website source, description, pipeline ID and starting stage. The result has a generated lead number, separate name fields, a score, and a contact ID. Keep the record's sk for subsequent actions; the display number is a different value. The list endpoint supports search and pagination. Searching for this lead returns the same saved record. The score reflects the supplied information and current rules; do not treat the example value as a universal qualification threshold.
Replace pipelineId with your new pipeline's ID; retain stageId: new. Use a distinct controlled email and submit once. Save the lead's sk separately from its contactId; they refer to different records. Search results use paging, so inspect total and data rather than treating one page as the whole organization. The recorded search was taken after conversion; its later status is not proof that creation alone converts a lead.


Create lead
POST /crm/leads/detail · Identity: staff · Observed HTTP 201.
{
"fullName": "Mira CRM Course",
"email": "[email protected]",
"source": "website",
"description": "Controlled API inquiry",
"pipelineId": "6aca82e237ff863efff476aa",
"stageId": "new"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Lead filter
GET /crm/leads/detail?search=Mira%20CRM&page=1&pageSize=10 · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
6. Assign, follow up, qualify and convert
Assign the lead with assignedTo. This example assigns it to the signed-in staff member. Add a note through activities, and set a follow-up with an actual future timestamp. Qualification records the decision and its notes. Conversion then connects the lead to a customer. Our returned customer ID matches the existing contact ID, so this conversion did not create a second person. This configured pipeline also moves the converted lead to its Won stage. Finally read activities: creation, assignment, notes and transitions are visible there. Conversion is not a recorded payment or an invoice.
Run the operations in order against your returned lead ID. Assign to your authorized training staff email; this rehearsal assigned the caller to themselves. Add an activity with type and description, then set a future followUpDate. Qualification and conversion are separate actions. Save the converted customer ID and compare it with contactId; the observed values match. Our configured Won stage was selected by conversion. This is a pipeline outcome, not payment or revenue collection. Activity history provides creation, assignment, note and transition entries; read it after mutation. A forecast aggregates configured lead values and probabilities; it is not a ledger of cash received.


Assign
POST /crm/leads/assign/6aca82e237ff863efff476ad · Identity: staff · Observed HTTP 201.
{
"assignedTo": "[email protected]"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Note
POST /crm/leads/activities/6aca82e237ff863efff476ad · Identity: staff · Observed HTTP 201.
{
"type": "note",
"description": "Requested the API integration guide"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Follow up
POST /crm/leads/follow-up/6aca82e237ff863efff476ad · Identity: staff · Observed HTTP 201.
{
"followUpDate": "2026-10-14T18:24:34.989434+00:00",
"notes": "Review the requested guide"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Qualify
POST /crm/leads/qualify/6aca82e237ff863efff476ad · Identity: staff · Observed HTTP 201.
{
"notes": "Integration requirements confirmed"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Convert
POST /crm/leads/convert/6aca82e237ff863efff476ad · Identity: staff · Observed HTTP 201.
{
"conversionValue": 150
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Lead activities
GET /crm/leads/activities/6aca82e237ff863efff476ad · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Customer
GET /repository/get/customer/6aca82e237ff863efff476ab · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
7. Configure a bookable service
Before asking for times, create a reservation definition. Our complete payload defines an active service called API consultation, lasting thirty minutes, one available place, weekdays, opening hours, and America Chicago as the business timezone. It also specifies the reminder configuration. This is configuration data; the actual booking will use the reservation action. The returned definition ID connects every slot request and reservation to these rules. Definitions can also describe hosts, locations and blocked times. Configure those when your service needs them, and verify their effect through availability rather than guessing from a calendar display.
Create the definition before searching for times. The exact payload supplies service name and duration, active status, weekdays, hours, timezone, capacity and reminders. spots: 1 limits this definition's available place count. hosts: [] is the simplified unassigned-host example; it does not teach a configured staff rota. Save the definition sk. The definition name and the service name are different values; slot lookup needs the service name exactly as configured. Reminder configuration may queue a delivery; it is not evidence a recipient received it.


Definition
PUT /repository/create · Identity: staff · Observed HTTP 200.
{
"datatype": "reservation_definition",
"isNew": true,
"data": {
"name": "crm-api-consultation-1010",
"title": "CRM API Consultation",
"type": "service",
"status": "active",
"services": [
{
"name": "API consultation",
"duration": 30,
"price": 0,
"breakAfter": 0
}
],
"workDays": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
],
"officeHours": {
"startTime": "09:00",
"endTime": "17:00",
"timezone": "America/Chicago"
},
"spots": 1,
"hosts": [],
"notifications": [
{
"timeBefore": 1,
"unit": "days",
"type": "email"
}
]
}
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Public definitions
GET /crm/reservations/definitions · Identity: public (orgid only) · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
8. Discover available times in the business timezone
The slots request needs the definition ID, exact service name, and a calendar date. Its response gives start and end instants, places available, and the business timezone. Fourteen hundred UTC is nine in the morning in Chicago on the date shown. Keep the ISO instant for the booking and format the display in the business timezone. For a longer chooser, availability accepts a starting date and number of days and returns the next available time. Use future dates when following this lesson. An available slot is an offer at the time of the query, not a reservation.
Generate a future service date, pass your definition ID, and inspect the returned slots. The response includes actual ISO start/end instants and business-timezone context. For a booking, copy one currently available startTime. The broader availability endpoint reports a date range and next available time. Availability is a read at a moment in time: a later booking request must still be checked for refusal. Change timezone/host/location rules in the definition and re-query availability when using those alternatives.


Slots
POST /crm/reservations/slots · Identity: public (orgid only) · Observed HTTP 201.
{
"reservationDefinitionId": "6aca82e337ff863efff476b7",
"serviceName": "API consultation",
"serviceDate": "2026-10-13"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Availability
POST /crm/reservations/availability · Identity: public (orgid only) · Observed HTTP 201.
{
"reservationDefinitionId": "6aca82e337ff863efff476b7",
"serviceName": "API consultation",
"from": "2026-10-13",
"days": 2
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
9. Book the slot and handle a conflict
The public book action accepts the definition, service, selected start time, and the customer's name and contact details. The saved reservation has its own ID and booking code, a calculated end time, and a linked customer. The returned notices say queued. That means delivery work was queued, not that an email arrived. A second request for the occupied time returns HTTP four hundred with SLOT TAKEN in this run. Keep the entered details, show that specific problem, and request fresh availability. Do not bypass this refusal by inserting a reservation through the generic Repository API.
Submit the selected slot, definition/service and controlled customer name/email. Save the booking sk, its human-facing booking code/name, linked customer ID and start/end times. Those are different identifiers. The response's notice entry reported queued state; the training email configuration did not deliver a confirmation. Show a user “booking saved” from the saved record, and only show “confirmation delivered” if you separately verified delivery. A second sequential booking into the same single-place slot returned HTTP 400 with SLOT_TAKEN. Refresh availability and let the user choose another slot. This is an occupied-slot test, not a concurrency stress test.


Public book
POST /crm/reservations/book · Identity: public (orgid only) · Observed HTTP 201.
{
"reservationDefinitionId": "6aca82e337ff863efff476b7",
"service": "API consultation",
"startTime": "2026-10-13T14:00:00.000Z",
"customer": {
"name": "Mira CRM Course",
"email": "[email protected]"
}
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Slot conflict
POST /crm/reservations/book · Identity: public (orgid only) · Observed HTTP 400.
{
"reservationDefinitionId": "6aca82e337ff863efff476b7",
"service": "API consultation",
"startTime": "2026-10-13T14:00:00.000Z",
"customer": {
"name": "Mira CRM Course",
"email": "[email protected]"
}
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
10. Reschedule, cancel and inspect the retained record
For a staff change, address the reservation by its saved ID. Reschedule takes the new start time and a reason; notify false makes this change quiet. The response has the changed times and reschedule history. Cancel records a reason and retains the reservation with cancelled status. Our final Repository readback confirms those values. The reservation get route is a customer read. A staff token returned forbidden there; staff can inspect the record through their authorized Repository read. Use the customer session when building that customer view. Customer management by email and booking code is a separate contract; the companion lists its routes and identity requirements. Cancellation and deletion are different operations.
Use the staff ID route for the shown reschedule and cancellation. Supply an available future instant and a reason; notify: false was used for rescheduling. Inspect returned start/end times and rescheduleHistory. Cancellation retains the reservation with cancelled status and a reason. Authorized staff Repository readback then verifies the exact record.
GET /crm/reservations/get/:id is a customer read route. Sending the staff token returned 403 as an expected identity boundary, not a broken staff API. A signed-in customer must use the customer session required by that route. Separate email/booking-code management routes exist; POST /crm/reservations/reschedule/:email/:code is an alternative contract, not the staff-ID example. Its configured access requirements still apply; a known email alone is not a staff session.


Reschedule
POST /crm/reservations/reschedule/6aca830537ff863efff476c2 · Identity: staff · Observed HTTP 201.
{
"startTime": "2026-10-13T14:30:00.000Z",
"reason": "Course change example",
"notify": false
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Cancel
POST /crm/reservations/cancel/6aca830537ff863efff476c2 · Identity: staff · Observed HTTP 201.
{
"reason": "Controlled API lesson completed"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Reservation record
GET /repository/get/reservation/6aca830537ff863efff476c2 · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Booking readback
GET /crm/reservations/get/6aca830537ff863efff476c2 · Identity: staff · Observed HTTP 403.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
11. Define the intake schema and access mode
A CRM form defines what a visitor is allowed to submit. This example has its own schema rather than a bound collection: full name, email and request are required. The form's machine name identifies its public route. Reading that route returns the schema and access information your interface needs. We deliberately use an open form here. An email entered into that form is contact information, not proof of a signed-in identity. For restricted forms, configure the intended customer, access-code or invitation policy and carry that proof on both reading and submission. Those alternatives are documented in the companion.
The complete crm_form record below supplies the route name, active status, open authentication mode, inline JSON schema and submit message. Use a unique form name. Reading /client/forms/YOUR_NAME returns its schema and access requirements. The three required fields are fullName, email and request. This example saves a form_submission; it is not configured to create a lead, ticket or workflow automatically.
For restricted access, POST /client/forms/:name/access accepts an email and can send an access message. A subsequent request must carry the form's x-form-code or x-form-token as applicable. Do not merely change the schema or send an email field and claim verified identity. Restricted invitation delivery was not executed here. Appmint Forms covers the form editor and its destination configuration; Workflow APIs covers configured process handling.


Create form
PUT /repository/create · Identity: staff · Observed HTTP 200.
{
"datatype": "crm_form",
"isNew": true,
"data": {
"name": "crm-intake-1010",
"title": "CRM API Intake",
"status": "active",
"authenticationType": "none",
"schema": {
"type": "object",
"properties": {
"fullName": {
"type": "string",
"title": "Full name"
},
"email": {
"type": "string",
"format": "email",
"title": "Email"
},
"request": {
"type": "string",
"title": "Request"
}
},
"required": [
"fullName",
"email",
"request"
]
},
"submitMessage": "Your request was saved"
}
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Public form
GET /client/forms/crm-intake-1010 · Identity: public (orgid only) · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
12. Validate and read back a form submission
Submit answers using the schema's exact field names. Leaving request out returns a required-field error. With all three fields supplied, AppEngine saves a form submission. Read its values, form ID and authentication result: this open submission explicitly says verified false. Staff can retrieve submissions by the form name with pagination. A collection-bound form saves its answers into that collection instead, so follow the configured destination. Workflow is another configuration choice; this unbound form example proves validation and saved answers, not a separately configured approval process.
First omit the request field deliberately. The observed response is a required-field error; display it beside the corresponding field and preserve the user's entered values. Then send all three fields. Save the submission ID and inspect data.values. Authentication metadata records open admission and verified: false: an email-shaped value is not a verified account identity. Staff can page through submissions filtered by formId. A saved unbound form does not establish that a destination process or approval ran.


Form required
POST /client/forms/crm-intake-1010/submit · Identity: public (orgid only) · Observed HTTP 400.
{
"fullName": "Mira CRM Course",
"email": "[email protected]"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Form submit
POST /client/forms/crm-intake-1010/submit · Identity: public (orgid only) · Observed HTTP 201.
{
"fullName": "Mira CRM Course",
"email": "[email protected]",
"request": "Send the developer guide"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Form submissions
GET /crm/forms/submissions?formId=crm-intake-1010&page=1&pageSize=10 · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
13. Create a ticket as a signed-in customer
Support intake uses a different identity. Here we reuse the training customer's session from API Foundations and submit a title and description inside data. To make the boundary visible, this request also tries to set done and urgent. The saved ticket is new with medium priority: customer intake does not accept those staff controls. Its reporter comes from the authenticated customer. Reading tickets with that same session and the returned ticket number retrieves this request. For attachments, the multipart create-ticket route accepts the ticket JSON and files together; File Storage APIs explains file paths and upload handling.
Use the existing customer session for /crm/tickets/create. The demonstration deliberately attempts to set done status and urgent priority; the returned ticket instead has new status and medium priority, with reporting identity taken from the authenticated customer. Treat this as proof for this route and payload, not a universal ownership assertion about every API. The same customer successfully read the returned ticket number. This support example uses the separate existing customer from API Foundations, not the email used by the lead/booking example.
For a staff-created ticket with files, the documented alternative is POST /crm/tickets/create-ticket using multipart form data. The data part contains a JSON string of the ticket payload or full envelope; file parts contain actual bytes. Use FormData in JavaScript, files= with Python requests, -F in curl, or Postman form-data. Let the client set the multipart boundary; do not force Content-Type: application/json. This attachment contract was reviewed, not uploaded in this CRM rehearsal. File Storage APIs teaches storage uploads and file references.


Customer ticket
POST /crm/tickets/create · Identity: customer · Observed HTTP 201.
{
"data": {
"title": "CRM API course request",
"description": "Please confirm the integration guide",
"status": "done",
"priority": "urgent"
}
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Customer ticket read
GET /crm/tickets/get/Q3CHB319 · Identity: customer · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
14. Add a staff note, assign and resolve
Switch back to staff credentials to work the ticket. An internal comment is separate from a customer reply. Assignment uses assignTo, and the status action accepts resolved with a resolution summary. Read the ticket again: its stored status is done and the resolution identifies the staff member. The workflow endpoint places the same ticket in Resolved. The reply and resend actions are for external delivery, so they are not executed here. When using them, inspect the saved message and delivery status; a queued message or successful request alone does not prove the customer received it.
Switch back to the staff session. Add an internal comment, assign with assignTo, and resolve using the status endpoint plus a resolution summary. The recorded response and Repository readback contain done status and resolution metadata. Read the ticket workflow as well: the observed stage is Resolved and final. Comments and messages are different collections. Internal notes are not proof of an outgoing reply. Reading messages here did not send, resend or notify a customer. Before integrating an actual reply, configure a delivery channel and verify recipient-visible delivery independently.


Ticket note
POST /crm/tickets/comments/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 201.
{
"message": "Checked the API guide for this request"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Ticket assign
PATCH /crm/tickets/assign/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 200.
{
"assignTo": "[email protected]",
"note": "Course reviewer"
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Ticket resolve
PATCH /crm/tickets/status/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 200.
{
"status": "resolved",
"resolution": {
"summary": "Developer guide confirmed"
}
}Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Ticket record
GET /repository/get/ticket/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Ticket workflow
GET /crm/tickets/workflow/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Ticket comments
GET /crm/tickets/comments/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Ticket messages
GET /crm/tickets/messages/6aca833237ff863efff476ce · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
15. Read customer context and pipeline measurements
Customer three sixty gathers related information for staff. Query by the customer's email, then inspect who matched and the separate result groups. Our CRM customer has the cancelled reservation and related message records; the support example used a different authenticated customer, so its ticket does not belong in this person's count. Keep that identity distinction in your own interface. Pipeline forecast is another read model. The example returns a converted deal, but a tiny training dataset is not a useful business prediction. Use the response structure and filters; do not turn these fixture totals into a performance claim.
Customer 360 takes an email query and assembles matching identities and related sections. Inspect who as well as aggregate counts. In this recording the lead/booking identity has a cancelled reservation; the support ticket belongs to a different customer and is therefore not expected in this person's ticket count. Do not merge two people merely because both examples use a similar first name. Empty sections mean no matching records in this result, not that the platform lacks those modules. Forecast results describe leads within the selected pipeline and range, not settled invoices.


Customer360
GET /crm/customer-360?email=mira.crm1010%40example.invalid · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
Forecast
GET /crm/leads/forecast?pipelineId=6aca82e237ff863efff476aa&months=3 · Identity: staff · Observed HTTP 200.
Complete request metadata and observed response. Replace recorded IDs with your own returned values before running.
16. Choose the next CRM capability deliberately
The same platform extends beyond these core actions. Lead import maps rows into a chosen pipeline; enrichment needs provider configuration and credit. Signed documents add participants, signature positions and a signing lifecycle to a stored PDF. Merchant customer accounts add credit terms, charges and invoicing. Shared accounts connect several people to one account. These are separate capabilities with their own setup and side effects, not extra fields to add to this booking request. The companion maps their contracts and links the relevant course material. No signature invitation, charge, enrichment purchase or external message was executed in this lesson.


Lead import, enrichment and pipeline rules
documented alternatives; external enrichment was not purchased or executed. POST /crm/leads/import accepts this shape with your saved pipeline ID:
{
"pipelineId": "YOUR_PIPELINE_ID",
"stageId": "new",
"rows": [
{"First Name": "Mira", "Last Name": "Example", "Email": "YOUR_CONTROLLED_EMAIL", "Company": "Training"}
]
}The current implementation recognizes common spreadsheet/export column headings, limits one import to 5,000 rows, and skips duplicate emails within the chosen pipeline or incoming file. Read the returned import result and inspect representative saved leads; do not infer that every row was imported from an HTTP success alone. Use POST /crm/leads/enrich/:id or /enrich/batch with {"leadIds":["YOUR_LEAD_ID"]} only after configuring your enrichment provider and credit. GET /crm/leads/enrichment/status/:id provides the follow-up read. Import and enrichment are different operations.
Pipeline stage IDs, assignment rules and scoring rules belong to pipeline configuration. Read GET /crm/leads/pipelines/:id before changing it with PUT /crm/leads/pipelines/:id; preserve existing fields deliberately and inspect the effect on a training lead. Appmint Leads covers the user-facing pipeline controls. Appmint Automation covers configured automation when available; its written status is separate from a produced video.
Reservation alternatives
A definition may also describe hosts, locations, blocked times and other service constraints. Configure these through Reservations, then inspect the resulting reservation_definition and query /crm/reservations/slots and /crm/reservations/availability with that exact definition. This course provides the complete minimal active definition; it does not claim a multi-host rota was tested. Preserve timezone semantics across daylight-saving changes. Use an ISO instant from the actual slot response rather than appending a guessed offset to a calendar label. Customer email/booking-code rescheduling and cancellation are separate contracts from staff-ID actions, and their current authentication requirements still apply.
Restricted forms and configured destinations
POST /client/forms/:name/access takes {"email":"YOUR_CONTROLLED_EMAIL"} and may deliver an access code or link. Submit the resulting x-form-code or x-form-token on the form's read/submit requests when required. Invitation issuance was not executed here. For a process-bound form, first complete destination/process configuration in Forms and Workflow APIs. Then require both the saved submission and the intended process result as acceptance evidence; an open-form success does not prove that binding.
Signed documents
Upload a real PDF first using File Storage APIs, retaining its storage path. Staff POST /crm/signed-documents creates a draft envelope. Its body supplies title, files: [{path,name}], named participants: [{name,email,role,order}], optional signing order/expiry/context, and signatureFields. A signature field uses fileIndex, PDF page, x, y, width, height, field type and participant assignment. Coordinates are PDF points from the bottom-left of the page. Use the actual PDF geometry; CSS pixels from a scaled browser preview are not interchangeable.
Read GET /crm/signed-documents/:id to inspect normalized participant IDs and spots. While still draft, use POST /:id/update to associate the actual participant with its fields. Preview every page/assignment before POST /:id/send; that action can send invitations. Participants use their own portal token. POST /crm/signed-documents/portal/:token/sign takes consent and signature values:
{
"consent": true,
"signature": {"typedName": "YOUR_SIGNER_NAME"},
"spots": {"YOUR_TEXT_SPOT_ID": {"text": "Agreed value"}}
}This is a contract reference, not an executed signature. The staff lifecycle includes /:id/remind, /:id/cancel, /:id/void and /:id/retry-merge; select the action appropriate to the current state. Verify participant completion, audit evidence and the resulting signed PDF, including merge failure recovery. Sending an invitation does not complete signing. The AppEngine API reference exposes the Signed Documents route group. This CRM lesson does not provide a separately completed signature video.
Merchant customers and shared accounts
Merchant account billing is distinct from an ordinary customer contact. Staff routes are under /crm/merchant-customers. Create/configure the account via POST /detail and read its saved detail before approval. POST /approve/:id accepts optional notes; suspend/close require a reason. Terms and spending controls must be configured for that account before billing. POST /charge/:id accepts {id,type,amount,reference?,description?}. POST /credit/:id accepts {amount,reason}. These change financial state; they were not executed. POST /invoices/generate/:id and /invoices/send/:id/:invoiceId are separate actions—generation is not recipient delivery. Use Merchant Accounts and the current API reference for account-specific requirements.
Shared accounts assemble multiple customer memberships under one account. Staff can inspect GET /crm/shared-accounts, /:accountId, /:accountId/orders and /:accountId/activity. POST /:accountId/members links an existing customer or invites an email; PUT /:accountId/members/:associationId changes a membership; DELETE unlinks it without deleting previous orders. Adding an invitation can deliver an email and was not executed. Customer-facing management lives under /client-data/shared-accounts/* with its own manager checks. CRM General: Shared Accounts explains membership setup. Read the exact membership and resulting access rather than assuming a shared email gives access.
17. Find the same CRM records in Appmint Studio
The API records also appear in Appmint Studio. Open Leads, find Mira CRM Course, and inspect the saved pipeline, owner and activity. Then open Reservations and find the API consultation: the booking remains visible with its cancelled status. These are the same records returned by the requests, not a separate demonstration database. Use their identifiers and saved values when checking your own application's result.
Open Appmint Studio → Leads. Select the CRM API course pipeline, find Mira CRM Course and open its details. Compare the saved stage and owner, then select Activity to inspect qualification/conversion entries. Open Reservations and find the API consultation in the Cancelled column. Compare its identity and timestamp with the API response. This is an actual app recording of the same saved records. No separate mock database or generated interface is substituted.


18. Connect each CRM action to its actual result
You can now connect your own application to AppEngine's lead, booking, form and support capabilities. Start with the built-in action, provide the required configuration and caller identity, then inspect the exact saved outcome. Keep queued delivery separate from confirmed delivery, and keep a rejected operation visible instead of silently replacing it with a data write. The written course includes the complete requests, repeatable setup, advanced alternatives and the observed limitations. Each chapter is available independently when you need one operation again.
Completion check: you can create and inspect a pipeline lead, perform its configured qualification/conversion, define and book an available service, handle a refused slot, reschedule and cancel the booking, validate and save an intake submission, and create/read/resolve a support ticket with the correct identity. You can find the resulting lead and reservation in Studio. External notification delivery, signature execution, merchant billing and paid enrichment remain separate, explicitly unexecuted alternatives.


Verification scope and recovery
Forty-four API evidence files cover the controlled core sequence; the two staff-token calls to the customer reservation-read route deliberately failed. Staff authorized Repository readback succeeded. Real Studio capture verifies the lead and cancelled booking. Six client formats are equivalent examples, not six separately executed end-to-end tests. Restricted form delivery, actual support reply, attachment upload through the ticket alternative, paid enrichment, electronic signing and merchant billing were not executed in this CRM course.
If a request fails, first compare route identity and organization, then the returned configuration IDs and required fields. Refresh availability for a slot refusal. Preserve form input on validation failure. Inspect exact saved IDs before retrying a mutation; blind repeated creates can produce duplicate records. Do not replace an action that refused with a raw write just to display success.
Limitations and checks
Current source reviewed: AppEngine src/crm/leads.controller.ts, leads.service.ts, reservations.controller.ts, reservations.service.ts, crm-form.controller.ts, crm-form-client.controller.ts, form services, ticket controller/service, customer360, signed-document controller/service, merchant-customer controller/service and shared-accounts controller. Actual contracts are preserved in the linked sanitized lab files. This guide supersedes the previous Northside Physio script and its unsupported delivery and identity claims.