{
  "service": "orchestrion",
  "version": "1",
  "description": "Agent-native task orchestration API. Provides task lifecycle management with lease-based ownership, retry logic, and machine-readable recovery guidance (agent_contract) on every response.",
  "authentication": {
    "scheme": "Bearer",
    "header": "Authorization",
    "key_format": "orch_live_{64 hex chars}",
    "registration_endpoint": "/v1/keys/register",
    "notes": [
      "API keys are hashed with SHA-256; plaintext is never stored.",
      "Keys in query strings are rejected with 400.",
      "Each API key maps to exactly one account."
    ]
  },
  "task_lifecycle": {
    "states": [
      "pending",
      "claimed",
      "completed",
      "dead_letter",
      "cancelled"
    ],
    "terminal_states": [
      "completed",
      "dead_letter",
      "cancelled"
    ],
    "transitions": [
      {
        "from": null,
        "to": "pending",
        "trigger": "POST /v1/tasks"
      },
      {
        "from": "pending",
        "to": "claimed",
        "trigger": "POST /v1/tasks/claim or POST /v1/tasks/:id/claim"
      },
      {
        "from": "claimed",
        "to": "completed",
        "trigger": "POST /v1/tasks/:id/complete"
      },
      {
        "from": "claimed",
        "to": "pending",
        "trigger": "POST /v1/tasks/:id/fail (retries remaining)"
      },
      {
        "from": "claimed",
        "to": "dead_letter",
        "trigger": "POST /v1/tasks/:id/fail (retries exhausted)"
      },
      {
        "from": "claimed",
        "to": "pending",
        "trigger": "Lease expiry (retries remaining)"
      },
      {
        "from": "claimed",
        "to": "dead_letter",
        "trigger": "Lease expiry (retries exhausted)"
      },
      {
        "from": "pending",
        "to": "cancelled",
        "trigger": "POST /v1/tasks/:id/cancel"
      },
      {
        "from": "dead_letter",
        "to": "pending",
        "trigger": "POST /v1/tasks/:id/requeue"
      }
    ]
  },
  "lease": {
    "default_duration_seconds": 300,
    "min_duration_seconds": 30,
    "max_duration_seconds": 3600,
    "heartbeat_endpoint": "/v1/tasks/:id/heartbeat",
    "recommended_heartbeat_interval": "lease_duration_seconds / 3",
    "expiry_detection_interval_seconds": 30,
    "max_expiry_detection_latency_seconds": 30,
    "notes": [
      "Lease duration is set by the producer at task creation and is immutable.",
      "Heartbeat extends lease_expires_at by lease_duration_seconds.",
      "Lease authority requires same account + claimed status + non-expired lease.",
      "claimed_by is observational metadata only — not used for authorization."
    ]
  },
  "retry": {
    "default_max_attempts": 3,
    "min_max_attempts": 1,
    "max_max_attempts": 10,
    "attempt_count_incremented_on": "claim",
    "retry_delay_support": true,
    "retry_delay_field": "retry_after_seconds",
    "retry_delay_min": 1,
    "retry_delay_max": 86400,
    "dead_letter_condition": "attempt_count >= max_attempts on fail or lease expiry",
    "requeue_endpoint": "/v1/tasks/:id/requeue",
    "requeue_resets_attempt_count": true
  },
  "idempotency": {
    "supported_on": [
      "POST /v1/tasks"
    ],
    "key_header": "Idempotency-Key",
    "key_max_length": 255,
    "key_format": "Printable ASCII (0x20-0x7E)",
    "scope": "Per API key + key value",
    "ttl_days": 7,
    "stale_placeholder_recovery_seconds": 90,
    "behaviors": {
      "same_key_same_payload": "Returns cached 201 response",
      "same_key_different_payload": "409 idempotency_conflict",
      "in_flight": "503 idempotency_in_flight with retry_after_seconds: 2"
    }
  },
  "task_fields": {
    "required_at_creation": {
      "type": {
        "type": "string",
        "max_length": 100,
        "pattern": "alphanumeric, hyphens, underscores"
      },
      "payload": {
        "type": "object (JSONB)",
        "max_size_bytes": 65536,
        "max_depth": 5
      }
    },
    "optional_at_creation": {
      "priority": {
        "type": "integer",
        "default": 0,
        "min": 0,
        "max": 100
      },
      "maxAttempts": {
        "type": "integer",
        "default": 3,
        "min": 1,
        "max": 10
      },
      "leaseDurationSeconds": {
        "type": "integer",
        "default": 300,
        "min": 30,
        "max": 3600
      },
      "scheduledAt": {
        "type": "ISO 8601 timestamp",
        "min": "now()",
        "max": "now() + 30 days"
      },
      "idempotencyKey": {
        "type": "string",
        "max_length": 255,
        "scope": "request-only, not stored on task"
      }
    },
    "system_managed": [
      "id",
      "status",
      "attemptCount",
      "claimedBy",
      "claimedAt",
      "leaseExpiresAt",
      "lastHeartbeatAt",
      "createdAt",
      "updatedAt",
      "completedAt",
      "lastFailedAt",
      "lastFailureReason"
    ],
    "worker_written": {
      "result": {
        "type": "object (JSONB)",
        "max_size_bytes": 65536,
        "set_on": "/complete"
      },
      "outputId": {
        "type": "string",
        "set_on": "/complete",
        "notes": "OutputLayer artifact reference"
      },
      "lastFailureReason": {
        "type": "string",
        "max_length": 500,
        "set_on": "/fail"
      }
    }
  },
  "claim_ordering": {
    "method": "SELECT ... FOR UPDATE SKIP LOCKED",
    "order": [
      {
        "field": "priority",
        "direction": "DESC",
        "notes": "Higher priority claimed first"
      },
      {
        "field": "scheduled_at",
        "direction": "ASC NULLS FIRST",
        "notes": "NULL (immediate) before future"
      },
      {
        "field": "created_at",
        "direction": "ASC",
        "notes": "FIFO tiebreaker"
      }
    ],
    "scheduled_task_filter": "scheduled_at IS NULL OR scheduled_at <= now()"
  },
  "billing": {
    "model": "One-time plan purchase, active for 30 days",
    "default_plan": "free",
    "plan_duration_days": 30,
    "plans_endpoint": "/v1/billing/plans",
    "checkout_endpoint": "/v1/billing/checkout",
    "status_endpoint": "/v1/billing/status",
    "verify_endpoint": "/v1/billing/verify",
    "payment_provider": "PayPal",
    "notes": [
      "Every account starts on the Free tier.",
      "Paid plans are activated via one-time PayPal purchase.",
      "Checkout requires human approval on PayPal — agents cannot complete payment themselves.",
      "When a paid plan expires, the account silently reverts to Free tier limits.",
      "Purchasing while an existing plan is active extends from max(now, current_expiry) + 30 days."
    ],
    "plans": [
      {
        "id": "free",
        "name": "Free",
        "priceUsd": 0,
        "maxWorkers": 2,
        "maxTasksPerMonth": 200,
        "maxClaimsPerHour": 120,
        "maxCreatesPerHour": 120,
        "retentionDays": 7,
        "purchasable": false
      },
      {
        "id": "basic",
        "name": "Basic",
        "priceUsd": 9.99,
        "maxWorkers": 10,
        "maxTasksPerMonth": 5000,
        "maxClaimsPerHour": 1200,
        "maxCreatesPerHour": 500,
        "retentionDays": 30,
        "purchasable": true
      },
      {
        "id": "pro",
        "name": "Pro",
        "priceUsd": 24.99,
        "maxWorkers": 25,
        "maxTasksPerMonth": 25000,
        "maxClaimsPerHour": 4000,
        "maxCreatesPerHour": 2000,
        "retentionDays": 60,
        "purchasable": true
      },
      {
        "id": "agency",
        "name": "Agency",
        "priceUsd": 59.99,
        "maxWorkers": 50,
        "maxTasksPerMonth": 150000,
        "maxClaimsPerHour": 15000,
        "maxCreatesPerHour": 7500,
        "retentionDays": 90,
        "purchasable": true
      }
    ]
  },
  "rate_limits": {
    "note": "V1 uses in-memory stores (per-process). Not suitable for horizontal scaling without a shared store.",
    "plan_aware_note": "Task creation and claim rate limits scale with plan tier. Values shown are Free tier defaults.",
    "limits": [
      {
        "endpoint": "POST /v1/keys/register",
        "limit": "10/hour",
        "key": "IP"
      },
      {
        "endpoint": "POST /v1/tasks",
        "limit": "120/hour (plan-aware)",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/claim",
        "limit": "600/hour (plan-aware)",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/:id/claim",
        "limit": "600/hour (plan-aware)",
        "key": "API key"
      },
      {
        "endpoint": "GET /v1/tasks",
        "limit": "3000/hour",
        "key": "API key"
      },
      {
        "endpoint": "GET /v1/tasks/:id",
        "limit": "3000/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/:id/heartbeat",
        "limit": "1800/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/:id/complete",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/:id/fail",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/:id/cancel",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "POST /v1/tasks/:id/requeue",
        "limit": "600/hour",
        "key": "API key"
      },
      {
        "endpoint": "Billing endpoints",
        "limit": "10/hour",
        "key": "API key"
      },
      {
        "endpoint": "GET /v1/capabilities",
        "limit": "60/minute",
        "key": "IP"
      },
      {
        "endpoint": "All routes (global)",
        "limit": "6000/hour",
        "key": "IP"
      }
    ]
  },
  "endpoints": {
    "public": [
      {
        "method": "POST",
        "path": "/v1/keys/register",
        "purpose": "Register new API key"
      },
      {
        "method": "GET",
        "path": "/v1/capabilities",
        "purpose": "API capabilities document"
      },
      {
        "method": "GET",
        "path": "/v1/tool",
        "purpose": "MCP-compatible tool manifest"
      },
      {
        "method": "GET",
        "path": "/v1/schema",
        "purpose": "OpenAPI 3.1 schema document"
      },
      {
        "method": "GET",
        "path": "/.well-known/agent.json",
        "purpose": "Agent discovery manifest"
      },
      {
        "method": "GET",
        "path": "/health",
        "purpose": "Health check with lease job status"
      },
      {
        "method": "GET",
        "path": "/v1/billing/plans",
        "purpose": "List available billing plans"
      }
    ],
    "authenticated": [
      {
        "method": "POST",
        "path": "/v1/tasks",
        "purpose": "Create task"
      },
      {
        "method": "GET",
        "path": "/v1/tasks",
        "purpose": "List tasks (filtered, paginated)"
      },
      {
        "method": "GET",
        "path": "/v1/tasks/:id",
        "purpose": "Get single task"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/claim",
        "purpose": "Claim next available task by type"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/:id/claim",
        "purpose": "Claim specific task by ID"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/:id/complete",
        "purpose": "Complete task with result"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/:id/fail",
        "purpose": "Fail task (retry or dead-letter)"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/:id/heartbeat",
        "purpose": "Renew lease"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/:id/cancel",
        "purpose": "Cancel pending task"
      },
      {
        "method": "POST",
        "path": "/v1/tasks/:id/requeue",
        "purpose": "Requeue dead-lettered task"
      },
      {
        "method": "GET",
        "path": "/v1/billing/status",
        "purpose": "Current plan, usage, and limits"
      },
      {
        "method": "POST",
        "path": "/v1/billing/checkout",
        "purpose": "Start PayPal checkout for plan purchase"
      },
      {
        "method": "POST",
        "path": "/v1/billing/verify",
        "purpose": "Verify payment and activate plan"
      }
    ]
  },
  "error_model": {
    "shape": "{ error, message, request_id, agent_contract }",
    "codes": [
      {
        "code": "missing_api_key",
        "http": 401,
        "retryable": true
      },
      {
        "code": "invalid_api_key",
        "http": 401,
        "retryable": false
      },
      {
        "code": "invalid_request",
        "http": 400,
        "retryable": false
      },
      {
        "code": "task_not_found",
        "http": 404,
        "retryable": false
      },
      {
        "code": "invalid_transition",
        "http": 409,
        "retryable": false
      },
      {
        "code": "lease_expired",
        "http": 409,
        "retryable": false
      },
      {
        "code": "task_currently_claimed",
        "http": 409,
        "retryable": true
      },
      {
        "code": "not_yet_claimable",
        "http": 409,
        "retryable": true
      },
      {
        "code": "idempotency_conflict",
        "http": 409,
        "retryable": false
      },
      {
        "code": "idempotency_in_flight",
        "http": 503,
        "retryable": true
      },
      {
        "code": "quota_exceeded",
        "http": 402,
        "retryable": false
      },
      {
        "code": "plan_expired",
        "http": 402,
        "retryable": false
      },
      {
        "code": "max_workers_reached",
        "http": 429,
        "retryable": true
      },
      {
        "code": "payment_failed",
        "http": 402,
        "retryable": false
      },
      {
        "code": "rate_limited",
        "http": 429,
        "retryable": true
      },
      {
        "code": "server_error",
        "http": 500,
        "retryable": true
      }
    ]
  },
  "agent_contract": {
    "description": "Every response (success and error) includes an agent_contract field with machine-readable guidance. Agents should read next_actions to determine what to do, rather than parsing HTTP status codes.",
    "version": "1",
    "structure": {
      "version": "Always \"1\"",
      "retryable": "boolean — whether the same request can be retried",
      "next_actions": "Array of Action objects, each with action, available, recommended",
      "task_claimable": "Present on claim responses only (true = task found, false = queue empty)",
      "lease_valid": "Present when task is claimed (true = lease active)",
      "lease_expires_in_seconds": "Present when task is claimed",
      "recommended_heartbeat_interval_seconds": "Present when task is claimed with active lease (lease_duration / 3)"
    },
    "action_codes": [
      "create_task",
      "claim_task",
      "complete_task",
      "fail_task",
      "heartbeat",
      "check_task_status",
      "requeue_task",
      "retry_after_wait",
      "authenticate",
      "fix_request",
      "download_artifact",
      "list_tasks",
      "upgrade_plan",
      "renew_plan",
      "check_billing_status",
      "verify_payment",
      "retry_checkout"
    ],
    "stability_guarantee": {
      "breaking": "Removing or renaming action codes, error codes, or agent_contract fields requires /v2/",
      "non_breaking": "Adding new optional fields, action codes, or error codes stays in /v1/"
    }
  },
  "limitations": [
    "Rate limiting uses in-memory stores — per-process only, not distributed.",
    "No priority aging — low-priority tasks can starve under continuous high-priority load.",
    "No force-cancel of claimed tasks — must wait for lease expiry.",
    "No webhook or push notifications — consumers must poll.",
    "No workflow DAGs — tasks are independent.",
    "No task type registry — type is a free-form string.",
    "Background lease expiry job runs in a single process."
  ]
}