Errors
How the API signals problems, how to parse them, and how to react. All error responses are JSON unless the connection fails.
Structure
Every unsuccessful JSON response uses a minimal envelope:
1{
2 "error": "INVALID_CONTENT",
3 "message": "Missing content field"
4}
- error: Stable machine-readable code.
- message: Human-readable explanation (not for programmatic branching).
Categories
- Validation (400): Malformed JSON, missing fields, type mismatches, mutually exclusive / required groups.
- Authentication (400 / 401): Missing agent key (400) vs invalid (rotated) key (401).
- Balance (402): Insufficient balance to perform the action.
- Internal (500): Transient platform issue.
- External (502): Upstream model / dependency failure.
Balance headers behavior
On success Message / Sort / Rewrite / Should-engage include usage headers (cost, balance). They are omitted on 400 validation errors and not returned by Limits at all.
See Limits.
Common error codes
HTTP | Error | Meaning | Action |
---|---|---|---|
400 | INVALID_JSON | Body isn't valid JSON. | Fix JSON formatting. |
400 | INVALID_CONTENT / INVALID_TEXT / INVALID_ARRAY | Required field missing or empty. | Provide a valid non-empty field. |
400 | INVALID_HISTORY / INVALID_INSTRUCTIONS / INVALID_CONTEXT | Field present but wrong type. | Correct the type or omit the field. |
400 | MISSING_AGENT_KEY / INVALID_AGENT_KEY | Authorization header missing. | Add agent key. |
401 | UNAUTHORIZED | Key not recognized (revoked / rotated). | Use current active key. |
402 | INSUFFICIENT_BALANCE | Balance is exhausted. | Add funds. |
500 | INTERNAL | Platform processing error. | Retry / contact support. |
502 | EXTERNAL_SERVICE | Upstream dependency issue. | Retry; transient. |
Handling strategy
- 400: Surface validation feedback directly; client can correct without retry loops.
- 401: Rotate key.
- 402: Back off; optionally poll Limits endpoint; avoid aggressive retry.
- 500 / 502: Implement exponential backoff with jitter; cap attempts.
Example handler
Minimal pattern for differentiating recovery paths in JavaScript:
1async function callMessage(content){
2 const res = await fetch('https://heylock.dev/api/v1/message', {
3 method: 'POST',
4 headers: {
5 'Authorization': process.env.HEYLOCK_AGENT_KEY,
6 'Content-Type': 'application/json'
7 },
8 body: JSON.stringify({ content })
9 });
10
11 if(response.ok){
12 const data = await response.json();
13 return { ok: true, message: data.message };
14 }
15
16 let payload = null;
17 try { payload = await response.json(); } catch { /* Non-JSON error */ }
18
19 const code = response.status;
20 const error = payload?.error;
21
22 switch(code){
23 case 400:
24 // Validation – fix request.
25 throw new Error(error || 'Bad Request');
26 case 401:
27 // Invalid / revoked key.
28 throw new Error('Auth failed – verify agent key');
29 case 402:
30 // Balance depletion – add funds or backoff.
31 throw new Error('Balance exhausted – add funds or backoff');
32 case 500:
33 case 502:
34 // Transient.
35 throw new Error('Temporary issue – retry later');
36 default:
37 throw new Error('Unexpected response');
38 }
39}
Route differences
- Should-Engage returns fallback object (HTTP 200) instead of an error when model output can't be parsed.
- Message streaming: parse line-by-line; each line maintains the same message/done shape—errors still use standard JSON.
- Limits never decrements usage.
Testing your handling
- Send deliberately malformed JSON to trigger INVALID_JSON.
- Omit required fields: e.g. remove content in Message or array in Sort.
- Rotate an agent key then call an endpoint with the old one for a 401.
- Simulate balance exhaustion by looping valid calls until a 402 (observe headers decrement each success).