Examples and recipes

Short answers to common problems: the problem, the solution, the code or configuration, and the result.

CRUD

Create a record over REST

Problem
An external system needs to add vendors to your app.
Solution
Call the Data API's insert endpoint with the entity name and field values.
cURL
curl -X POST "https://your-data-host/api/v1/records/insert" \
  -H "Authorization: Bearer $TAF_TOKEN" \
  -H "TenantId: $TENANT_ID" -H "AppId: $APP_ID" -H "EnvironmentId: $ENVIRONMENT_ID" \
  -H "Content-Type: application/json" \
  -d '{ "EntityName": "Vendor",
        "Fields": [ { "FieldName": "Name", "Value": "Northwind Traders" } ] }'

Result The record is created with owner, audit and tenant fields stamped by the platform, and any triggers on Vendor fire.

CRUD

Query with grouped conditions

Problem
You need open tickets that are either high priority or overdue.
Solution
Use numbered filters and a FilterLogic expression in a select query.
POST /api/v1/queries/select
{
  "EntityName": "Ticket",
  "SelectedFields": ["Id", "Title", "Priority", "DueDate"],
  "WhereClause": {
    "Filters": [
      { "FieldName": "Status",   "Operator": 8, "Value": "Resolved", "Sequence": 1 },
      { "FieldName": "Priority", "Operator": 3, "Value": "High",     "Sequence": 2 },
      { "FieldName": "DueDate",  "Operator": 2, "Value": "2026-10-01", "Sequence": 3 }
    ],
    "FilterLogic": "1 AND (2 OR 3)"
  },
  "Sort": [ { "FieldName": "DueDate", "Direction": 1 } ],
  "Pager": { "PageNumber": 1, "PageSize": 50 }
}

Result The database does the filtering and paging, and rows outside the caller's read scope are excluded.

Permissions

Own records for staff, team records for managers

Problem
Sales staff should see their own opportunities; managers should see their team's.
Solution
Set the Read scope per role on the entity. Team follows the reporting hierarchy.
Object permissions: Opportunity
Role            Read   Create  Update  Delete
Sales           Own    Own     Own     None
Sales manager   Team   Team    Team    Own
Administrator   All    All     All     All

Result Grids, reports, APIs and TAFI all return the same rows for the same user, because the data engine applies the scope.

Lifecycles

Move a record by status name

Problem
Your front end should approve a vendor without knowing status ids.
Solution
Use the client SDK's transitionTo, which refuses moves the lifecycle does not allow.
vendor.service.ts
import { inject } from '@angular/core';
import { TAF_WORKFLOW } from 'taf-client-angular';

const workflow = inject(TAF_WORKFLOW);

// Throws a TRANSITION error when the lifecycle does not allow the move
await workflow.transitionTo('Vendor', vendorId, 'Approved');

Result The transition is validated, recorded in history, and any after-transition workflows run.

Approvals

Two of three finance approvers

Problem
Large purchases need agreement from finance, but not every member of the team.
Solution
Use the Role strategy on the finance level with an N-of-M quorum.
Approval process: Purchase request
Routing   Sequential
Level 1   Reporting manager          quorum: any one
Level 2   Role: Finance              quorum: 2 of M
          reminder after 24 hours, then escalate
Band      Amount > 50,000  →  add Level 3: Department head

Result Requests settle when two finance approvers agree, and large amounts get a third level automatically.

Workflows

Create an invoice when an opportunity is won

Problem
Winning an opportunity should create the invoice and tell finance.
Solution
Attach a workflow to the Won transition with a CreateRecord action followed by SendEmail.
Workflow actions (illustrative)Configure in Studio's workflow designer
[
  { "ActionType": "CreateRecord", "Name": "invoice",
    "Config": { "EntityName": "Invoice", "Fields": { "Opportunity": "{{Reqtokens.Id}}", "Amount": "{{Reqtokens.Amount}}" } } },
  { "ActionType": "SendEmail", "Name": "notify",
    "Config": { "To": "finance@yourcompany.example", "Subject": "New invoice for {{Reqtokens.Name}}" } }
]

Result Finance gets an email as soon as the opportunity moves to Won, with the invoice already created.

Notifications

Notify a role, with data from a query

Problem
Everyone in Support should hear about new high-priority tickets, with the customer's details.
Solution
Bind a notification to a Ticket-created trigger, choose the Support role as recipients and add a template query.
Liquid templateIllustrative template
New {{ Priority }} ticket: {{ Title }}
Customer: {{ Customer_Detail | first | map: "Name" }}
Open it: {{ RecordUrl }}

Result Every Support user receives the email and an in-app notification, unless they turned that channel off.

Reports

Email an Excel report every Monday

Problem
Managers want last week's orders in their inbox without asking.
Solution
Create an Excel report on a saved query and add a recurring schedule with a cron expression and time zone.
Report schedule
Report      Weekly orders (Excel)
Data        Orders_LastWeek (saved query)
Schedule    Recurring, 0 8 * * MON, Asia/Kolkata
Recipients  Role: Sales manager
Delivery    Email with a secure download link

Result The report runs every Monday at 08:00 in the chosen time zone, and each run is recorded with its outcome.

C# Hooks

Refuse a save with a validation hook

Problem
A discount above 20% must not be saved without an approval request.
Solution
Implement IValidationHook and add a message to context.Errors.
DiscountRule.cs
using TAF.Infra.QueryHook.Interfaces;
using TAF.Infra.QueryHook.Models;

public class DiscountRule : IValidationHook
{
    public Task ValidateAsync(HookContext context)
    {
        if (context.Fields.TryGetValue("DiscountPercent", out var value)
            && Convert.ToDecimal(value) > 20
            && !context.Fields.ContainsKey("ApprovalRequest"))
        {
            context.Errors.Add("Discounts above 20% need an approval request.");
        }
        return Task.CompletedTask;
    }
}

Result The rule applies to every write path: screens, APIs, workflows and TAFI.

C# Hooks

Normalise data before it is written

Problem
Email addresses arrive in mixed case from different sources.
Solution
Use a pre-execution hook to change the field before the write.
NormaliseEmail.cs
using TAF.Infra.QueryHook.Interfaces;
using TAF.Infra.QueryHook.Models;

public class NormaliseEmail : IPreExecutionHook
{
    public Task PreExecuteAsync(HookContext context)
    {
        if (context.Fields.TryGetValue("Email", out var email) && email is string s)
            context.Fields["Email"] = s.Trim().ToLowerInvariant();
        return Task.CompletedTask;
    }
}

Result Every contact is stored with a lower-case email, whichever screen or API created it.

TypeScript

Default a field when a screen opens

Problem
New requests should default the request date to today.
Solution
Write a screen extension and set the value in afterFormLoad.
purchase-request.extension.tsIllustrative: parameters depend on the screen
export class PurchaseRequestExtension extends IScreenExtensionBaseClass {
  afterFormLoad(screenParameters: any) {
    const data = screenParameters.event?.form?.submission?.data ?? {};
    if (!data.RequestDate) {
      this.refreshSubmission(screenParameters, { ...data, RequestDate: new Date().toISOString() });
    }
  }
}

Result The date is filled in on every new request, and users can still change it.

Client SDK

A platform grid inside your own app

Problem
Your branded front end needs a data grid with paging and access rules.
Solution
Use the taf-grid component; it reads through the platform and ships unstyled.
vendors.page.html
<taf-grid
  entity="Vendor"
  [fields]="['Name', 'Country', 'Status']"
  [pageSize]="25"
  [sortable]="true">
</taf-grid>

Result A paged grid of the vendors the user is allowed to see, styled by your own theme.

REST APIs

Publish a partner endpoint without code

Problem
A partner needs a simple GET /vendors/{id} rather than the generic data API.
Solution
Define a Virtual URL with one select step and an output shape, then share its OpenAPI document.
POST /api/v1/virtualurls/upsert
{
  "Method": "GET",
  "PathTemplate": "/vendors/{id}",
  "OperationId": "getVendor",
  "Summary": "Get one vendor",
  "Steps": [
    {
      "Kind": "select",
      "Entity": "Vendor",
      "Criteria": {
        "Id": "{path.id}"
      },
      "AssignTo": "vendor"
    }
  ],
  "OutputTemplate": {
    "id": "{steps.vendor.Id}",
    "name": "{steps.vendor.Name}"
  }
}

Result GET /vu/vendors/{id} returns the shaped record under the caller's permissions, with an OpenAPI 3.1 document for client generation.

Multi-tenancy

Call the API in a tenant and app context

Problem
An integration serves several tenants and must work in the right one.
Solution
Send the tenant, app and environment headers with every call, with a token for a user of that tenant.
HTTP
POST /api/v1/queries/select HTTP/1.1
Host: your-data-host
Authorization: Bearer <token for a user of the tenant>
TenantId: <tenant id>
AppId: <app id>
EnvironmentId: <environment id>
Content-Type: application/json

{ "EntityName": "Vendor", "SelectedFields": ["Id", "Name"], "TopCount": 10 }

Result The data engine scopes the query to that tenant, app and environment, then applies the user's role scopes.

Custom Front-end

Sign users in from your own front end

Problem
Your branded app needs sign-in without building an identity service.
Solution
Use the identity slice of the client SDK, which stores and refreshes tokens for you.
sign-in.page.ts
import { inject } from '@angular/core';
import { TAF_IDENTITY } from 'taf-client-angular';

const identity = inject(TAF_IDENTITY);

const result = await identity.signIn(email, password, /* rememberMe */ true);
// Later calls through TAF_DATA carry the token; a 401 triggers one refresh and a retry.
// identity.signOut() ends the session.

Result Users sign in with their TechAppForce account, and tokens are refreshed automatically.

TAFI

Ask TAFI for a plan before any change

Problem
You want AI help, but no surprises in your application.
Solution
Start in Plan mode, review the proposed changes, then switch to Agent mode to apply them one confirmation at a time.
TAFI, Plan mode
You:   Add an approval step for vendor onboarding: procurement, then finance.
TAFI:  Plan (nothing changed yet)
       1. Create entity Vendor (6 fields)
       2. Add approval process: Procurement, then Finance
       3. Notify the requester when decided
       4. Add screen Vendor approval
       Switch to Agent mode to preview and apply each change.

Result Each change is previewed, confirmed by you, applied under your name and read back.

Sample applications Coming soon

Complete applications you can install into Development and take apart, starting with approvals, service desk and field service.

Sample applications are being prepared as installable packs. Until then, the tutorials walk through the same builds step by step.