{ "openapi": "3.1.0", "info": { "title": "Stripe Sync Service", "version": "1.0.0", "description": "Stripe Sync Service — manage pipelines and webhook ingress.\n\n## Endpoints\n\n| Method | Path | Summary |\n|--------|------|---------|\n| GET | /health | Health check |\n| GET | /pipelines | List pipelines |\n| POST | /pipelines | Create pipeline |\n| GET | /pipelines/{id} | Retrieve pipeline |\n| PATCH | /pipelines/{id} | Update pipeline |\n| DELETE | /pipelines/{id} | Delete pipeline |\n| POST | /pipelines/{id}/sync | Run sync for a pipeline |\n| POST | /pipelines/{id}/sync_workflow_test | Run sync using the workflow backfill loop (no Temporal) |\n| POST | /webhooks/{pipeline_id} | Ingest a Stripe webhook event |" }, "paths": { "/health": { "get": { "operationId": "health", "tags": ["Status"], "summary": "Health check", "responses": { "200": { "description": "Server is healthy", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean", "const": true }, "hostname": { "type": "string" } }, "required": ["ok", "hostname"], "additionalProperties": false } } } } } } }, "/pipelines": { "get": { "operationId": "pipelines.list", "tags": ["Pipelines"], "summary": "List pipelines", "responses": { "200": { "description": "List of pipelines", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Pipeline" } }, "has_more": { "type": "boolean" } }, "required": ["data", "has_more"], "additionalProperties": false } } } } } }, "post": { "operationId": "pipelines.create", "tags": ["Pipelines"], "summary": "Create pipeline", "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "description": "Optional pipeline identifier. If omitted, the service generates one (e.g. pipe_abc123).", "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$" }, "source": { "$ref": "#/components/schemas/SourceConfig" }, "destination": { "$ref": "#/components/schemas/DestinationConfig" }, "streams": { "description": "Selected streams to sync. All streams synced if omitted.", "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Stream (table) name to sync." }, "sync_mode": { "description": "How the source reads this stream. Defaults to full_refresh.", "type": "string", "enum": ["incremental", "full_refresh"] }, "backfill_limit": { "description": "Cap backfill to this many records, then mark the stream complete.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": ["name"] } } }, "required": ["source", "destination"] } } } }, "responses": { "201": { "description": "Created pipeline", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pipeline" } } } }, "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } }, "409": { "description": "Pipeline id already exists", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } } } } }, "/pipelines/{id}": { "get": { "operationId": "pipelines.get", "tags": ["Pipelines"], "summary": "Retrieve pipeline", "parameters": [ { "in": "path", "name": "id", "schema": { "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Unique pipeline identifier (e.g. pipe_abc123).", "example": "pipe_abc123" }, "required": true, "description": "Unique pipeline identifier (e.g. pipe_abc123)." } ], "responses": { "200": { "description": "Retrieved pipeline with status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pipeline" } } } }, "404": { "description": "Not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } } } }, "patch": { "operationId": "pipelines.update", "tags": ["Pipelines"], "summary": "Update pipeline", "parameters": [ { "in": "path", "name": "id", "schema": { "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Unique pipeline identifier (e.g. pipe_abc123).", "example": "pipe_abc123" }, "required": true, "description": "Unique pipeline identifier (e.g. pipe_abc123)." } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "description": "Optional pipeline identifier. If omitted, the service generates one (e.g. pipe_abc123).", "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$" }, "source": { "$ref": "#/components/schemas/SourceConfig" }, "destination": { "$ref": "#/components/schemas/DestinationConfig" }, "streams": { "description": "Selected streams to sync. All streams synced if omitted.", "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Stream (table) name to sync." }, "sync_mode": { "description": "How the source reads this stream. Defaults to full_refresh.", "type": "string", "enum": ["incremental", "full_refresh"] }, "backfill_limit": { "description": "Cap backfill to this many records, then mark the stream complete.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": ["name"] } }, "desired_status": { "description": "Set to \"paused\" to pause, \"active\" to resume, \"deleted\" to tear down.", "type": "string", "enum": ["active", "paused", "deleted"] } } } } } }, "responses": { "200": { "description": "Updated pipeline", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pipeline" } } } }, "400": { "description": "Bad request", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } }, "404": { "description": "Not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } }, "409": { "description": "Invalid status transition", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } } } }, "delete": { "operationId": "pipelines.delete", "tags": ["Pipelines"], "summary": "Delete pipeline", "parameters": [ { "in": "path", "name": "id", "schema": { "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Unique pipeline identifier (e.g. pipe_abc123).", "example": "pipe_abc123" }, "required": true, "description": "Unique pipeline identifier (e.g. pipe_abc123)." } ], "responses": { "200": { "description": "Deleted pipeline", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "string" }, "deleted": { "type": "boolean", "const": true } }, "required": ["id", "deleted"], "additionalProperties": false } } } }, "404": { "description": "Not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } } } } }, "/pipelines/{id}/sync": { "post": { "operationId": "pipelines.sync", "tags": ["Pipelines"], "summary": "Run sync for a pipeline", "description": "Triggers an ad-hoc sync run for the pipeline and streams NDJSON messages (records, state, progress, eof) back to the client. Persists the ending sync_state on the pipeline so the next run resumes where this one left off.", "parameters": [ { "in": "path", "name": "id", "schema": { "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Unique pipeline identifier (e.g. pipe_abc123).", "example": "pipe_abc123" }, "required": true, "description": "Unique pipeline identifier (e.g. pipe_abc123)." }, { "in": "query", "name": "state_limit", "schema": { "description": "Max state messages before stopping", "type": "number" }, "description": "Max state messages before stopping" }, { "in": "query", "name": "time_limit", "schema": { "description": "Stop after N seconds", "type": "number" }, "description": "Stop after N seconds" }, { "in": "query", "name": "sync_run_id", "schema": { "description": "Sync run identifier (resumes or starts fresh)", "type": "string" }, "description": "Sync run identifier (resumes or starts fresh)" }, { "in": "query", "name": "no_state", "schema": { "description": "Ignore and do not persist the pipeline sync_state checkpoint", "type": "boolean" }, "description": "Ignore and do not persist the pipeline sync_state checkpoint" } ], "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "properties": { "source": { "$ref": "#/components/schemas/SourceConfig" }, "destination": { "$ref": "#/components/schemas/DestinationConfig" }, "streams": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Stream (table) name to sync." }, "sync_mode": { "description": "How the source reads this stream. Defaults to full_refresh.", "type": "string", "enum": ["incremental", "full_refresh"] }, "backfill_limit": { "description": "Cap backfill to this many records, then mark the stream complete.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": ["name"] } }, "sync_state": { "description": "Explicit sync checkpoint override for resumed ad-hoc runs", "$ref": "#/components/schemas/SyncState" } } } } } }, "responses": { "200": { "description": "Streaming NDJSON sync output", "content": { "application/x-ndjson": { "schema": { "type": "object", "properties": {}, "additionalProperties": {} } } } }, "404": { "description": "Pipeline not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } } } } }, "/pipelines/{id}/sync_workflow_test": { "post": { "operationId": "pipelines.sync_workflow_test", "tags": ["Pipelines"], "summary": "Run sync using the workflow backfill loop (no Temporal)", "description": "Exercises the same backfill loop code that the Temporal workflow uses, but runs inline without a Temporal server. Useful for testing the full workflow logic end-to-end.", "parameters": [ { "in": "path", "name": "id", "schema": { "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Unique pipeline identifier (e.g. pipe_abc123).", "example": "pipe_abc123" }, "required": true, "description": "Unique pipeline identifier (e.g. pipe_abc123)." }, { "in": "query", "name": "state_limit", "schema": { "description": "Max state messages per iteration", "type": "number" }, "description": "Max state messages per iteration" }, { "in": "query", "name": "time_limit", "schema": { "description": "Time limit per iteration (seconds)", "type": "number" }, "description": "Time limit per iteration (seconds)" } ], "responses": { "200": { "description": "Backfill result with final eof and sync state", "content": { "application/json": { "schema": { "type": "object", "properties": { "eof": { "type": "object", "properties": {}, "additionalProperties": {} }, "sync_state": { "type": "object", "properties": {}, "additionalProperties": {} } }, "required": ["eof"], "additionalProperties": false } } } }, "404": { "description": "Pipeline not found", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": {} }, "required": ["error"], "additionalProperties": false } } } } } } }, "/webhooks/{pipeline_id}": { "post": { "operationId": "webhooks.push", "tags": ["Webhooks"], "summary": "Ingest a Stripe webhook event", "description": "Receives a raw Stripe webhook event, verifies its signature using the pipeline's webhook secret, and enqueues it for processing by the active pipeline.", "parameters": [ { "in": "path", "name": "pipeline_id", "schema": { "type": "string", "example": "pipe_abc123" }, "required": true } ], "responses": { "200": { "description": "Event accepted", "content": { "text/plain": { "schema": { "type": "string", "const": "ok" } } } } } } } }, "components": { "schemas": { "SourceConfig": { "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "const": "stripe" }, "stripe": { "$ref": "#/components/schemas/SourceStripeConfig" } }, "required": ["type", "stripe"] } ], "type": "object", "discriminator": { "propertyName": "type" } }, "SourceStripeConfig": { "type": "object", "properties": { "api_key": { "type": "string", "description": "Stripe API key (sk_test_... or sk_live_...)" }, "account_id": { "type": "string", "description": "Stripe account ID (resolved from API if omitted)" }, "account_created": { "type": "integer", "minimum": 0, "maximum": 9007199254740991, "description": "Stripe account creation timestamp in unix seconds (resolved from API if omitted)" }, "livemode": { "type": "boolean", "description": "Whether this is a live mode sync" }, "api_version": { "type": "string", "enum": [ "2026-03-25.dahlia", "2026-02-25.clover", "2026-01-28.clover", "2025-12-15.clover", "2025-11-17.clover", "2025-10-29.clover", "2025-09-30.clover", "2025-08-27.basil", "2025-07-30.basil", "2025-06-30.basil", "2025-05-28.basil", "2025-04-30.basil", "2025-03-31.basil", "2025-02-24.acacia", "2025-01-27.acacia", "2024-12-18.acacia", "2024-11-20.acacia", "2024-10-28.acacia", "2024-09-30.acacia", "2024-06-20", "2024-04-10", "2024-04-03", "2023-10-16", "2023-08-16", "2022-11-15", "2022-08-01", "2020-08-27", "2020-03-02", "2019-12-03", "2019-11-05", "2019-10-17", "2019-10-08", "2019-09-09", "2019-08-14", "2019-05-16", "2019-03-14", "2019-02-19", "2019-02-11", "2018-11-08", "2018-10-31", "2018-09-24", "2018-09-06", "2018-08-23", "2018-07-27", "2018-05-21", "2018-02-28", "2018-02-06", "2018-02-05", "2018-01-23", "2017-12-14", "2017-08-15" ] }, "base_url": { "type": "string", "format": "uri", "description": "Override the Stripe API base URL (e.g. http://localhost:12111 for stripe-mock)" }, "webhook_url": { "type": "string", "format": "uri", "description": "URL for managed webhook endpoint registration" }, "webhook_secret": { "type": "string", "description": "Webhook signing secret (whsec_...) for signature verification" }, "websocket": { "type": "boolean", "description": "Enable WebSocket streaming for live events" }, "poll_events": { "type": "boolean", "description": "Enable events API polling for incremental sync after backfill" }, "webhook_port": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Port for built-in webhook HTTP listener (e.g. 4242)" }, "revalidate_objects": { "type": "array", "items": { "type": "string" }, "description": "Object types to re-fetch from Stripe API on webhook (e.g. [\"subscription\"])" }, "backfill_limit": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "Max objects to backfill per stream (useful for testing)" }, "max_concurrent_streams": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "Max streams paginating in parallel (default: 5, capped at catalog size)." }, "rate_limit": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991, "description": "Override max requests per second (default: auto-derived from API key mode — 20 live, 10 test)." } }, "required": ["api_key"], "additionalProperties": false }, "DestinationConfig": { "oneOf": [ { "type": "object", "properties": { "type": { "type": "string", "const": "postgres" }, "postgres": { "$ref": "#/components/schemas/DestinationPostgresConfig" } }, "required": ["type", "postgres"] }, { "type": "object", "properties": { "type": { "type": "string", "const": "google_sheets" }, "google_sheets": { "$ref": "#/components/schemas/DestinationGoogleSheetsConfig" } }, "required": ["type", "google_sheets"] } ], "type": "object", "discriminator": { "propertyName": "type" } }, "DestinationPostgresConfig": { "type": "object", "properties": { "url": { "type": "string", "description": "Postgres connection string" }, "connection_string": { "type": "string", "description": "Deprecated alias for url; prefer url" }, "schema": { "default": "public", "type": "string", "description": "Target schema name (e.g. \"stripe\")" }, "batch_size": { "default": 100, "type": "number", "description": "Records to buffer before flushing" }, "aws": { "type": "object", "properties": { "host": { "type": "string", "description": "Postgres host for RDS IAM auth" }, "port": { "default": 5432, "type": "number", "description": "Postgres port for RDS IAM auth" }, "database": { "type": "string", "description": "Database name for RDS IAM auth" }, "user": { "type": "string", "description": "Database user for RDS IAM auth" }, "region": { "type": "string", "description": "AWS region for RDS instance" }, "role_arn": { "type": "string", "description": "IAM role ARN to assume (cross-account)" }, "external_id": { "type": "string", "description": "External ID for STS AssumeRole" } }, "required": ["host", "database", "user", "region"], "additionalProperties": false, "description": "AWS RDS IAM authentication config" }, "ssl_ca_pem": { "type": "string", "description": "PEM-encoded CA certificate for SSL verification (required for verify-ca / verify-full with a private CA)" } }, "additionalProperties": false }, "DestinationGoogleSheetsConfig": { "type": "object", "properties": { "client_id": { "type": "string", "description": "Google OAuth2 client ID (env: GOOGLE_CLIENT_ID)" }, "client_secret": { "type": "string", "description": "Google OAuth2 client secret (env: GOOGLE_CLIENT_SECRET)" }, "access_token": { "type": "string", "description": "OAuth2 access token" }, "refresh_token": { "type": "string", "description": "OAuth2 refresh token" }, "spreadsheet_id": { "type": "string", "description": "Target spreadsheet ID (created if omitted)" }, "spreadsheet_title": { "default": "Stripe Sync", "type": "string", "description": "Title when creating a new spreadsheet" }, "batch_size": { "default": 50, "type": "number", "description": "Rows per Sheets API append call" } }, "required": ["access_token", "refresh_token"], "additionalProperties": false }, "SyncState": { "type": "object", "properties": { "source": { "$ref": "#/components/schemas/SourceState" }, "destination": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": {}, "description": "Destination connector state." }, "sync_run": { "type": "object", "properties": { "sync_run_id": { "description": "Identifies a finite backfill run. Omit for continuous sync.", "type": "string" }, "time_ceiling": { "description": "Frozen upper bound (ISO 8601). Set on first invocation when sync_run_id is present; reused on continuation.", "type": "string" }, "progress": { "type": "object", "properties": { "started_at": { "type": "string", "description": "When this sync started (ISO 8601); generally equals time_ceiling." }, "elapsed_ms": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Wall-clock milliseconds since the sync run started." }, "global_state_count": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Total source_state messages observed so far." }, "connection_status": { "description": "Set when source or destination emits connection_status: failed.", "type": "object", "properties": { "status": { "type": "string", "enum": ["succeeded", "failed"], "description": "Whether the connection check passed." }, "message": { "description": "Human-readable explanation of the check result.", "type": "string" } }, "required": ["status"] }, "derived": { "type": "object", "properties": { "status": { "type": "string", "enum": ["started", "succeeded", "failed"], "description": "succeeded = all streams completed/skipped; failed = connection_status failed OR any stream errored." }, "records_per_second": { "type": "number", "description": "Overall throughput for the entire run." }, "states_per_second": { "type": "number", "description": "State checkpoints per second." } }, "required": ["status", "records_per_second", "states_per_second"], "description": "Computed aggregates." }, "streams": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "object", "properties": { "status": { "type": "string", "enum": ["not_started", "started", "completed", "skipped", "errored"], "description": "Current state, derived from stream_status events." }, "state_count": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Number of state checkpoints for this stream." }, "record_count": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991, "description": "Records synced for this stream in this run." }, "message": { "description": "Human-readable status message (error reason, skip reason, etc).", "type": "string" }, "total_range": { "description": "Full backfill time span for this stream.", "type": "object", "properties": { "gte": { "type": "string", "description": "Inclusive lower bound (ISO 8601)." }, "lt": { "type": "string", "description": "Exclusive upper bound (ISO 8601)." } }, "required": ["gte", "lt"] }, "completed_ranges": { "description": "Completed time sub-ranges within the total_range.", "type": "array", "items": { "type": "object", "properties": { "gte": { "type": "string", "description": "Inclusive lower bound (ISO 8601)." }, "lt": { "type": "string", "description": "Exclusive upper bound (ISO 8601)." } }, "required": ["gte", "lt"] } } }, "required": ["status", "state_count", "record_count"], "description": "Per-stream progress snapshot." }, "description": "Per-stream progress, keyed by stream name." } }, "required": [ "started_at", "elapsed_ms", "global_state_count", "derived", "streams" ], "description": "Accumulated progress from prior requests in this run." } }, "required": ["progress"], "description": "Engine-managed run state — sync_run_id, time_ceiling, accumulated progress." } }, "required": ["source", "destination", "sync_run"], "description": "Full sync checkpoint with separate sections for source, destination, and sync run. Connectors only see their own section; the engine manages routing." }, "SourceState": { "type": "object", "properties": { "streams": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": {}, "description": "Per-stream checkpoint data, keyed by stream name." }, "global": { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": {}, "description": "Source-wide state shared across all streams." } }, "required": ["streams", "global"], "description": "Source connector state — cursors, backfill progress, events cursors." }, "Pipeline": { "type": "object", "properties": { "id": { "type": "string", "minLength": 3, "maxLength": 64, "pattern": "^[a-z][a-z0-9_-]*$", "description": "Unique pipeline identifier (e.g. pipe_abc123)." }, "source": { "$ref": "#/components/schemas/SourceConfig" }, "destination": { "$ref": "#/components/schemas/DestinationConfig" }, "streams": { "description": "Selected streams to sync. All streams synced if omitted.", "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "Stream (table) name to sync." }, "sync_mode": { "description": "How the source reads this stream. Defaults to full_refresh.", "type": "string", "enum": ["incremental", "full_refresh"] }, "backfill_limit": { "description": "Cap backfill to this many records, then mark the stream complete.", "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": ["name"], "additionalProperties": false } }, "desired_status": { "default": "active", "description": "User-controlled lifecycle state. Set via PATCH to pause, resume, or delete.", "type": "string", "enum": ["active", "paused", "deleted"] }, "status": { "default": "setup", "description": "Workflow-controlled execution state. Updated by the Temporal workflow.", "type": "string", "enum": ["setup", "backfill", "ready", "paused", "teardown", "error"] }, "sync_state": { "description": "Latest full sync checkpoint emitted by the engine. Includes source, destination, and sync-run state for the next request.", "$ref": "#/components/schemas/SyncState" } }, "required": ["id", "source", "destination", "desired_status", "status"], "additionalProperties": false } } } }