Pyron exposes a JSON API over HTTPS, so a system you run elsewhere can read your data, make changes in it, and react to things that happen inside it without a person at the keyboard. Every call authenticates as a service account, sees only what that account is allowed to see, and returns JSON in a shape you can depend on.
Authentication
A program authenticates with an inbound API key — the same key covered in inbound API keys. Send the key on every request, either as a bearer token or in a dedicated header:
Authorization: Bearer l1k_<your-key>
X-Api-Key: l1k_<your-key>
Every key starts with l1k_. Pyron shows the full key once, when you issue it, and never again — it keeps only a fingerprint to check the key against, so hold your own secure copy. If a key is lost or exposed, revoke it and issue a fresh one from the inbound keys page.
A key does not carry permissions of its own. It belongs to a service account — a non-human identity an administrator sets up and grants roles to — and it inherits exactly that account's permissions. So a call made with a key reads and writes precisely what its service account is allowed to, the same way a person's roles bound what they can do in the app. A key can be given an expiry and can be revoked at any time; a request bearing an expired, revoked, or disabled key is refused.
Reading data
Reads are ordinary GET requests to endpoints under /api/. Send your key, and the response comes back as JSON, scoped to what your service account may see — the same scoping that decides what a person sees in the app. A key never widens access: anything your service account cannot open in Pyron is absent from the API too.
A list endpoint returns one page of results, not the whole set. Each response carries a link to the next page, so you walk a large result set by following next until there is no next link left:
{
"next": "https://your-tenant.pyron.io/api/<resource>/?cursor=cD0yMDI2",
"previous": null,
"results": []
}
A page holds up to 100 items. Many list endpoints also accept query parameters that narrow the results — matching on a field's value, for instance — so you can ask for the slice you need instead of filtering on your own side.
When a request cannot be served, Pyron returns a matching HTTP status and a JSON body in a consistent shape: a type that classifies the failure, and an errors array describing each problem.
{
"type": "validation_error",
"errors": [
{ "code": "required", "detail": "This field is required.", "attr": "name" }
]
}
Read this shape rather than the human-readable sentence inside it. The type and each entry's code are stable values you can branch on, and attr names the field a problem belongs to.
Writing via commands
Every change you make through the API goes through a command. Rather than a scatter of write endpoints, Pyron exposes one door:
POST /api/commands/<name>/
Here <name> is the change you want — creating an entry, taking a lifecycle action, updating a schema. Send the command's inputs as JSON under a config object; the fields inside config depend on the command:
{
"config": {
"field": "value"
}
}
Sending a change through this one door means it inherits the same guarantees the app relies on:
- Permission checks. Pyron confirms your service account may run the command and, where it matters, that it may act on the specific thing being changed. A caller without the right permission is refused, exactly as in the app.
- Validation before anything changes. The inputs are checked first. A payload with a missing or malformed field is rejected with the error shape shown above, and nothing is written.
- All or nothing. A command makes its whole change or none of it. There is no half-finished state to tidy up afterwards.
- A note of who acted. Every command notes the service account that ran it, so a change made over the API appears in your history beside changes made in the app.
To make a create safe to repeat — after a network retry, or a double-fire on your side — include an idempotency_key of your own choosing alongside the config:
{
"config": {
"field": "value"
},
"idempotency_key": "order-2026-07-20-0042"
}
Send the same key again and Pyron replays the original result instead of making a second change. Choose a key that is unique to the thing you are creating.
Webhooks
A webhook lets Pyron and another system tell each other the moment something happens, rather than one side polling the other. Webhooks run in both directions, and both directions share the same signing scheme so each side can trust the message it receives.
Messages Pyron sends
An automation can post a message to a web address you control when its trigger fires — a high-priority entry logged, a lifecycle action taken. You build one on the webhooks page; this section covers what the receiving end has to do.
Every delivery is signed so you can confirm it came from Pyron and arrived unaltered. Pyron computes an HMAC-SHA256 signature and sends it in two headers alongside the JSON body:
X-Timestamp: 1721452800
X-Signature: sha256=<hex digest>
To verify a delivery on your side:
- Read the
X-TimestampandX-Signatureheaders, and take the raw request body exactly as it arrived — do not reformat it first. - Build the signing input by joining the timestamp and the body with a full stop:
<timestamp>.<raw body>. - Compute an HMAC-SHA256 over that input using the signing secret you set on the outbound connection, and hex-encode the result.
- Compare your digest, with a constant-time comparison, against the digest in
X-Signature— the part aftersha256=. Reject the delivery if they differ.
Reject a delivery whose timestamp is far from the current time as well: a message minted more than a few minutes ago is stale and should not be acted on.
A delivery that fails for a passing reason — your endpoint was briefly unreachable or slow — is retried by the automation engine. A retry re-sends the identical message with the identical timestamp and signature, so your endpoint can recognise a repeat and act on it once. Deduplicate on the signature, and build your handler so that receiving the same delivery twice does no harm. Every attempt and its outcome is kept in the automation's run history.
Messages Pyron receives
An external system can trigger an automation by posting to a web address Pyron gives that automation. The address sits under /api/webhooks/, followed by the path you set when you add the webhook trigger:
POST /api/webhooks/<path>/
The sender posts its JSON body to that address, and the automation runs with the body as its input. Authenticate the call one of two ways:
- With an API key —
Authorization: Bearer l1k_…— whose service account is allowed to run automations. - With an HMAC signature, when the sender is a provider that signs its own callbacks. Pyron verifies the
X-TimestampandX-Signatureheaders against the shared secret using the same scheme described above, and rejects a stale or tampered call.
By default the trigger returns straight away with a 202 response and a run identifier, and the automation continues in the background. An automation built to reply inline instead runs to completion and returns a response you compose — for a sender that needs an answer, not just an acknowledgement.