Message
POST https://heylock.dev/api/v1/message
Send a message to your agent and receive a single response or a streaming sequence of chunks.
Use this for conversational interactions.
Authentication
Provide the agent key in the Authorization header. Raw key only (no Bearer
prefix). See Authorization.
Request body
- content (string, required): User's message. Non-empty.
- history (array<{ content: string, role: "user" | "assistant" }>, optional): Prior turns to preserve conversation continuity.
- context (string, optional): Additional situational data. Brief structured narrative.
- stream (boolean, optional, default false): If true, returns incremental chunks until completion.
Behavior
- If context is present it's prepended as a system message.
- Validation rejects empty strings and malformed history entries.
- When streaming, each line is a JSON object with partial text.
- Final non-stream response contains a single message field.
Responses
Non-streaming
Returns JSON with the agent reply.
1{
2 "message": "Hello. It's nice to meet you."
3}
Streaming
When stream is true, the body is a stream of newline-delimited JSON objects. Each chunk has a message fragment and done flag. The final object sets done true.
1{ "message": "Hello", "done":false }
2{ "message": " there", "done": false }
3{ "message": "!", "done": false }
4{ "message": "", "done": true }
Examples
Basic message:
1const response = await fetch('https://heylock.dev/api/v1/message', {
2 method: 'POST',
3 headers: {
4 'Authorization': process.env.HEYLOCK_AGENT_KEY,
5 'Content-Type': 'application/json'
6 },
7 body: JSON.stringify({
8 content: 'Hello there'
9 })
10});
11
12const data = await response.json();
13console.log(data.message);
With prior history and context:
1const history = [
2 { role: 'user', content: 'Hi' },
3 { role: 'assistant', content: 'Hello! How can I help?' }
4];
5
6const response = await fetch('https://heylock.dev/api/v1/message', {
7 method: 'POST',
8 headers: {
9 Authorization: process.env.HEYLOCK_AGENT_KEY,
10 'Content-Type': 'application/json'
11 },
12 body: JSON.stringify({
13 content: 'Can you remind me what you said?',
14 history,
15 context: 'User revisiting earlier answer.'
16 })
17});
18
19console.log(await response.json());
Streaming consumption in JavaScript:
1const response = await fetch('https://heylock.dev/api/v1/message', {
2 method: 'POST',
3 headers: {
4 Authorization: process.env.HEYLOCK_AGENT_KEY,
5 'Content-Type': 'application/json'
6 },
7 body: JSON.stringify({
8 content: 'Explain quantum tunneling simply',
9 stream: true
10 })
11});
12
13if (!response.body) throw new Error('Streaming not supported');
14
15const reader = response.body.getReader();
16const decoder = new TextDecoder();
17let full = '';
18
19while (true) {
20 const { value, done } = await reader.read();
21 if (done) break;
22
23 const chunk = decoder.decode(value, { stream: true });
24
25 for (const line of chunk.split('\n')) {
26 if (!line.trim()) continue;
27
28 try {
29 const obj = JSON.parse(line);
30
31 if (obj.message) {
32 full += obj.message;
33 process.stdout.write(obj.message);
34 }
35
36 if (obj.done) {
37 console.log('\n[done]');
38 }
39 } catch (e) {
40 // Ignore partial/incomplete lines
41 }
42 }
43}
44
45console.log('Full message:', full);
Streaming with cURL (shows incremental JSON lines):
1curl -N -X POST https://heylock.dev/api/v1/message \
2 -H 'Authorization: AGENT_KEY' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "content": "Stream a short poetic line",
6 "stream": true
7 }'
Usage headers
Successful responses include usage headers (balance, cost. Not returned on 4xx validation errors. If remaining balance reaches zero, you'll get 402 status code.
See Limits for details.
1const response = await fetch('https://heylock.dev/api/v1/message', {
2 method: 'POST',
3 headers: {
4 Authorization: process.env.HEYLOCK_AGENT_KEY,
5 'Content-Type': 'application/json'
6 },
7 body: JSON.stringify({
8 content: 'Hello'
9 })
10});
11
12if (!response.ok) throw new Error('Request failed');
13
14const balance = Number(response.headers.get('balance'));
15const cost = Number(response.headers.get('cost'));
16
17console.log({ balance, cost });
18
Errors
Handle both validation and runtime failures. Error bodies contain error and message.
Code | Error | What it means | What to do |
---|---|---|---|
400 | INVALID_JSON | Body isn't valid JSON. | Check request body formatting. |
400 | INVALID_CONTENT | Missing or empty content. | Provide a non-empty content string. |
400 | INVALID_HISTORY | history is not an array. | Pass an array of valid message objects. |
400 | INVALID_AGENT_KEY | Missing Authorization header. | Add your agent key to the header. |
401 | UNAUTHORIZED | Key doesn't match an agent (rotated or revoked). | Regenerate and use a valid agent key. |
402 | INSUFFICIENT_BALANCE | Balance exhausted. | Add funds. |
500 | INTERNAL | Temporary internal issue. | Retry or contact support if persistent. |
502 | EXTERNAL_SERVICE | Temporary external issue. | Retry; usually resolves itself. |
1{
2 "error": "INSUFFICIENT_BALANCE",
3 "message": "Your account balance is zero or less. Please add funds to continue using the service."
4}
Common issues
- Sending current user message inside history.
- Parsing streaming chunks as a single JSON blob instead of line by line.
- Assuming headers appear on validation 400 responses.
- Rotating agent key and reusing the old one.