Skip to content

No-code & AI tools

Not every useful thing built on the Packt Platform needs an engineer. The platform speaks plain HTTP and emits ordinary webhooks, which means the tools a business user already reaches for — Zapier and n8n for automation, Lovable and Replit for throwing together a quick interface — can talk to it directly. You can wire a Slack alert to fire when a Contract is about to expire, stand up a search box over your own content in an afternoon, or draft product copy from material already in the lake, all without writing a service.

This page is for the person doing exactly that. It assumes no programming background: it explains how to get the credentials you need, how to connect a no-code tool to the platform's event stream, and how to point an AI app-builder at the platform so it can build against the real API. Three end-to-end recipes then walk through common jobs from empty canvas to working automation. Where a payload is shown it is real and copy-paste valid, because the point is that you can paste it into a tool and it works.

Two facts make all of this safe to hand to a business user. First, every credential is tenant-scoped — a token can only ever see and touch your own organisation's content, never anyone else's. Second, rights are enforced on the server, not in the app you build: the platform resolves what each piece of content may be used for and applies that on every read, search, and AI surface. A prototype cannot overreach, however enthusiastically it is wired together, because the boundary lives below the tool. That guarantee is what lets you experiment freely.

flowchart LR
    subgraph tools [Your no-code / AI tools]
        Z["Zapier · n8n<br/>automations"]
        L["Lovable · Replit<br/>prototype UIs"]
    end
    W["Webhooks<br/>(CloudEvents)"] --> Z
    L -->|HTTPS + Bearer token| API["Public API<br/>api.packtpub.services"]
    API --> CL["Content Lake<br/>content · rights"]
    EG["Event egress"] --> W
    CL -.->|events| EG

Getting credentials

Every request to the platform carries a token — a short-lived, signed credential issued by WorkOS, the platform's identity provider. There are no API keys to copy from a settings screen and no tenant header to set by hand; the token itself says who you are and which tenant you belong to. Before a no-code tool can call anything, someone with admin rights in your tenant registers the application that will hold that credential.

Which kind of application depends on who the automation acts as. There are two shapes a business user will meet:

  • An OAuth application acts on behalf of a person. The user signs in through a hosted consent screen and approves the scopes the app asks for; the token the app receives carries that person's identity. This is the right choice when a tool should only ever do what the signed-in user is already allowed to do.
  • An M2M client (machine-to-machine) acts as itself — an unattended automation with no human in the loop. A Zapier zap that runs on a schedule, or an n8n workflow reacting to webhooks, uses M2M client credentials. It authenticates with a client ID and secret and receives a token scoped to your tenant.

Either way the steps are the same, and a tenant admin does them once:

flowchart LR
    A["Tenant admin"] --> R["Register app<br/>(OAuth or M2M)"]
    R --> S["Choose scopes<br/>consented / registered"]
    S --> C["Credentials issued<br/>client ID + secret"]
    C --> T["Tool exchanges<br/>for short-lived token"]
    T --> API["Calls Public API"]
  1. Register the application. In your tenant's admin area, create either an OAuth application (for on-behalf-of-a-user tools) or an M2M client (for unattended automations).
  2. Choose the scopes. Scopes are the named permissions a token may carry — for example, permission to read content or to run a search. For an M2M client the admin registers the scopes directly; for an OAuth application the user consents to them at sign-in. Ask for the least you need: a search prototype needs read and search scopes, not write.
  3. Collect the credentials. The client ID and secret are shown once. Store the secret in your tool's credential vault — Zapier, n8n, Lovable, and Replit all provide one — never in a workflow step or in source you might share.
  4. Let the tool fetch tokens. The tool exchanges the client ID and secret for a short-lived access token and refreshes it automatically. Access tokens live at most an hour by design; nothing you hold is a long-lived password.

Tenant-scoped tokens and server-side rights make prototypes safe

A token is bound to exactly one tenant — yours — and the platform reads the tenant from the token, not from anything the caller sends. A prototype therefore cannot reach another organisation's content even by accident. On top of that, content rights are resolved and enforced server-side: a search only ever returns hits the token is allowed to see, a read carries its own resolved usage terms, and consequential actions (anything AI-facing, publishing, distribution) are re-checked against the governing contract at the point of use. The most a mis-wired prototype can do is ask for something and be told no. This is why it is safe to let business users build directly.

Every call then carries the token as a bearer credential:

GET /v0/content-lake/resources/res_ml_go_ch3?fields=id,title,rights HTTP/1.1
Host: api.packtpub.services
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/json

The tenant comes from the token's tenant_id claim and the permissions from its scopes claim — there is nothing else to configure. The full credential contract is on the Authentication page.

Connecting Zapier and n8n to platform events

The platform announces change by publishing events — a Resource was created, a Product was published, a contract's rights changed. Internally these travel on Amazon EventBridge, but you never touch that. What reaches you is the Eventing service's external catalogue: curated events with their own packt.* names and workflow-friendly payloads, delivered in a CloudEvents envelope — the open, vendor-neutral webhook format that Zapier, n8n, and most automation tools understand out of the box — to the endpoints you have subscribed. This external delivery is the event egress, and it is the only place CloudEvents appears.

The egress is upcoming

The event egress is specified but not yet built. The flow below is the contract it will ship against.

flowchart LR
    CL["Content Lake<br/>Contracts · Products"] -->|EventBridge| EG["Eventing service<br/>egress"]
    EG -->|CloudEvents webhook| ZAP["Zapier<br/>Catch Hook"]
    EG -->|CloudEvents webhook| N8N["n8n<br/>Webhook node"]
    ZAP --> ACT["Slack · email · sheet<br/>your action"]
    N8N --> ACT

To receive events in a no-code tool:

  1. Create a webhook trigger in your tool. In Zapier this is the Webhooks by Zapier → Catch Hook trigger; in n8n it is the Webhook node. Either gives you a unique URL to receive POST requests.
  2. Subscribe that URL to the event types you care about. A tenant admin registers the webhook endpoint with the event egress and names the event types it should receive — for example packt.contract.v1.renewal_approaching. Delivery is filtered to exactly those types.
  3. Read the CloudEvents envelope. Each delivery is a JSON body with the event type, a source, an id for de-duplication, a time, and a data object carrying the domain payload. Map the fields you need into the next step of your workflow.

Delivery is at-least-once — handle duplicates

The same event may arrive more than once. Every CloudEvents delivery carries a stable id; if your workflow does something that must not happen twice (posting a message, creating a row), branch on that id and skip an id you have already seen. Most tools can store the last-seen id in a lookup table or data store step.

External event names follow a fixed grammar, packt.<entity>.v<major>.<verb> — business entities, past-tense verbs, and no internal service names, so platform refactors never rename the events your workflow is built on. The catalogue entries a no-code tool subscribes to most often are:

Event type Fires when
packt.contract.v1.renewal_approaching A contract is nearing its expiry / renewal date
packt.contract.v1.rights_changed A contract's resolved rights change
packt.content.v1.registered New content is registered in the lake
packt.product.v1.published A product reaches the PUBLISHED state

The authoritative external catalogue lives on the Events & webhooks page. The per-service event docs it links to describe the platform's internal buses — webhooks never receive those events directly.

Prototyping a UI with Lovable or Replit

AI app-builders like Lovable and Replit generate a working application from a description of what you want. They build far better against the platform when you give them two things to read: the machine-readable OpenAPI specification, which describes every endpoint, parameter, and response shape precisely, and this documentation site, which explains the concepts and business rules the raw spec cannot. Point the tool at both.

The OpenAPI specification is published at a stable URL:

https://api.packtpub.services/v0/openapi.json

A good prompt to an AI builder names both sources and states the goal plainly:

Build a React app that searches our content library.

Use the Packt Platform Public API, described by the OpenAPI spec at
https://api.packtpub.services/v0/openapi.json. Concepts and rules are
documented at https://docs.packtpub.services. Search is a POST to
/v0/content-lake/search. Authenticate with a Bearer token I will
provide as an environment variable — do not hard-code it. Show each
result's title and a snippet, and respect that some results may carry
usage restrictions.

Give the builder the spec, not guesses

An AI builder left to invent endpoints will hallucinate plausible but wrong ones. Handing it the OpenAPI URL pins it to the real contract: correct paths, correct field names, correct enums. Handing it this site on top teaches it why — that content is held in Works under Contracts, that reads carry resolved rights, that search paginates by session. The combination is what produces a prototype that actually runs.

Keep the token out of the generated code. Every builder supports environment variables or a secrets store; put the credential there and have the app read it at runtime, so nothing sensitive lands in a file you might share or deploy publicly.

Three worked recipes

The rest of this page is three complete jobs, each starting from an empty canvas. They combine the pieces above: credentials, the event egress, and the Public API.

Recipe 1 — Slack alert when a contract nears expiry

Goal. Post a message to a Slack channel whenever one of your contracts is approaching its renewal date, so the rights team can act before it lapses.

How it works. The external catalogue includes packt.contract.v1.renewal_approaching, published when a contract nears expiry. You catch that event in Zapier and turn it into a Slack message — no polling, no scheduled checks.

flowchart LR
    C["Contract nears<br/>renewal date"] -->|renewal_approaching| EG["Event egress<br/>(CloudEvents)"]
    EG -->|webhook| Z["Zapier<br/>Catch Hook"]
    Z --> F["Format message"]
    F --> S["Post to Slack<br/>#rights-alerts"]

Steps.

  1. In Zapier, add a Webhooks by Zapier → Catch Hook trigger. Copy the URL it gives you.
  2. Ask your tenant admin to subscribe that URL to packt.contract.v1.renewal_approaching on the event egress.
  3. Trigger a test delivery. Zapier captures a sample so you can map fields. The CloudEvents envelope looks like this:
{
  "specversion": "1.0",
  "id": "0197f8c1-2e4a-7b9d-8f01-6a3c5d2e4b17",
  "type": "packt.contract.v1.renewal_approaching",
  "source": "https://events.packtpub.services",
  "subject": "ctr_chandra_2026",
  "time": "2027-10-03T00:05:00Z",
  "datacontenttype": "application/json",
  "data": {
    "tenant_id": "packt",
    "contract_id": "ctr_chandra_2026",
    "expiry_date": "2028-01-01T00:00:00Z",
    "notice_cutoff": "2027-10-03T00:00:00Z",
    "auto_renew_months": 12
  }
}
  1. Add a Slack → Send Channel Message action. Point it at #rights-alerts and compose the text from the mapped fields, e.g. "Contract {{data__contract_id}} is up for renewal — notice must be served by {{data__notice_cutoff}}, expiry {{data__expiry_date}}."
  2. Turn the zap on. Every future renewal_approaching event now posts automatically.

Field names come from the event catalogue

The exact fields on a catalogue entry are defined in the external catalogue — see Events & webhooks. Map from what the test delivery shows rather than assuming a field exists.

Recipe 2 — A search UI over your tenant

Goal. A simple web page where someone types a query and sees matching content from your organisation — a prototype internal search.

How it works. Search is a single POST to /v0/content-lake/search. The results are already scoped to your tenant by the token and already filtered by rights, so the UI only has to send the query and render what comes back.

flowchart LR
    U["User types<br/>a query"] --> APP["Prototype UI<br/>(Lovable / Replit)"]
    APP -->|POST /v0/content-lake/search| API["Public API"]
    API --> R["Rights-filtered<br/>results"]
    R --> APP

Steps.

  1. Register an M2M client (or an OAuth application if the page should act as the signed-in user) with content read and search scopes. Store the credentials in your builder's secrets store.
  2. Prompt Lovable or Replit to build the page, giving it the OpenAPI URL and this site as described above. The core call it needs to make is:
POST /v0/content-lake/search HTTP/1.1
Host: api.packtpub.services
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Content-Type: application/json

{
  "query": "machine learning with go",
  "search_mode": "hybrid",
  "limit": 20
}
  1. Render each hit's title and snippet from the response. Results carry their resolved rights inline, so if you want to show a badge for restricted items the data is already there — you do not fetch it separately.
  2. For "load more", search paginates by session: the first response returns a search_id; send it back on the next request and previously returned hits are excluded automatically. The generated app should thread search_id through rather than track offsets.

The rights filter is not yours to implement

You never write the logic that decides which results a user may see — the platform applies it before the response leaves the server. A prototype that forgets to check rights is still safe, because there was never any unauthorised content in the response to begin with.

Recipe 3 — Auto-draft product blurbs, respecting AI rights

Goal. Draft marketing blurbs for products from the content already in the lake, using an AI model — while honouring whether that content is actually cleared for AI use.

How it works. This recipe reads content in PCF (the platform's structured content format), checks the resolved AI_RETRIEVAL usage term on what it reads, and only feeds the text to a model when that term permits it. The rights check is the whole point: the platform tells you what is allowed, and the automation respects the answer.

flowchart LR
    START["Pick a Work's<br/>content"] --> READ["Read Resource<br/>(PCF + rights)"]
    READ --> CHECK{"AI_RETRIEVAL<br/>PERMITTED?"}
    CHECK -->|yes| GEN["Send text to model<br/>→ draft blurb"]
    CHECK -->|no| SKIP["Skip — record<br/>'not cleared for AI'"]

Steps.

  1. Read the Resource, asking for its rights alongside its content. A read carries the resolved usage view, so one call gives you both:
GET /v0/content-lake/resources/res_ml_go_ch1?fields=id,title,rights HTTP/1.1
Host: api.packtpub.services
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Accept: application/json
  1. Check the resolved AI_RETRIEVAL term before using the text. Among the sixteen usage types the platform resolves, AI_RETRIEVAL governs whether content may be surfaced to an end user inside an AI answer. Look for it in the resolved rights and proceed only when its status is PERMITTED:
{
  "id": "res_ml_go_ch1",
  "title": "Machine Learning with Go — Chapter 1",
  "rights": {
    "usable": true,
    "usage_terms": [
      { "usage_type": "AI_RETRIEVAL", "status": "PERMITTED" },
      { "usage_type": "AI_TRAINING", "status": "PROHIBITED" }
    ]
  }
}

If AI_RETRIEVAL is PROHIBITED (or CONDITIONAL and the condition is not met), do not send the content to the model. Record that it was skipped and move on. AI_TRAINING is a separate term — permission to retrieve is never permission to train.

  1. When permitted, fetch the readable text. Ask the Public API edge for Markdown with an Accept: text/markdown header, and it renders the PCF for you — handy for pasting into a model prompt.
  2. Send that text to your AI step (an OpenAI, Anthropic, or built-in AI action in your tool) with a prompt like "Write a 60-word marketing blurb for this chapter." Save the draft against the product for a human to review.

The platform decides, and re-checks at the point of use

Even if a prototype skipped the check, an AI-facing action is re-validated server-side against the contract versions the content's rights were resolved from. But do not lean on that as a safety net: reading and honouring the AI_RETRIEVAL term is how you keep the automation correct and transparent about why something was skipped. Retrieval permission is per-Work, resolved from its inbound Contract, and can change when the contract does — so check on every run, not once.

  • Authentication — the full token and credentials contract for humans, apps, and M2M clients.
  • Events — the event grammar, buses, and the CloudEvents egress that feeds webhooks.
  • API — the Public API surface, content negotiation, and pagination in full.
  • Recipes — more worked automations against the platform.
  • Works & rights — how the AI_RETRIEVAL term and the rest of the rights model are resolved.
  • Explorer — Chapter I — the platform and its service map, visualised.