AXTODO
REST API · v1

Your first API call in 5 minutes

CRUD company members / goals (tasks) / knowledge base externally with a single API key. JSON, standard HTTP — integrate in 5 minutes from any language.

1Sign up + log in

Create an axtodo.com account → create one company

2Generate an API key

Settings → API Keys → Generate (90-day expiry recommended). Shown only once

3First call

Copy-paste the curl below → done when you get 200 OK

Register one knowledge item (curl)
curl -X POST https://axtodo.com/api/v1/external/companies/{slug}/knowledge \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Our company coding conventions",
    "type": "markdown",
    "content": "# Style Guide\n\n- TypeScript strict\n- ...",
    "tags": ["engineering", "style"]
  }'

{slug} is where the company slug goes (find it in the dashboard URL /dashboard/companies/{slug}/...). Response 201 + JSON.

AgentOffice External API 명세서

외부 시스템에서 API key로 회사의 회사 멤버 / 목표(태스크) / 지식베이스를 관리하는 REST API.

마지막 갱신: 2026-05-05


목차

  1. 기본 정보
  2. 인증
  3. 공통 응답 형식
  4. 회사 멤버 API
  5. 목표(태스크) API
  6. 지식베이스 API
  7. 에러 코드
  8. 속도 제한 / 권한 한계

기본 정보

항목
Base URLhttps://axtodo.com/api/v1/external
요청 형식application/json (UTF-8)
응답 형식application/json. 모든 응답은 { "data": ... } 또는 { "error": "...", "code": "..." }
타임존모든 datetime은 ISO 8601 UTC (예: 2026-05-05T08:30:00.000Z)
ID 타입string. 내부적으로는 BigInt이지만 number 직렬화가 발생 — number/string 양쪽 모두 받음

인증

모든 엔드포인트는 API key 인증 필요. 두 가지 헤더 방식 모두 지원:

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

또는

X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API key 발급

  1. 대시보드 로그인 → 좌측 사이드바 "설정" 클릭
  2. 페이지 하단 "API Keys" 섹션 → "+ Generate Key"
  3. 이름 + 만료 기간 선택 (30일 / 90일 / 1년 / 만료 없음 / 직접 지정 — 기본 90일)
  4. 키는 한 번만 표시 — 즉시 안전한 곳에 저장
  5. 분실 시 폐기 후 재발급

인증 실패

상태code의미
401MISSING_API_KEY헤더 없음
401INVALID_API_KEY키가 존재하지 않음 또는 형식 오류
401KEY_REVOKED폐기된 키
401KEY_EXPIRED만료된 키

공통 응답 형식

성공

{
  "data": { /* 단일 객체 */ }
}
{
  "data": [ /* 배열 */ ]
}

에러

{
  "error": "사람이 읽을 수 있는 메시지",
  "code": "MACHINE_CODE",
  "issues": [ /* 검증 실패 시 zod issues */ ]
}

상태 코드

코드의미
200성공
201생성 성공 (POST)
400입력 검증 실패 (VALIDATION_ERROR)
401인증 실패
403권한 없음 (현재 미구현 — 향후)
404리소스 없음 (NOT_FOUND)
422도메인 검증 실패 (예: invalid type)
500서버 에러 (INTERNAL_ERROR)

에이전트 API (read-only)

회사에 배치된 AI 에이전트 조회. 읽기 전용 — 생성/수정/삭제는 대시보드 또는 internal API에서만 (LLM 안전성 보장).

GET /companies/:slug/agents

배포된 에이전트 목록 (소프트 삭제 제외).

Response 200:

{
  "data": [
    {
      "id": "1",
      "name": "김서연",
      "roleTitle": "프론트엔드 개발자",
      "roleGoal": "...",
      "tier": "mid",
      "power": "on",
      "currentState": "working",
      "mode": "independent",
      "hiredType": "cloned",
      "llmProvider": "claude",
      "llmModel": "claude-opus-4-7",
      "createdAt": "2026-04-01T00:00:00.000Z"
    }
  ]
}

필드 enum:

  • tier: junior | mid | senior | lead
  • power: on | off
  • currentState: idle | thinking | working | collaborating | waiting | done | sleeping | error
  • mode: independent | collaborative
  • hiredType: cloned | subscribed | outsourced | rookie
  • llmProvider: claude | openai | google

예시:

curl -H "Authorization: Bearer sk_live_xxx" \
  https://axtodo.com/api/v1/external/companies/team-8/agents

GET /companies/:slug/agents/:id

단일 에이전트 상세 (assignedDesk, skills, knowledge, growthStats, proficiencies 포함).

Response 200: { data: AgentItem } — 위 필드 + 관계 데이터 Response 404: { error: "Agent not found", code: "NOT_FOUND" }

노출되지 않는 작업

다음 작업은 외부 API에 노출되지 않음. 대시보드 또는 internal JWT API 사용:

작업위치
에이전트 생성 (마켓 고용)POST /api/companies/:slug/agents (JWT) 또는 대시보드 마켓
instructions / rules / skills / knowledge 편집/api/companies/:slug/agents/:id/soul/* (JWT) 또는 대시보드 → 에이전트 → Soul 탭
power on/offPOST /api/companies/:slug/agents/:id/power (JWT) 또는 대시보드
삭제DELETE /api/companies/:slug/agents/:id (JWT)

LLM 자체에 영향을 주는 변경은 클라이언트 책임 한계 안에서만 허용됩니다. 외부 통합에서 자동 변경이 필요하면 향후 PR로 별도 scope-제한 키 발급 검토.


회사 멤버 API

회사에 속한 사람 멤버 (에이전트는 별도 — 현재 외부 API 미노출).

GET /companies/:slug/members

회사 멤버 목록 조회.

Path params:

  • slug (string) — 회사 슬러그 (예: team-8)

Response 200:

{
  "data": [
    {
      "id": "1",
      "companyId": "8",
      "userId": "42",
      "role": "owner",
      "note": null,
      "user": {
        "id": "42",
        "email": "user@example.com",
        "name": "홍길동",
        "department": null,
        "position": null
      },
      "createdAt": "2026-04-01T00:00:00.000Z"
    }
  ]
}

role enum: owner | vice_owner | member

예시:

curl -H "Authorization: Bearer sk_live_xxx" \
  https://axtodo.com/api/v1/external/companies/team-8/members

현재 외부 API에서는 조회만 지원. 초대/역할 변경/제거는 대시보드 UI에서.


목표(태스크) API

회사 단위 태스크. taskType으로 에이전트 성장(agent_task) vs 프로젝트 목표(team_todo) 구분. parentTaskId로 트리 구조 (만다라트 슬롯 = orderIndex 0..7).

GET /companies/:slug/tasks

태스크 목록 (최대 50개, createdAt desc).

Query:

  • status (optional) — pending | assigned | in_progress | done | failed
  • agentId (optional) — 특정 에이전트 담당 태스크만

Response 200: { data: TaskItem[] }

interface TaskItem {
  id: string;
  title: string;
  description: string;
  status: 'pending' | 'assigned' | 'in_progress' | 'done' | 'failed';
  priority: 'low' | 'medium' | 'high' | 'urgent';
  taskType: 'agent_task' | 'team_todo';
  parentTaskId: string | null;
  assignedAgentId: string | null;
  result: string | null;
  tokensUsed: number;
  dueDate: string | null;
  startDate: string | null;
  startedAt: string | null;
  completedAt: string | null;
  weight: number | null;
  depth: number;
  orderIndex: number;
  createdAt: string;
  updatedAt: string;
}

GET /companies/:slug/tasks/:id

단일 태스크 상세. 404 NOT_FOUND 가능.

POST /companies/:slug/tasks

태스크 생성.

Body:

필드타입필수설명
titlestring (1..200)제목
descriptionstring비우면 title로 fallback
priorityenum기본 medium
taskTypeenum기본 team_todo
assignedAgentIdstring | number담당 에이전트. 지정 시 status=assigned
parentTaskIdstring | number만다라트 부모
dueDateISO 8601마감일

Response 201: { data: TaskItem }

예시 — 만다라트 sub-task 생성:

curl -X POST https://axtodo.com/api/v1/external/companies/team-8/tasks \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q2 매출 분석",
    "description": "지난 분기 대비 매출 증감 보고서",
    "priority": "high",
    "taskType": "team_todo",
    "parentTaskId": "123",
    "dueDate": "2026-06-30T23:59:59Z"
  }'

PATCH /companies/:slug/tasks/:id

태스크 부분 갱신. 모든 필드 optional.

Body (모두 optional):

필드타입
titlestring (1..200)
descriptionstring
priorityenum
statusenum (pending/assigned/in_progress/done/failed)
dueDateISO 8601 | null (null = 마감일 제거)
assignedAgentIdstring | number | null (null = 담당 해제)

Response 200: { data: TaskItem }

주의: 외부 API의 status 변경은 internal task.service의 자동 hook(startedAt/completedAt 자동 설정 + TaskLog 기록)을 우회. 일관성 필요 시 status는 pendingdone 전이 외에 internal API 권장.

DELETE /companies/:slug/tasks/:id

태스크 삭제. 자식 태스크는 parentTaskId가 null로 설정됨 (cascade 안 함).

Response 200: { data: { id: "..." } }


지식베이스 API

회사 단위 지식 항목. 에이전트별 RAG와는 분리된 t_company_knowledge 테이블.

GET /companies/:slug/knowledge

지식 목록 (최대 100개, updatedAt desc).

Query:

  • search (optional) — name/content contains OR 검색
  • type (optional) — post | file | markdown | url | text
  • isActive (optional) — 'true' | 'false'. 기본 active만

Response 200: { data: KnowledgeItem[] }

interface KnowledgeItem {
  id: string;
  name: string;
  type: 'post' | 'file' | 'markdown' | 'url' | 'text';
  content: string; // LongText, 마크다운/URL/원본 텍스트
  tags: string[];
  isActive: boolean;
  createdAt: string;
  updatedAt: string;
}

GET /companies/:slug/knowledge/:id

단일 지식 상세.

POST /companies/:slug/knowledge

지식 생성.

Body:

필드타입필수
namestring (1..200)
typeenum
contentstring (1..)
tagsstring[]

Response 201: { data: KnowledgeItem }

예시:

curl -X POST https://axtodo.com/api/v1/external/companies/team-8/knowledge \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "코딩 컨벤션",
    "type": "markdown",
    "content": "# Style Guide\n\n- TypeScript strict mode\n- ...",
    "tags": ["engineering", "style"]
  }'

PATCH /companies/:slug/knowledge/:id

부분 갱신. name/type/content/tags/isActive 모두 optional.

Response 200: { data: KnowledgeItem }

DELETE /companies/:slug/knowledge/:id

지식 항목 삭제 (hard delete).

Response 200: { data: { id: "..." } }


파일 업로드 (R2)

type=file 지식 항목은 Cloudflare R2에 직접 업로드. 백엔드는 presigned URL만 발급, 파일 자체는 클라이언트가 R2로 PUT.

업로드 흐름 (3단계)

1) POST .../knowledge/upload-url   → { uploadUrl, key }
2) PUT  uploadUrl  (Content-Type: <mime>, body: <file bytes>)
3) POST .../knowledge              → content: <key>, type: 'file'

POST /companies/:slug/knowledge/upload-url

presigned PUT URL 발급 (10분 만료).

Body:

필드타입필수
filenamestring (1..255)
contentTypestring❌ (예: application/pdf, image/png)

Response 200:

{
  "data": {
    "uploadUrl": "https://<account>.r2.cloudflarestorage.com/axtodo/team-8/...?X-Amz-Signature=...",
    "key": "team-8/1714900000000-abc123-doc.pdf",
    "bucket": "axtodo",
    "publicUrl": "https://<account>.r2.cloudflarestorage.com/axtodo/team-8/...",
    "expiresIn": 600
  }
}

클라이언트 PUT (직접 R2로)

curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @./report.pdf

→ 200 (R2 응답, body 비어있음).

파일 등록

받은 keycontent로 저장:

curl -X POST https://axtodo.com/api/v1/external/companies/team-8/knowledge \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q2 보고서",
    "type": "file",
    "content": "team-8/1714900000000-abc123-report.pdf",
    "tags": ["report"]
  }'

GET /companies/:slug/knowledge/:id/download-url

type=file 지식의 presigned GET URL 발급 (1시간 만료).

Response 200:

{
  "data": {
    "url": "https://<account>.r2.cloudflarestorage.com/axtodo/team-8/...?X-Amz-Signature=...",
    "expiresIn": 3600,
    "key": "team-8/..."
  }
}

type !== 'file'이면 400 NOT_A_FILE.

풀 통합 예 (curl 3-step)

KEY="sk_live_xxx"
SLUG="team-8"
FILE="./report.pdf"
BASE="https://axtodo.com/api/v1/external"

# 1. presigned PUT 발급
RESPONSE=$(curl -s -X POST "$BASE/companies/$SLUG/knowledge/upload-url" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename":"report.pdf","contentType":"application/pdf"}')

UPLOAD_URL=$(echo "$RESPONSE" | jq -r '.data.uploadUrl')
KEY_OBJ=$(echo "$RESPONSE" | jq -r '.data.key')

# 2. R2로 직접 PUT
curl -X PUT "$UPLOAD_URL" -H "Content-Type: application/pdf" --data-binary @"$FILE"

# 3. 지식 등록
curl -X POST "$BASE/companies/$SLUG/knowledge" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"Q2 보고서\",\"type\":\"file\",\"content\":\"$KEY_OBJ\"}"

에러 코드

codeHTTP의미
MISSING_API_KEY401인증 헤더 없음
INVALID_API_KEY401알 수 없는 키
KEY_REVOKED401폐기됨
KEY_EXPIRED401만료됨
VALIDATION_ERROR400요청 body/query zod 검증 실패. 응답 issues 배열 참조
NOT_FOUND404리소스 없음
KNOWLEDGE_NOT_FOUND404지식 항목 없음
INVALID_TYPE422knowledge type enum 외 값
NOT_A_FILE400download-url을 type !== 'file' 항목에 호출
R2_ERROR500R2 storage 오류
INTERNAL_ERROR500서버 오류. 운영자 문의

속도 제한 / 권한 한계

  • 속도 제한: 현재 미적용. 향후 분당 60req 제한 예정
  • 회사 스코프: API key는 발급한 사용자가 멤버인 모든 회사 접근 가능. 키 단위 회사 제한은 후속 추가 예정
  • 에이전트 관리: 현재 외부 API 미노출 (대시보드 UI 또는 internal API 사용)
  • 결제/구독: 외부 API 미노출
  • 외부 task PATCH의 status 변경은 자동 startedAt/completedAt 갱신 hook을 우회 — 정확한 lifecycle이 필요한 통합은 internal API 권장

관련 문서

  • 카탈로그: docs/feature-catalog.md
  • API key 인프라: backend/src/services/api-key.service.ts, backend/src/middleware/apiKeyAuth.ts
  • 외부 라우터 소스: backend/src/api/external.ts
  • 도메인 용어: .claude/rules/10-business-domain.md