Skip to main content

Custom API Definition

Schema id: custom-api@1 Machine-readable schema: custom-api.schema.json

The canonical definition of a Custom API (Section 6.9): a versioned HTTP contract an environment publishes to systems outside the platform, owned by a Solution and travelling in its package. A definition is validated identically whether it comes from the Admin Control Center, an API call, an AI assistant, or a Solution import.

Shape​

FieldTypeRequiredNotes
logicalNamestringyesPublisher-prefixed, lower snake-case. Stable across versions.
displayNamestringyesThe OpenAPI operation's summary.
publisherPrefixstringyesAlso a segment of the served path.
versionintegeryes1–999. Served as the v{version} path segment.
routestringyesBelow the version segment, no leading slash. A braced segment is a path parameter.
verbstringyesGet, Post, Put, Patch, Delete.
scriptstringyesThe BS Lang this API runs. There is one implementation and this is it.
requiredScopestringyesThe scope a caller's App Registration must hold.
descriptionstring | nullnoThe OpenAPI operation's description.
requestSchemaobject | nullnoA JSON Schema, checked on every call. Refused for a body-less verb. Null means the script's own getInput names are the contract (published in OpenAPI; on a portal call, a closed list).
responseSchemaobject | nullnoA JSON Schema, published in the OpenAPI document.
responseContentTypestringnoapplication/json, text/plain or text/html — what the contract normally answers with, and what the generated OpenAPI publishes.
isEnabledbooleannoA disabled definition is stored and exported, and returns 404.

Identity is a pair​

(logicalName, version) identifies the API; (publisherPrefix, version, verb, route) is its address. par_approve_invoice v1 and v2 are one contract at two ages and answer side by side while an integration partner migrates — so a second definition sharing a name and a version is a duplicate, while one sharing the name with a new version is the next version. Two definitions may never share an address, whatever they are called.

The served path​

/api/{publisherPrefix}/v{version}/{route}

Reached today under an environment — /environments/{environmentId}/api/par/v1/invoices — because a client-credentials token names a tenant rather than one of its environments. The shorter public shape arrives with the Gateway, which maps a hostname onto an environment.

What runs​

One thing: the script. A definition names a route and the BS Lang that answers it, and there is nothing else to choose. It reads the request with getInput, answers with setOutput or setBody, chooses a status and content type with setStatus and setContentType, sets headers with setHeader, reaches other tables with the record functions, and refuses with throwValidationError.

It used to choose between four implementation kinds — a direct table operation, an Automation Flow, a Function, and a custom plugin message. Three of them could be defined but never dispatched, so a call answered 501 naming the iteration it waited on, and only the fourth ran. That is gone. A contract whose behaviour is the maker's own code is simpler to explain, simpler to version, and the only one of the four that could express something the platform had not anticipated — and there is no 501 any more, because there is no unwired kind left to produce one.

Logic worth sharing between two APIs is a solution script, imported by name, not a fifth kind.

Rules a schema cannot state​

Checked when the definition is saved, in addition to the schema above:

  • The declared responseContentType is one the platform can serve.
  • A Read, Update or Delete route declares {id} — it is the only place the record's id can come from.
  • A body-less verb has no request schema. A body-carrying verb may leave it null.
  • The request and response schemas compile as JSON Schema.
  • The script parses as BS Lang, and every module it imports resolves to a solution script.

Example​

{
"logicalName": "par_create_invoice",
"displayName": "Create an invoice",
"publisherPrefix": "par",
"description": "Creates an invoice from a validated body.",
"version": 1,
"route": "invoices",
"verb": "Post",
"requestSchema": {
"type": "object",
"additionalProperties": false,
"required": ["par_name", "par_amount"],
"properties": {
"par_name": { "type": "string", "maxLength": 200 },
"par_amount": { "type": "number", "exclusiveMinimum": 0 }
}
},
"responseContentType": "application/json",
"requiredScope": "modelapps.write",
"isEnabled": true,
"script": "var id = createRecord('par_invoice', {
par_name: getInput('par_name'),
par_amount: getInput('par_amount')
});
setOutput('id', id);"
}

The body is checked against requestSchema before the script runs, so getInput reads a shape that has already been validated rather than one every script has to defend against itself.

An input can be an object or a list​

getInput hands the script whatever the property holds — a string, a number, a boolean, or an object or array carried whole, which a script walks by member (f.fileName) and by length(). So a contract that describes a structured input in its requestSchema can actually read one:

"par_document": {
"type": "object",
"additionalProperties": false,
"required": ["fileName", "contentType", "contentBase64"],
"properties": {
"fileName": { "type": "string", "maxLength": 260 },
"contentType": { "type": "string", "maxLength": 200 },
"sizeBytes": { "type": "integer" },
"contentBase64": { "type": "string", "maxLength": 5600000 }
}
}

Until portals round 10 an object arrived as its raw JSON text, which BS Lang has no way to parse — so a maker who declared the shape they wanted was worse off than one who flattened it into loose properties, which is the opposite of what a schema is for. Nesting is bounded by the JSON parser's own 64-level limit, before any of this is reached.

What it never does​

A Custom API does not widen access. The script's record functions go through the same door as the platform's own screens and run as the caller, so their permission on a table and the data policy behind it decide what comes back. A curated contract gives an outside system a stable view, never a larger one.