TeamDay Docs

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.json

Authentication

  • 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: replayed

If 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=60

On 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 typeStream
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-Signature with HMAC-SHA256 over ${timestamp}.${rawBody}.
  • Deduplicate with Teamday-Event-Id or the identical Idempotency-Key header.
  • Return a 2xx response only after the event is durably accepted.
  • Treat Teamday-Delivery-Attempt as 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.
  • 429 handling uses Retry-After.
  • Logs include X-Request-Id.
  • Long-running work uses jobs and review endpoints instead of synchronous blocking requests.

On this page