Push and publish a version
MarkdownOnce the files are written, a version goes live in a few calls: push it, fix what the validation found, try one of its actions, then put it in production.
The examples use $TOKEN, $API_KEY and $APP_ID as Build an
application defines them.
Push a version
Section titled “Push a version”With the files on a machine
Section titled “With the files on a machine”Reserve a deposit, then PUT the archive to the URL it returns. The URL carries its own authorisation and works once, so the script that uploads holds no key of yours.
VERSION=$(curl -s -X POST "https://api.eu.apigami.com/v1/apps/$APP_ID/versions" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"mode":"archive"}')
URL=$(printf '%s' "$VERSION" | jq -r '.upload.url')VERSION_ID=$(printf '%s' "$VERSION" | jq -r '.version_id')
cd project && zip -qr - . | curl -s -X PUT "https://api.eu.apigami.com$URL" \ -H 'Content-Type: application/zip' --data-binary @-
curl -s "https://api.eu.apigami.com/v1/apps/$APP_ID/versions/$VERSION_ID" -H "Authorization: Bearer $TOKEN"Reserving receives nothing: the version exists once the PUT lands.
Without a terminal
Section titled “Without a terminal”Send the changes instead of the files. POST /v1/apps/{app}/versions with base_version_id and a
list of changes builds a new version from an existing one.
{ "base_version_id": "VERSION_ID", "changes": [ { "op": "write", "path": "actions/basket.ts", "content": "export const readBasket = …" }, { "op": "replace", "path": "main.ts", "old_text": "'POST /search'", "new_text": "'POST /search'" }, { "op": "delete", "path": "actions/old.ts" } ]}A replace whose old_text appears zero times or more than once is refused, and nothing is
written: an edit that lands in the wrong place is worse than an edit that does not land.
Only bytes your environment can actually read can be sent. An image you can see in a conversation is not a file you can upload — ask for its path, or have the file written as text.
Add one file to a version
Section titled “Add one file to a version”A logo, or a corrected manifest, without resending the rest: reserve a deposit with mode: "file",
a base_version_id and the path the bytes land on, then PUT the bytes.
curl -s -X POST "https://api.eu.apigami.com/v1/apps/$APP_ID/versions" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d '{"mode":"file","base_version_id":"'"$VERSION_ID"'","path":"public/logo.svg"}'Fix what the validation found
Section titled “Fix what the validation found”Receiving a version and being a valid project are two different results. A version whose manifest is missing or wrong is kept, and its diagnostics name the file, the field and the fix:
{ "version_id": "…", "state": "ready", "validation": { "status": "invalid", "errors": 1, "diagnostics": [ { "code": "missing_operation_id", "file": "manifest.json", "pointer": "/paths/~1basket/post", "message": "Give this operation an `operationId`: it is the name of the action." } ] }}Fix the file it names with one replace, on the same base_version_id. Nothing unchanged is sent
again, and you never have to read the whole project back to correct one line.
Run it, deploy it, publish it
Section titled “Run it, deploy it, publish it”# Run an action of a precise version — this is how you try one. It takes an API key of the application.curl -s -X POST "https://api.eu.apigami.com/v1/apps/$APP_ID/actions/searchProducts" \ -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \ -H "X-Apigami-Version: $VERSION_ID" \ -d '{"query":"coffee"}'
# Deploy it, and check its health.curl -s -X PATCH "https://api.eu.apigami.com/v1/apps/$APP_ID/versions/$VERSION_ID" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"deployed":true}'
# Put it in production. The same call on an earlier version is the rollback.curl -s -X PATCH "https://api.eu.apigami.com/v1/apps/$APP_ID/versions/$VERSION_ID" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"production":true}'Work in bounded chunks. Apigami waits up to 900 seconds for an action, fifteen minutes, and less
when another run calls it: a child never outlives its parent. Within that time, each request has 300 seconds of
computing time, and an instance 128 MB of memory shared by the requests it serves at once
(Limits); past either, the run fails with a sentence that names the limit. Its caller waits thirty seconds, then gets 202 { run_id, state: "running" }
while the run goes on; GET /v1/runs/{run_id} reads its outcome with a member’s credential, never with a key
or a page’s token. So have each run do a short chunk, save its result and its progress in the database, and
let a trigger or the next call carry on from there.
There is no try route: trying is running, with X-Apigami-Version naming the version. A member of
the owning group may name one; everyone else always reaches the version in production.
A version that has no deployment yet runs in a sandbox, a trial, which is what lets you try code before
you deploy it. The answer says which of the two served it, and only a run served by the version’s own
deployment that answered below 500 counts towards publishing.
Publication is refused in three cases, and each one says what to do: a version none of whose actions
has answered below 500 on its own deployment, a version whose last GET /healthz did not answer
200, and a version one of whose pages answered an error (page_failed). After GET /healthz, a
deployment asks each page with a relative page_url once, with no token and no cookie, as anyone
could; a page answering 400 or more, or loading apigami.js from /apigami.js on your app’s own
address, blocks publication, and publishing asks it again first. A page
answering 200 with a body is not refused, but the answer reminds you that anyone can read what it
rendered on the server. A deployment answers with a publish block carrying publishable, what is blocking and the one
sentence to act on, so you do not have to ask a second time.
Deploying and publishing are one route, PATCH /v1/apps/{app_id}/versions/{version_id}, whose body
takes deployed: true, production: true or both, and nothing else. Sent together, the deployment
comes first, but a version deployed for the first time has never been run, so the publication answers
409: run an action between the two. With the MCP tools, app_deploy and app_publish take
app_id and version_id, and app_publish on an earlier version is the rollback. The answer is the
version: its state, production, health, tried_at, review and a publish block with
publishable, its blockers and the next step.
An application that is unlisted or public goes to an Apigami administrator for review before another
group may call it. Publishing it is a request for review: production does not change, for anyone, your
group included, and the answer carries "review": { "state": "pending_review" } with a note. An
administrator then puts the version in production, or answers rejected with a reason. Every
version you publish is reviewed, a rollback included; a private application is published at once.
app_status (GET /v1/apps/{app}) follows the review, and you can write to
contact@apigami.com about it.
Visibility: private, unlisted or public
Section titled “Visibility: private, unlisted or public”Who may connect is the application’s visibility: private, your group only, the default;
unlisted, anyone with its link; public, also listed in the catalogue. Opening it to others is the
person’s decision: an assistant asks before raising it, and never lowers it on its own. It is
app_settings with app_id and visibility, or:
curl -s -X PATCH "https://api.eu.apigami.com/v1/apps/$APP_ID" \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"visibility":"unlisted"}'It answers the application. POST /v1/apps takes the same field at creation,
{"name":"Watch","visibility":"private"}; any field these two routes do not know answers 400. Once
the application is unlisted or public, each publication is a request for review, as above. A version
already in production when you raise it, published while the application was private, was never reviewed: until a
reviewed version replaces it, it keeps serving your group alone, outside the catalogue, and anyone else is refused
with 403 app not reviewed. Publish a new version to ask for that review.
Try an action that reaches a site through the browser
Section titled “Try an action that reaches a site through the browser”An action that uses a site session runs in one of your user’s browsers. With your application’s key,
name the user with X-Apigami-User-Ref, their user_ref, and to pick one of their browsers add
X-Apigami-Install-Ref; without it, Apigami takes the browser POST /call would take for the first
site the version reaches through a browser. Both are headers, because the body and the query string
are your action’s own. Under a run token neither is accepted: the run already names its browser. The same
holds for the body of POST /call: with your application’s key, user_ref is required and install_ref
optional; when your action calls it with the token handed in X-Apigami-Run, leave both out, since naming
one answers 400.
The first time you try such an action on a site, the person may see Apigami’s approval window for that
site, even just after connecting to your application: the connection approves your sites in the same
click only when it attached their browser in Apigami’s window (Site approval). Tell
them to expect it and to click Authorize; until they answer, the action’s POST /call gets 403
with consentPending: true, and running it again afterwards goes through.
When something fails
Section titled “When something fails”Three places to look, and they answer different questions:
GET /v1/apps/{app}/logs?version=…— what a deployed version printed. A version whose deployment was refused printed nothing there: the refusal says why.GET /v1/runs/{run}— what the invocation returned: its state, the status your code answered, which environment served it, and the lines a trial kept when a sandbox ran it.GET /v1/apps/{app}/runs?version=…— what ran and what failed, including a trigger that fired with nobody watching.
What your code wrote to its own database is read with POST /v1/apps/{app}/database/queries:
Apigami is not on that path.
A deployment that failed: its build, or its server at startup
Section titled “A deployment that failed: its build, or its server at startup”A deployment that could not be brought up answers 422, with the step that failed and what it printed:
- the build: an import that does not resolve, a package refused, with the end of the build’s log;
- the migrations: the file and the statement that failed (Migrations);
- the code as it loads: input or output at the top level of a file, an exception thrown there, or
[apigami] Export default { fetch(request, env, ctx) } from your entrypoint.
Nothing ran, so GET /v1/apps/{app}/logs?version=… (app_logs) has nothing from it. Run one of its actions
with X-Apigami-Version: a version with no deployment runs in a sandbox, whose answer and run
(GET /v1/apps/{app}/runs, app_runs) carry the same failure and what the code printed. A version written
for manifest v1 is refused with 409 before any of this (Retired fields).