Docs · api

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: 3000
X-RateLimit-Remaining: 2999
X-RateLimit-Reset: 1779330180
RateLimit-Policy: 3000;w=60

On 429, wait for Retry-After before retrying.

For machine-readable limits, fetch:

https://app.teamday.ai/api/rate-limits

The policy endpoint returns the quota identity order, default burst budget, 60-second window, Redis-backed production behavior, enterprise tier semantics, and normal/throttled response examples.

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. Teamday emits specialized job.* lifecycle events plus typed mutation events for successful JSON API changes across agents, chats, missions, workspaces, resources, service tokens, and webhook configuration.

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.