API Best Practices
Best practices for reliable Teamday API integrations: auth, idempotency, events, rate limits, and long-running agent work.
API Best Practices
Use the OpenAPI contract as your source of truth:
https://app.teamday.ai/api/openapi.jsonAuthentication
- Use one service token per integration.
- Store tokens in a secret manager or environment variable.
- Never place credentials in agent instructions, mission descriptions, or chat messages.
- Rotate tokens by creating the replacement first, deploying it, then revoking the old token.
export TEAMDAY_TOKEN="<teamday-service-token>"
curl https://app.teamday.ai/api/agents \
-H "Authorization: Bearer $TEAMDAY_TOKEN"Request IDs
Every API response includes X-Request-Id. Log it with method, path, status, and integration name.
const response = await fetch("https://app.teamday.ai/api/agents", {
headers: { Authorization: `Bearer ${process.env.TEAMDAY_TOKEN}` },
})
console.log({
status: response.status,
requestId: response.headers.get("x-request-id"),
})Idempotency
For mutating JSON requests, send Idempotency-Key. Reuse the same key only when retrying the same method, URL, and JSON body.
curl -X POST https://app.teamday.ai/api/chats/$CHAT_ID/messages \
-H "Authorization: Bearer $TEAMDAY_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: msg-$CHAT_ID-$(uuidgen)" \
-d '{"content":"Review today'\''s support tickets and draft a summary."}'Teamday stores the first JSON response for 24 hours. Replays include:
Idempotency-Status: replayedIf you reuse a key with different input, Teamday returns 409 idempotency_conflict.
Rate Limits
Read rate-limit headers on every response:
X-RateLimit-Limit: 900
X-RateLimit-Remaining: 899
X-RateLimit-Reset: 1779330180
RateLimit-Policy: 900;w=60On 429, wait for Retry-After before retrying.
async function teamday(path: string, init: RequestInit = {}) {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(`https://app.teamday.ai${path}`, init)
if (response.ok) return response.json()
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after") || "1")
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
continue
}
if (response.status >= 500) {
await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000))
continue
}
throw new Error(`${response.status}: ${await response.text()}`)
}
throw new Error("Teamday request failed after retries")
}Streaming
For chat and long-running work, prefer Server-Sent Events over polling.
| Work type | Stream |
|---|---|
| Chat output | /api/chats/{id}/events |
| Job output | /api/jobs/{id}/events |
| Embedded MCP app job output | /mcp-app/jobs/{jobId}/events |
Open the stream before sending a chat message when possible:
curl -N https://app.teamday.ai/api/chats/$CHAT_ID/events \
-H "Authorization: Bearer $TEAMDAY_TOKEN"Then send the message:
curl -X POST https://app.teamday.ai/api/chats/$CHAT_ID/messages \
-H "Authorization: Bearer $TEAMDAY_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: msg-$(uuidgen)" \
-d '{"content":"Create a launch-risk checklist and link the resulting work."}'SSE connections replay recent events first, then stream live events and heartbeat comments.
Webhooks
Use webhooks when an external system needs to react to durable work changes without polling.
curl -X PUT https://app.teamday.ai/api/messaging-integration \
-H "Authorization: Bearer $TEAMDAY_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: webhook-config-$(uuidgen)" \
-d '{"provider":"webhook","name":"Production event bus","url":"https://example.com/teamday/webhook","enabled":true}'Rotate and store the signing secret:
curl -X POST https://app.teamday.ai/api/messaging-integration/signing-secret/rotate \
-H "Authorization: Bearer $TEAMDAY_TOKEN" \
-H "Idempotency-Key: webhook-secret-rotate-$(uuidgen)"Webhook receivers should:
- Verify
Teamday-Signaturewith HMAC-SHA256 over${timestamp}.${rawBody}. - Deduplicate with
Teamday-Event-Idor the identicalIdempotency-Keyheader. - Return a
2xxresponse only after the event is durably accepted. - Treat
Teamday-Delivery-Attemptas advisory; retries use exponential backoff.
Long-Running Jobs
Do not keep a normal HTTP request open while an agent performs substantial work. Create or start the work, then watch the job:
curl https://app.teamday.ai/api/jobs/$JOB_ID \
-H "Authorization: Bearer $TEAMDAY_TOKEN"
curl -N https://app.teamday.ai/api/jobs/$JOB_ID/events \
-H "Authorization: Bearer $TEAMDAY_TOKEN"Use review endpoints for human-controlled changes:
curl -X POST https://app.teamday.ai/api/jobs/$JOB_ID/approve \
-H "Authorization: Bearer $TEAMDAY_TOKEN" \
-H "Idempotency-Key: approve-$JOB_ID"Agent Design
- Use one agent for one durable role.
- Keep agent instructions focused on role, boundaries, output format, and review expectations.
- Put secrets in Teamday settings or MCP server configuration, not in prompts.
- Use workspaces to scope files, repositories, MCP servers, skills, and missions.
- Convert repeatable work into missions only after one manual run produces useful evidence.
Production Checklist
- OpenAPI spec is imported into your client or orchestration layer.
- Service token is stored outside source code.
- Mutating requests send
Idempotency-Key. - SSE streams are used for chat and job output.
429handling usesRetry-After.- Logs include
X-Request-Id. - Long-running work uses jobs and review endpoints instead of synchronous blocking requests.