API Errors & Validation
All non-2xx responses return a JSON error body. This page covers the error format, HTTP status codes, and common validation rules enforced across the API.
Error response format
Section titled “Error response format”Every error response returns a JSON object with an error field:
{ "error": "journal entry lines do not balance: debits=5000000, credits=3000000"}The error string is human-readable and suitable for display. Machine consumers should rely on the HTTP status code to determine the error category.
HTTP status codes
Section titled “HTTP status codes”| Code | Meaning | When it happens |
|---|---|---|
| 200 | OK | Successful request with response body |
| 204 | No Content | Successful request with no response body |
| 400 | Bad Request | Validation error — empty names, negative amounts, invalid state transitions, unbalanced journal entries |
| 401 | Unauthorized | Missing or invalid Bearer token / API key |
| 403 | Forbidden | API key lacks the required scope for this operation |
| 404 | Not Found | Entity, document, or resource does not exist |
| 409 | Conflict | Duplicate resource (e.g., duplicate email, duplicate instrument symbol) or concurrency conflict |
| 429 | Too Many Requests | Rate limit exceeded — back off and retry |
| 500 | Internal Server Error | Unexpected server error — report to support |
Common validation rules
Section titled “Common validation rules”All name fields (entity names, account names, holder names, contact names) must be:
- Between 1 and 500 characters
- Non-empty after whitespace trimming
{"error": "name must not be empty"}Symbols
Section titled “Symbols”Instrument symbols (e.g., CS, SAFE-1) must be:
- Between 1 and 50 characters
- Alphanumeric characters, hyphens, underscores, and spaces only
{"error": "symbol contains invalid characters"}Jurisdiction
Section titled “Jurisdiction”Jurisdiction codes must be a 2-letter uppercase US state code:
"DE", "CA", "WY", "NY", "TX", ...{"error": "jurisdiction must be a 2-letter US state code"}Monetary amounts
Section titled “Monetary amounts”All monetary values are in whole cents as positive integers. There are no fractional cents.
| Dollar amount | Cents value |
|---|---|
| $50,000.00 | 5000000 |
| $15,000.00 | 1500000 |
| $2,500.00 | 250000 |
| $0.01 | 1 |
Amounts must be positive:
{"error": "amount_cents must be positive"}Shares
Section titled “Shares”Share counts must be positive integers:
{"error": "shares must be a positive integer"}Entity type
Section titled “Entity type”The entity_type field accepts exactly two values:
"c_corp"— C-Corporation"llc"— Limited Liability Company
{"error": "entity_type must be 'c_corp' or 'llc'"}Domain-specific validations
Section titled “Domain-specific validations”Over-issuance protection
Section titled “Over-issuance protection”Equity grants cannot exceed the instrument’s authorized_units. If you try to issue 8,000,000 shares on an instrument authorized for 10,000,000 that already has 5,000,000 issued, the request fails:
{"error": "grant would exceed authorized units: requested=8000000, available=5000000"}To fix this, either reduce the grant size or increase the instrument’s authorized units (which typically requires a board resolution).
Cap table ownership
Section titled “Cap table ownership”The cap_table_id referenced in equity operations must belong to the entity in the URL path. Cross-entity references are rejected:
{"error": "cap_table_id does not belong to this entity"}Dissolved entity guard
Section titled “Dissolved entity guard”Write operations on dissolved entities are rejected with a 400 error. Once an entity is dissolved via POST /v1/entities/{entity_id}/dissolve, it becomes read-only:
{"error": "entity is dissolved; write operations are not permitted"}Read operations (GET requests) continue to work on dissolved entities.
Transfer validation
Section titled “Transfer validation”Share transfers require the sender to hold enough shares. If the sender’s position is insufficient, the transfer is rejected:
{"error": "sender does not have enough shares: requested=1000000, available=500000"}State transition errors
Section titled “State transition errors”Resources with lifecycle state machines (invoices, journal entries, bank accounts, payroll runs, formations) reject invalid transitions:
{"error": "cannot void an invoice in 'paid' status"}{"error": "cannot post a journal entry in 'voided' status"}Error handling examples
Section titled “Error handling examples”The corp CLI prints the error message to stderr and exits with a non-zero status code:
$ corp finance create-account --account-code "" --account-name "" --currency usdError: name must not be emptyUse --json to get structured error output for scripting:
$ corp finance create-account --account-code "" --account-name "" --currency usd --json{"error": "name must not be empty"}API (curl)
Section titled “API (curl)”Check the HTTP status code to detect errors:
response=$(curl -s -w "\n%{http_code}" -X POST \ http://localhost:8000/v1/entities/$ENTITY_ID/grants \ -H "Authorization: Bearer $CORP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cap_table_id": "'$CAP_TABLE_ID'", "instrument_id": "'$INSTRUMENT_ID'", "recipient_name": "Jane Smith", "grant_type": "common_stock", "shares": 99000000 }')
http_code=$(echo "$response" | tail -1)body=$(echo "$response" | head -1)
if [ "$http_code" -ne 200 ]; then echo "Error ($http_code): $body"fiRate limiting (429)
Section titled “Rate limiting (429)”When rate limited, the response includes a Retry-After header with the number of seconds to wait:
HTTP/1.1 429 Too Many RequestsRetry-After: 5
{"error": "rate limit exceeded"}Back off for the indicated duration before retrying.