{
  "openapi": "3.0.3",
  "info": {
    "title": "Tempkey API",
    "description": "Programmatic access to everything the Tempkey dashboard can do: contractor grants with automatic expiry, provider integrations, revocation with read-back verification, the audit trail, and API key management. Authenticate with an API key created in Settings (or via this API) sent as `Authorization: Bearer tk_...`. Keys carry `read` and/or `write` scopes: GET requests need `read`, mutating requests need `write`. All endpoints are also reachable without the `/api/v1` prefix; the prefix is the stable, versioned surface for API clients.",
    "version": "1.0.0",
    "contact": { "name": "Tempkey", "url": "https://tempkey.io" }
  },
  "servers": [{ "url": "https://api.tempkey.io/api/v1" }],
  "security": [{ "apiKey": [] }],
  "tags": [
    { "name": "Grants", "description": "Time-boxed contractor access grants" },
    { "name": "Contractors", "description": "Re-grant access for known contractors" },
    { "name": "Integrations", "description": "Provider connections and health" },
    { "name": "Providers", "description": "Provider capability metadata" },
    { "name": "Audit", "description": "Append-only audit trail and exports" },
    { "name": "API keys", "description": "Create, rotate, and revoke API keys" },
    { "name": "Workspace", "description": "Workspace settings" }
  ],
  "paths": {
    "/grants": {
      "get": {
        "tags": ["Grants"],
        "summary": "List grants",
        "operationId": "listGrants",
        "responses": {
          "200": {
            "description": "Grants with contractor and per-tool access state",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "grants": { "type": "array", "items": { "$ref": "#/components/schemas/GrantResponse" } } } } } }
          }
        }
      },
      "post": {
        "tags": ["Grants"],
        "summary": "Create a grant",
        "description": "Creates (or reuses) the contractor, provisions access in every requested provider, and schedules automatic revocation at expiry. Exactly one of `expiresAt`, `durationDays`, or `endDate` is required.",
        "operationId": "createGrant",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateGrantRequest" } } } },
        "responses": {
          "201": { "description": "Grant created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "402": { "description": "Active-grant limit for the current plan reached" },
          "422": { "description": "One or more requested providers are not connected" }
        }
      }
    },
    "/grants/{grantId}": {
      "get": {
        "tags": ["Grants"],
        "summary": "Get a grant",
        "operationId": "getGrant",
        "parameters": [{ "$ref": "#/components/parameters/grantId" }],
        "responses": {
          "200": { "description": "Grant detail", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/grants/{grantId}/extend": {
      "post": {
        "tags": ["Grants"],
        "summary": "Extend a grant",
        "description": "Moves the expiry later, reschedules automatic revocation, and pushes the new deadline into providers that enforce expiry natively (e.g. GitLab membership expires_at).",
        "operationId": "extendGrant",
        "parameters": [{ "$ref": "#/components/parameters/grantId" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["expiresAt"], "properties": { "workspaceId": { "type": "string" }, "expiresAt": { "type": "string", "format": "date-time" } } } } } },
        "responses": {
          "200": { "description": "Extended grant", "content": { "application/json": { "schema": { "type": "object", "properties": { "grant": { "$ref": "#/components/schemas/Grant" } } } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/grants/{grantId}/revoke": {
      "post": {
        "tags": ["Grants"],
        "summary": "Revoke a grant now",
        "description": "Executes revocation in every provider immediately, then reads provider state back to verify. Failures are surfaced per tool.",
        "operationId": "revokeGrant",
        "parameters": [{ "$ref": "#/components/parameters/grantId" }],
        "responses": {
          "200": { "description": "Revocation outcome per tool", "content": { "application/json": { "schema": { "type": "object", "properties": { "grant": { "$ref": "#/components/schemas/Grant" }, "access": { "type": "array", "items": { "$ref": "#/components/schemas/ToolAccess" } } } } } } }
        }
      }
    },
    "/grants/{grantId}/access/{provider}": {
      "patch": {
        "tags": ["Grants"],
        "summary": "Update a grant's provider permission",
        "operationId": "updateToolAccess",
        "parameters": [{ "$ref": "#/components/parameters/grantId" }, { "$ref": "#/components/parameters/provider" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["permission"], "properties": { "workspaceId": { "type": "string" }, "permission": { "type": "string" } } } } } },
        "responses": {
          "200": { "description": "Updated access entry", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ToolAccess" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/contractors/{contractorId}/grants": {
      "post": {
        "tags": ["Contractors"],
        "summary": "Re-grant a past contractor",
        "operationId": "regrantContractor",
        "parameters": [{ "name": "contractorId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegrantContractorRequest" } } } },
        "responses": {
          "201": { "description": "Grant created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "description": "One or more requested providers are not connected" }
        }
      }
    },
    "/providers": {
      "get": {
        "tags": ["Providers"],
        "summary": "List provider capabilities",
        "operationId": "listProviders",
        "responses": {
          "200": { "description": "Capabilities keyed by provider id", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/ProviderCapabilities" } } } } }
        }
      }
    },
    "/integrations": {
      "get": {
        "tags": ["Integrations"],
        "summary": "List integrations",
        "operationId": "listIntegrations",
        "responses": {
          "200": { "description": "Connected integrations (tokens are never returned)", "content": { "application/json": { "schema": { "type": "object", "properties": { "integrations": { "type": "array", "items": { "$ref": "#/components/schemas/IntegrationConnection" } } } } } } }
        }
      },
      "post": {
        "tags": ["Integrations"],
        "summary": "Connect an integration",
        "description": "Stores the provider admin credential encrypted (KMS in production). The token is write-only: it is never returned by any endpoint.",
        "operationId": "connectIntegration",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConnectIntegrationRequest" } } } },
        "responses": {
          "201": { "description": "Integration connected", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntegrationConnection" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/integrations/{provider}": {
      "delete": {
        "tags": ["Integrations"],
        "summary": "Disconnect an integration",
        "description": "Removes the stored credential and marks affected grant access as unenforceable.",
        "operationId": "disconnectIntegration",
        "parameters": [{ "$ref": "#/components/parameters/provider" }],
        "responses": { "200": { "description": "Disconnected" }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },
    "/integrations/{provider}/health": {
      "post": {
        "tags": ["Integrations"],
        "summary": "Check integration health",
        "operationId": "checkIntegrationHealth",
        "parameters": [{ "$ref": "#/components/parameters/provider" }],
        "responses": {
          "200": { "description": "Current connection status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IntegrationConnection" } } } },
          "409": { "description": "Integration is not connected" }
        }
      }
    },
    "/audit": {
      "get": {
        "tags": ["Audit"],
        "summary": "List audit events",
        "operationId": "listAudit",
        "parameters": [{ "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100 } }],
        "responses": {
          "200": { "description": "Newest-first audit events", "content": { "application/json": { "schema": { "type": "object", "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/AuditEvent" } } } } } } }
        }
      }
    },
    "/audit.csv": {
      "get": {
        "tags": ["Audit"],
        "summary": "Export the audit trail as CSV",
        "operationId": "exportAuditCSV",
        "responses": { "200": { "description": "CSV export", "content": { "text/csv": { "schema": { "type": "string" } } } } }
      }
    },
    "/audit.pdf": {
      "get": {
        "tags": ["Audit"],
        "summary": "Export the audit trail as PDF",
        "operationId": "exportAuditPDF",
        "responses": { "200": { "description": "PDF export", "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } } }
      }
    },
    "/workspace": {
      "get": {
        "tags": ["Workspace"],
        "summary": "Get workspace settings",
        "operationId": "getWorkspace",
        "responses": { "200": { "description": "Workspace", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Workspace" } } } } }
      },
      "put": {
        "tags": ["Workspace"],
        "summary": "Update workspace settings",
        "operationId": "updateWorkspace",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Workspace" } } } },
        "responses": { "200": { "description": "Updated workspace", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Workspace" } } } } }
      }
    },
    "/api-keys": {
      "get": {
        "tags": ["API keys"],
        "summary": "List API keys",
        "description": "Returns key metadata only — secrets are shown once at creation or rotation and never again.",
        "operationId": "listAPIKeys",
        "responses": {
          "200": { "description": "API keys", "content": { "application/json": { "schema": { "type": "object", "properties": { "apiKeys": { "type": "array", "items": { "$ref": "#/components/schemas/APIKey" } } } } } } }
        }
      },
      "post": {
        "tags": ["API keys"],
        "summary": "Create an API key",
        "operationId": "createAPIKey",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateAPIKeyRequest" } } } },
        "responses": {
          "201": { "description": "The key and its one-time secret", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeySecretResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/api-keys/{keyId}": {
      "delete": {
        "tags": ["API keys"],
        "summary": "Revoke an API key",
        "description": "Revocation is immediate and permanent; create a new key to restore access.",
        "operationId": "revokeAPIKey",
        "parameters": [{ "$ref": "#/components/parameters/keyId" }],
        "responses": {
          "200": { "description": "Revoked key", "content": { "application/json": { "schema": { "type": "object", "properties": { "apiKey": { "$ref": "#/components/schemas/APIKey" } } } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api-keys/{keyId}/rotate": {
      "post": {
        "tags": ["API keys"],
        "summary": "Rotate an API key's secret",
        "description": "The key id, name, scopes, and expiry are kept; the old secret stops working immediately and the new secret is returned exactly once.",
        "operationId": "rotateAPIKey",
        "parameters": [{ "$ref": "#/components/parameters/keyId" }],
        "responses": {
          "200": { "description": "The key and its new one-time secret", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeySecretResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "The key is revoked and cannot be rotated" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "tk_<key id>_<secret>",
        "description": "API key created in Settings → API keys (or via POST /api-keys). GET requests require the `read` scope; mutating requests require `write`."
      }
    },
    "parameters": {
      "grantId": { "name": "grantId", "in": "path", "required": true, "schema": { "type": "string" } },
      "provider": { "name": "provider", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Provider id, e.g. github, gitlab, zoom, microsoft_365, aws_iam, slack, google_workspace, figma, dropbox, asana" },
      "keyId": { "name": "keyId", "in": "path", "required": true, "schema": { "type": "string" } }
    },
    "responses": {
      "BadRequest": { "description": "The request body or parameters are invalid", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "The resource does not exist in this workspace", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" } } },
      "Grant": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "workspaceId": { "type": "string" },
          "contractorId": { "type": "string" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "status": { "type": "string", "enum": ["pending", "active", "partial", "revoking", "revoked", "failed", "unenforceable"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "Contractor": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "workspaceId": { "type": "string" },
          "name": { "type": "string" },
          "email": { "type": "string", "format": "email" }
        }
      },
      "ToolAccess": {
        "type": "object",
        "properties": {
          "grantId": { "type": "string" },
          "provider": { "type": "string" },
          "permission": { "type": "string" },
          "externalId": { "type": "string" },
          "status": { "type": "string", "enum": ["pending", "active", "revoking", "revoked", "failed", "unenforceable"] },
          "lastChecked": { "type": "string", "format": "date-time" },
          "message": { "type": "string" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "GrantResponse": {
        "type": "object",
        "properties": {
          "grant": { "$ref": "#/components/schemas/Grant" },
          "contractor": { "$ref": "#/components/schemas/Contractor" },
          "access": { "type": "array", "items": { "$ref": "#/components/schemas/ToolAccess" } }
        }
      },
      "CreateGrantRequest": {
        "type": "object",
        "required": ["contractor", "access"],
        "properties": {
          "workspaceId": { "type": "string" },
          "contractor": { "type": "object", "required": ["name", "email"], "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } },
          "expiresAt": { "type": "string", "format": "date-time" },
          "durationDays": { "type": "integer" },
          "endDate": { "type": "string", "format": "date", "description": "YYYY-MM-DD in the workspace timezone; expiry lands at the workspace's default expiry hour" },
          "access": { "type": "array", "items": { "type": "object", "required": ["provider"], "properties": { "provider": { "type": "string" }, "permission": { "type": "string" } } } }
        }
      },
      "RegrantContractorRequest": {
        "type": "object",
        "required": ["access"],
        "properties": {
          "workspaceId": { "type": "string" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "durationDays": { "type": "integer" },
          "endDate": { "type": "string", "format": "date" },
          "access": { "type": "array", "items": { "type": "object", "required": ["provider"], "properties": { "provider": { "type": "string" }, "permission": { "type": "string" } } } }
        }
      },
      "ProviderCapabilities": {
        "type": "object",
        "properties": {
          "displayName": { "type": "string" },
          "enforcement": { "type": "string", "enum": ["native", "native_scim", "limited_native", "best_effort_bridge"] },
          "canGrant": { "type": "boolean" },
          "canRevoke": { "type": "boolean" },
          "canVerify": { "type": "boolean" }
        }
      },
      "ConnectIntegrationRequest": {
        "type": "object",
        "required": ["provider", "accessToken"],
        "properties": {
          "workspaceId": { "type": "string" },
          "provider": { "type": "string" },
          "accessToken": { "type": "string", "description": "Write-only; stored encrypted and never returned. AWS IAM uses ACCESS_KEY_ID:SECRET_ACCESS_KEY." },
          "scopes": { "type": "array", "items": { "type": "string" } },
          "externalTenantId": { "type": "string", "description": "Org/group/workspace identifier for tenant-scoped providers (GitHub org login, GitLab group id, Asana workspace gid, Figma org id)" }
        }
      },
      "IntegrationConnection": {
        "type": "object",
        "properties": {
          "workspaceId": { "type": "string" },
          "provider": { "type": "string" },
          "status": { "type": "string", "enum": ["connected", "disconnected", "degraded"] },
          "scopes": { "type": "array", "items": { "type": "string" } },
          "externalTenantId": { "type": "string" },
          "message": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "AuditEvent": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "workspaceId": { "type": "string" },
          "actorId": { "type": "string" },
          "action": { "type": "string" },
          "targetId": { "type": "string" },
          "message": { "type": "string" },
          "metadata": { "type": "object", "additionalProperties": true },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Workspace": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "ownerEmail": { "type": "string", "format": "email" },
          "timezone": { "type": "string" },
          "defaultExpiryHour": { "type": "integer" },
          "notificationDays": { "type": "array", "items": { "type": "integer" } }
        }
      },
      "APIKey": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "workspaceId": { "type": "string" },
          "name": { "type": "string" },
          "prefix": { "type": "string", "description": "Displayable token prefix (tk_<key id>)" },
          "scopes": { "type": "array", "items": { "type": "string", "enum": ["read", "write"] } },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "lastUsedAt": { "type": "string", "format": "date-time" },
          "revokedAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreateAPIKeyRequest": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "workspaceId": { "type": "string" },
          "name": { "type": "string" },
          "scopes": { "type": "array", "items": { "type": "string", "enum": ["read", "write"] }, "description": "Defaults to [\"read\", \"write\"]" },
          "expiresAt": { "type": "string", "format": "date-time", "description": "Optional; omit for a non-expiring key" }
        }
      },
      "APIKeySecretResponse": {
        "type": "object",
        "properties": {
          "key": { "$ref": "#/components/schemas/APIKey" },
          "secret": { "type": "string", "description": "The full bearer token. Shown exactly once — store it now." }
        }
      }
    }
  }
}
