Complete reference for AI agents. Base URL: https://api.chaprola.org
Scale: Proven on 1,050,000 records (NYC taxi dataset, analyzed in about 5 seconds). The compact instruction set is small by design — learnable in one context window, powerful enough to sort, filter, aggregate, and report across millions of rows. Fixed-record VM with O(1) field access and 10B instruction budget per execution.
Four HTTP calls: register, import, compile, run.
# 1. Register (returns JWT token)
curl -s -X POST https://api.chaprola.org/register \
-H "Content-Type: application/json" \
-d '{"username": "my-agent", "passcode": "my-secret-passcode", "never_expires": true}'
# Response: {"status":"registered","username":"my-agent","token":"<JWT>"}
# 2. Import JSON data
curl -s -X POST https://api.chaprola.org/import \
-H "X-Username: my-agent" \
-H "X-Access-Code: <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"userid":"my-agent","project":"demo","name":"STAFF",
"data":[{"name":"Alice","role":"admin"},{"name":"Bob","role":"viewer"}]}'
# 3. Compile a Chaprola program
curl -s -X POST https://api.chaprola.org/compile \
-H "X-Username: my-agent" \
-H "X-Access-Code: <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"userid":"my-agent","project":"demo","name":"SHOW",
"source":"OPEN \"STAFF\"\nGET\nPRINT 0\nEND"}'
# 4. Run the compiled program
curl -s -X POST https://api.chaprola.org/run \
-H "X-Username: my-agent" \
-H "X-Access-Code: <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"userid":"my-agent","project":"demo","name":"SHOW","primary_file":"STAFF","record":1}'
All endpoints except /hello, /register, /login, /check-username, and /report require two headers:
X-Username: my-agent
X-Access-Code: <JWT token from /register or /login>
The userid in the request body must match the authenticated user. Mismatched userids return 403 Forbidden.
Tokens expire 90 days after issue. Include "never_expires": true in /register or /login to get a permanent token. Previous tokens remain valid after a new login.
| Endpoints | Rate | Burst |
|---|---|---|
| Auth endpoints (/register, /login, /check-username) | 5 req/s | 10 |
| All other endpoints | 20 req/s | 50 |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /hello | No | Health check |
| POST | /register | No | Create account, get JWT token |
| POST | /login | No | Login, get new token (old tokens stay valid) |
| POST | /check-username | No | Check username availability |
| POST | /delete-account | No | Delete account + all data (passcode required) |
| POST | /import | Yes | JSON → Chaprola .F + .DA files |
| POST | /import-url | Yes | Presigned S3 upload URL (large files up to 5 GB) |
| POST | /import-process | Yes | Process staged S3 file |
| POST | /export | Yes | Chaprola .DA + .F → JSON |
| POST | /list | Yes | List files by project + wildcard |
| POST | /compile | Yes | Compile .CS source → .PR bytecode |
| POST | /run | Yes | Execute .PR bytecode program |
| POST | /publish | Yes | Make a .PR publicly accessible via /report |
| POST | /unpublish | Yes | Remove public access from a .PR |
| POST | /report | No | Run a published .PR (public, no auth) |
| POST | /email/inbox | Yes | List emails in your mailbox |
| POST | /email/read | Yes | Read a specific email by message_id |
| POST | /email/send | Yes | Send email from your @chaprola.org address |
| POST | /email/delete | Yes | Delete an email by message_id |
Create a new account. Returns a JWT token for all future requests.
{"username": "myagent", "passcode": "my-secret-passcode", "never_expires": true}
| Field | Required | Description |
|---|---|---|
username | Yes | 3-40 chars, alphanumeric + hyphens/underscores, must start with a letter |
passcode | Yes | 8-128 characters |
never_expires | No | Set to true for a permanent token. Default: 90-day expiry |
Response (201):
{"status": "registered", "username": "myagent", "token": "<jwt>", "token_expires": 1749600000}
Errors: 400 (invalid input), 409 (username taken)
Log in and get a new token. Invalidates previous token.
{"username": "myagent", "passcode": "my-secret-passcode", "never_expires": true}
Response (200):
{"status": "authenticated", "username": "myagent", "token": "<jwt>", "token_expires": 1749600000}
Errors: 401 (invalid credentials)
{"username": "myagent"}
Response: {"available": true}
Permanently delete account, all files, and all emails. Requires passcode confirmation.
{"username": "myagent", "passcode": "my-secret-passcode"}
Health check. Optional query param: ?name=Alice
Import JSON data into Chaprola format (.F format file + .DA data file).
{
"userid": "myagent",
"project": "myproject",
"name": "EMPLOYEES",
"data": [
{"first_name": "Alice", "salary": 95000},
{"first_name": "Bob", "salary": 87000}
],
"expires_in_days": 90
}
| Field | Required | Description |
|---|---|---|
userid | Yes | Must match authenticated user |
project | Yes | Project name |
name | Yes | File name (without extension) |
data | Yes | Array of flat JSON objects |
expires_in_days | No | Days until auto-deletion. Default: 90 |
Response (200):
{
"status": "ok",
"filename": "EMPLOYEES",
"records": 2,
"fields": 2,
"record_length": 73,
"format_file": "s3://chaprola-2026/myagent/myproject/format/EMPLOYEES.F",
"data_file": "s3://chaprola-2026/myagent/myproject/data/EMPLOYEES.DA",
"expires_at": "2026-06-07T00:00:00Z"
}
Export Chaprola .DA + .F files back to JSON.
{"userid": "myagent", "project": "myproject", "name": "EMPLOYEES"}
Response (200):
{
"status": "ok",
"name": "EMPLOYEES",
"records": 2,
"data": [
{"first_name": "Alice", "salary": "95000"},
{"first_name": "Bob", "salary": "87000"}
]
}
List files in a project with optional wildcard pattern.
{"userid": "myagent", "project": "myproject", "pattern": "EMP*"}
Response (200):
{
"status": "ok",
"files": [
{"name": "EMPLOYEES.F", "type": "format", "size": 234},
{"name": "EMPLOYEES.DA", "type": "data", "size": 146}
],
"total": 2
}
Compile Chaprola source code (.CS) to bytecode (.PR).
{
"userid": "myagent",
"project": "myproject",
"name": "REPORT",
"source": "OPEN \"EMPLOYEES\"\nGET\nPRINT 0\nEND",
"primary_format": "EMPLOYEES"
}
Include primary_format to enable field-name addressing (e.g. P.salary instead of P.63).
Response (200):
{"status": "ok", "name": "REPORT", "instructions": 4, "bytes": 48}
Execute a compiled Chaprola program.
{
"userid": "myagent",
"project": "myproject",
"name": "REPORT",
"primary_file": "EMPLOYEES",
"record": 1
}
Response (200):
{
"status": "ok",
"output": "Alice 95000...",
"registers": {"R1": 0.0, "R2": 0.0},
"records_read": 1,
"records_written": 0,
"print_files": []
}
Get a presigned S3 PUT URL for uploading large files (up to 5 GB).
{"userid": "myagent", "project": "myproject", "name": "BIGDATA"}
Response:
{"upload_url": "https://chaprola-2026.s3.amazonaws.com/...", "method": "PUT", "expires_in": 900}
Process a JSON file previously uploaded via presigned URL. Upload a bare JSON array or full import request object.
{"userid": "myagent", "project": "myproject", "name": "BIGDATA"}
# Step 1: Get presigned URL
RESP=$(curl -s -X POST https://api.chaprola.org/import-url \
-H "X-Username: myagent" -H "X-Access-Code: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"userid":"myagent","project":"demo","name":"BIGDATA"}')
# Step 2: Upload directly to S3
curl -X PUT "$(echo $RESP | jq -r .upload_url)" \
-H "Content-Type: application/json" --data-binary @data.json
# Step 3: Process
curl -s -X POST https://api.chaprola.org/import-process \
-H "X-Username: myagent" -H "X-Access-Code: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"userid":"myagent","project":"demo","name":"BIGDATA"}'
Make a compiled program publicly runnable via /report.
{
"userid": "myagent",
"project": "demo",
"name": "MYREPORT",
"primary_file": "EMPLOYEES",
"record": 1
}
Response: {"status": "ok", "message": "MYREPORT.PR published", "report_url": "..."}
{"userid": "myagent", "project": "demo", "name": "MYREPORT"}
Run a published program. No authentication required.
{"userid": "myagent", "project": "demo", "name": "MYREPORT"}
Returns plain text output. 403 if program is not published.
Every registered account gets {username}@chaprola.org. Agents can send and receive email through the API.
Send an email from your @chaprola.org address.
{
"from": "myagent",
"to": "recipient@example.com",
"subject": "Report ready",
"text": "Your report is available.",
"html": "<p>Your report is available.</p>"
}
| Field | Required | Description |
|---|---|---|
from | Yes | Local part only — must match authenticated username |
to | Yes | Recipient email address |
subject | Yes | Email subject |
text | Yes* | Plain text body (*at least one of text/html required) |
html | No | HTML body |
Response: {"status": "sent", "message_id": "...", "moderated": true}
All outbound emails are AI-moderated. Spam, threats, phishing, and hate speech are blocked (403).
Rate limit: 20 emails/day per user. Exceeding returns 429.
List emails in your mailbox.
{"address": "myagent", "limit": 20}
| Field | Required | Description |
|---|---|---|
address | Yes | Must match authenticated username |
limit | No | Max emails to return (default 20, max 100) |
before | No | ISO 8601 timestamp — return emails before this time |
Response:
{
"status": "ok",
"emails": [
{"message_id": "msg_123", "from": "sender@example.com", "subject": "Hello",
"date": "2026-03-10T15:00:00Z", "has_attachments": false}
],
"total": 1
}
Read a specific email.
{"address": "myagent", "message_id": "msg_123"}
Response includes from, to, subject, text, html, date, attachments.
{"address": "myagent", "message_id": "msg_123"}
Response: {"status": "deleted"}
Chaprola has a compact instruction set — small enough for any LLM to generate correctly. Comments use // (single-line) and /* */ (multi-line).
Four buffers: P (primary record), S (secondary record), U (user buffer), X (system text, read-only). Plus 50 numeric registers: R1–R50. All buffers are character-addressed (1-indexed).
OPEN "FILENAME" // Open primary data file
OPEN "FILENAME" AS SECONDARY // Open secondary data file
GET // Read next primary record into P
GET FROM SECONDARY // Read next secondary record into S
PUT // Write P back to primary file
PUT SECONDARY // Write S back to secondary file
SEEK R1 // Jump to record number in R1
FIND R1 FROM P.name USING U.1 // Search field P.name for value in U.1, result in R1
MOVE P.1 U.1 20 // Copy 20 chars from P position 1 to U position 1
MOVE "Hello" U.1 // Move literal string to U position 1
MOVE BLANKS U.1 80 // Fill 80 chars with spaces
CLEAR U // Clear entire user buffer
GET R1 FROM P.1 10 // Parse number from P position 1 (10 chars) into R1
GET R1 FROM P.salary // Parse number from named field into R1
PUT R1 INTO U.1 10 // Format number into U position 1 (10 chars wide)
PUT R1 INTO U.1 10 D // Format as dollar: $1,234.56
PUT R1 INTO U.1 10 I // Format as integer: 1234
PUT R1 INTO U.1 10 E // Format as scientific: 1.23E+03
PUT R1 INTO U.1 10 F2 // Format with 2 decimal places: 1234.56
LET R1 = 100 // Assign literal
LET R1 = R2 // Copy register
ADD R1 R2 // R1 = R1 + R2
ADD R1 5 // R1 = R1 + 5
SUB R1 R2 // R1 = R1 - R2
MUL R1 R2 // R1 = R1 * R2
DIV R1 R2 // R1 = R1 / R2
GOTO 100 // Jump to line 100
GOTO DONE // Jump to label DONE (requires DEFINE LABEL)
IF EQUAL "admin" P.role GOTO 100 // Branch if field equals literal
IF GREATER R1 R2 GOTO 200 // Branch if R1 > R2
IF LESS R1 R2 GOTO 300 // Branch if R1 < R2
IF ZERO R1 GOTO 400 // Branch if R1 == 0
IF POSITIVE R1 GOTO 500 // Branch if R1 > 0
IF NEGATIVE R1 GOTO 600 // Branch if R1 < 0
IF EOF GOTO 700 // Branch if end of file
CALL 100 // Call subroutine at line 100
RETURN // Return from subroutine
END // Terminate program
STOP // Also terminates program
PRINT 0 // Print user buffer (entire line)
PRINT U 1 40 // Print U positions 1-40
PRINT P 1 20 // Print P positions 1-20
PRINT FILE "REPORT" // Write output to file REPORT in S3
DEFINE VARIABLE total R1 // Name register R1 as "total"
DEFINE LABEL done 999 // Name line 999 as "done"
DEFINE CALL process 500 // Name subroutine at line 500
| Position | Length | Content |
|---|---|---|
| X.1 | 4 | Year (e.g. "2026") |
| X.5 | 3 | Julian day (e.g. "069") |
| X.8 | 2 | Hour (00-23) |
| X.11 | 2 | Minute (00-59) |
| X.13 | 2 | Hundredths of second |
| X.33 | 8 | Primary filename |
| X.41 | 8 | Secondary filename |
| X.104 | 9 | Elapsed time (SSSSS.CC format) |
All errors return JSON:
| Status | Meaning | Example |
|---|---|---|
| 400 | Bad Request | {"error": "Missing required field: userid"} |
| 401 | Unauthorized | {"error": "Invalid credentials"} |
| 403 | Forbidden | {"error": "Forbidden: userid mismatch"} |
| 404 | Not Found | {"error": "File not found: EMPLOYEES.DA"} |
| 409 | Conflict | {"error": "Username already exists"} |
| 429 | Too Many Requests | {"message": "Too Many Requests"} |
never_expires: true)expires_in_days)If your stored token stops working (HTTP 403 Forbidden), follow these steps. Common causes are token expiration or infrastructure redeployment (JWT secret rotation). Login from another session does NOT invalidate your token.
If you still have your passcode, this is all you need.
# Re-login to get a fresh token
RESP=$(curl -s -X POST https://api.chaprola.org/login \
-H "Content-Type: application/json" \
-d '{"username": "my-agent", "passcode": "my-secret-passcode", "never_expires": true}')
# Extract the new token
TOKEN=$(echo "$RESP" | jq -r '.token')
# Verify it works
curl -s -X POST https://api.chaprola.org/list \
-H "X-Username: my-agent" \
-H "X-Access-Code: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"userid": "my-agent", "project": "*"}'
Save the new token if you want to use it going forward. Your old token remains valid until it expires.
If you no longer have your passcode, an administrator must create a reset file in S3. After the reset file exists, you can log in with any new passcode — it becomes your new passcode.
# Admin creates reset file (requires AWS CLI with S3 write access)
echo -n "" | aws s3 cp - s3://chaprola-2026/admin/reset/my-agent.reset
# Agent logs in with a NEW passcode (reset file is auto-deleted after use)
curl -s -X POST https://api.chaprola.org/login \
-H "Content-Type: application/json" \
-d '{"username": "my-agent", "passcode": "my-new-passcode", "never_expires": true}'
never_expires: true at registration or login to avoid 90-day token expiration./login first before requesting an admin reset. Your passcode is probably still good./list before starting real work.© 2026 Charles Letcher · chaprola.org