DevelopersAPI reference

API reference

Every published app serves its own records API at its own address:

https://<app>.phiwebs.app/_host/records

This is the same API the app’s pages use. Your own code can call it too: a script that imports records, a service that reads new orders, a report that counts bookings. The app’s access rules apply to every call, exactly as they do for a visitor.

There is no PhiWebs API for building, changing or publishing an app. That happens in the studio.

Before you start

  • The app must be published. Call the published app’s address. The preview keeps its own, separate records and is not meant for outside callers. See Preview, versions and rollback.
  • Use the model’s name exactly as the app declares it in app/definitions/entities.json, for example Booking. A name the app does not declare answers 404 UNKNOWN_ENTITY_TYPE.
  • Closed by default. A data model that no access rule names cannot be read or written by anyone through this API. To open a model, ask the agent in the studio for a rule, for example “anyone may read Product”.

Who you are to the app

The API decides what a call may do from the access rules and from who is calling.

CallerHowWhat it may do
AnonymousNo credentialWhat the rules allow public, for example reading a menu or creating a contact request.
A visitor of a PhiWebs-sign-in appThe app’s own session, sent by the app’s pagesWhat the visitor’s roles allow.
A person or service of a company-sign-in appAuthorization: Bearer <token> from your company’s identity providerWhat their roles allow.

From your own server, the practical options are:

  • Public rules. Give the model a rule for public with just the verbs you need, for example create for a form that other systems submit to.
  • Company sign-in. If the app signs visitors in with your company’s account (for example Microsoft Entra ID), send a token your identity provider issued for the app. The token must be signed by the issuer the app names, and its audience must be the app’s audience or client ID. The caller then needs a place on the app’s people list, or sign-up must be open. Roles come from the people list, plus the roles claim when its values match roles the app declares.

An app that uses PhiWebs sign-in gives sessions only to people who sign in in a browser. A server cannot get one.

From a browser on another site: the API sends no cross-origin (CORS) headers, so a page on a different site cannot read its answers. Call it from your server, or from the app’s own pages.

Routes

All paths are on the app’s address, under /_host/records. <Model> is the model name; <id> is a record id.

MethodPathWhat it doesRule verb
GET/_host/records/<Model>List records, with filter, sort and paging.list
POST/_host/records/<Model>Create a record. Answers 201.create
GET/_host/records/<Model>/<id>Read one record.view
PATCH/_host/records/<Model>/<id>Change the fields you send.update
DELETE/_host/records/<Model>/<id>Delete one record.delete
GET/_host/records/<Model>/countCount records.count (plain count) or list (with a filter)
GET/_host/records/<Model>/aggregateSum, average or count of a field.list

In a rule, read means list and view, and write means create, update and delete. A rule with own limits the caller to the records they created.

Listing

GET /_host/records/Booking?filter=…&sort=date:desc&limit=20&offset=0
ParameterMeaning
filterA JSON filter object, URL-encoded (below).
sortfield:asc or field:desc. Separate several with commas.
limitRecords per page, 1 to 500. Default 100.
offsetRecords to skip. Default 0.

Without sort, the most recently changed records come first.

{
  "data": [{ "id": "…", "entityType": "Booking", "name": "Ada", "date": "2026-11-20", "createdAt": "…", "updatedAt": "…" }],
  "meta": { "total": 42, "scope": "project", "timestamp": "…" }
}

meta.total counts every match, not only this page. meta.scope is owner when the caller sees only their own records, and project when they see all.

Filters

A filter compares one field, and can combine others with and and or:

{ "field": "status", "operator": "eq", "value": "confirmed",
  "and": [{ "field": "guests", "operator": "gte", "value": 2 }] }

Operators: eq, neq, gt, gte, lt, lte, in (the value is a list), contains and startsWith. contains and startsWith ignore case. Text that looks like a number is compared as a number. Use a dot to reach into nested fields (address.city).

Count and aggregate

GET /_host/records/Booking/count
GET /_host/records/Order/aggregate?fn=sum&field=total&filter=…

fn is count, sum or avg. sum and avg need a field. The answer is:

{ "data": { "fn": "sum", "field": "total", "value": 1280.5, "count": 37 }, "meta": { "scope": "project", "timestamp": "…" } }

Creating and changing

Send the fields as a JSON object, either as they are or wrapped in { "data": { … } }. The body may be up to 1 MB.

curl -X POST "https://<app>.phiwebs.app/_host/records/ContactRequest" \
  -H "content-type: application/json" \
  -d '{ "name": "Ada", "email": "ada@example.com", "message": "Hello" }'
{ "id": "3f0c…", "data": { "id": "3f0c…", "entityType": "ContactRequest", "name": "Ada", "…": "…" } }
  • id, entityType, createdAt and updatedAt are set by the app. Values you send for them are ignored.
  • A model’s required fields must be present on create and cannot be emptied on update.
  • If the model has an owner field (for example owner or createdBy) and you leave it empty, it is filled with the signed-in caller.
  • PATCH changes only the fields you send. DELETE answers { "data": { "id": "…", "deleted": true } }.

Errors

Errors have one shape:

{ "error": { "code": "FORBIDDEN", "message": "You may not create Booking records." } }
StatusCodeMeaning
400VALIDATION_FAILEDA required field is missing or empty.
400INVALID_QUERYfilter, fn or field is not valid.
400INVALID_JSONThe body is not a JSON object.
401SIGN_IN_REQUIREDThe rules need a signed-in caller, the app requires sign-in, or the session or token is no longer valid.
403FORBIDDENThe caller is known, but the rules do not allow this.
403NOT_INVITEDThe app lets in only invited people, and this caller is not on its people list.
404UNKNOWN_ENTITY_TYPEThe app declares no such model.
404NOT_FOUNDNo such record, or one the caller may not see.
404APP_NOT_FOUNDThe app is not published right now.
405METHOD_NOT_ALLOWEDThe route does not take that method.
413PAYLOAD_TOO_LARGEThe body is over 1 MB.

The owner’s view

As the app’s owner, you also see and edit every record in the studio’s App → Records panel, whatever the rules say. That panel is for people. It is not an API for your code. See Working in the studio.

See also