← chaprola.org

Chaprola API Documentation

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.

Quick Start

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}'

Authentication

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.

Token Expiration

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.

Rate Limits

EndpointsRateBurst
Auth endpoints (/register, /login, /check-username)5 req/s10
All other endpoints20 req/s50

All Endpoints

MethodPathAuthDescription
GET/helloNoHealth check
POST/registerNoCreate account, get JWT token
POST/loginNoLogin, get new token (old tokens stay valid)
POST/check-usernameNoCheck username availability
POST/delete-accountNoDelete account + all data (passcode required)
POST/importYesJSON → Chaprola .F + .DA files
POST/import-urlYesPresigned S3 upload URL (large files up to 5 GB)
POST/import-processYesProcess staged S3 file
POST/exportYesChaprola .DA + .F → JSON
POST/listYesList files by project + wildcard
POST/compileYesCompile .CS source → .PR bytecode
POST/runYesExecute .PR bytecode program
POST/publishYesMake a .PR publicly accessible via /report
POST/unpublishYesRemove public access from a .PR
POST/reportNoRun a published .PR (public, no auth)
POST/email/inboxYesList emails in your mailbox
POST/email/readYesRead a specific email by message_id
POST/email/sendYesSend email from your @chaprola.org address
POST/email/deleteYesDelete an email by message_id

Endpoint Details

POST /register

Create a new account. Returns a JWT token for all future requests.

{"username": "myagent", "passcode": "my-secret-passcode", "never_expires": true}
FieldRequiredDescription
usernameYes3-40 chars, alphanumeric + hyphens/underscores, must start with a letter
passcodeYes8-128 characters
never_expiresNoSet 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)

POST /login

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)

POST /check-username

{"username": "myagent"}

Response: {"available": true}

POST /delete-account

Permanently delete account, all files, and all emails. Requires passcode confirmation.

{"username": "myagent", "passcode": "my-secret-passcode"}

GET /hello

Health check. Optional query param: ?name=Alice


POST /import

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
}
FieldRequiredDescription
useridYesMust match authenticated user
projectYesProject name
nameYesFile name (without extension)
dataYesArray of flat JSON objects
expires_in_daysNoDays 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"
}

POST /export

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"}
  ]
}

POST /list

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
}

POST /compile

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}

POST /run

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": []
}

POST /import-url

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}

POST /import-process

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"}

Large File Upload Workflow

# 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"}'

POST /publish

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": "..."}

POST /unpublish

{"userid": "myagent", "project": "demo", "name": "MYREPORT"}

POST /report

Run a published program. No authentication required.

{"userid": "myagent", "project": "demo", "name": "MYREPORT"}

Returns plain text output. 403 if program is not published.

Email

Every registered account gets {username}@chaprola.org. Agents can send and receive email through the API.

POST /email/send

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>"
}
FieldRequiredDescription
fromYesLocal part only — must match authenticated username
toYesRecipient email address
subjectYesEmail subject
textYes*Plain text body (*at least one of text/html required)
htmlNoHTML 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.

POST /email/inbox

List emails in your mailbox.

{"address": "myagent", "limit": 20}
FieldRequiredDescription
addressYesMust match authenticated username
limitNoMax emails to return (default 20, max 100)
beforeNoISO 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
}

POST /email/read

Read a specific email.

{"address": "myagent", "message_id": "msg_123"}

Response includes from, to, subject, text, html, date, attachments.

POST /email/delete

{"address": "myagent", "message_id": "msg_123"}

Response: {"status": "deleted"}

Language Quick Reference

Chaprola has a compact instruction set — small enough for any LLM to generate correctly. Comments use // (single-line) and /* */ (multi-line).

Memory Model

Four buffers: P (primary record), S (secondary record), U (user buffer), X (system text, read-only). Plus 50 numeric registers: R1R50. All buffers are character-addressed (1-indexed).

File I/O

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

Data Movement

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

Arithmetic

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

Control Flow

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

Output

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

Declarations

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

System Text (X buffer, read-only)

PositionLengthContent
X.14Year (e.g. "2026")
X.53Julian day (e.g. "069")
X.82Hour (00-23)
X.112Minute (00-59)
X.132Hundredths of second
X.338Primary filename
X.418Secondary filename
X.1049Elapsed time (SSSSS.CC format)

Error Responses

All errors return JSON:

StatusMeaningExample
400Bad Request{"error": "Missing required field: userid"}
401Unauthorized{"error": "Invalid credentials"}
403Forbidden{"error": "Forbidden: userid mismatch"}
404Not Found{"error": "File not found: EMPLOYEES.DA"}
409Conflict{"error": "Username already exists"}
429Too Many Requests{"message": "Too Many Requests"}

Account & Data Lifecycle

Credential Recovery

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.

Step 1: Re-login with your passcode

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.

Step 2: If you lost your passcode (admin reset)

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}'

Best practices

  1. Always store both passcode and token. The token changes on every login; the passcode is permanent (unless you reset it). With the passcode, you can always recover.
  2. Use never_expires: true at registration or login to avoid 90-day token expiration.
  3. On 403, try /login first before requesting an admin reset. Your passcode is probably still good.
  4. Persist the new token immediately after any login. The previous token is permanently invalid.
  5. Test your token with a lightweight call like /list before starting real work.

© 2026 Charles Letcher · chaprola.org