Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ivory.finance/llms.txt

Use this file to discover all available pages before exploring further.

Authentication

Authorization: Bearer <access_token>
All endpoints except the webhook require a JWT Bearer token. The tenant_id is extracted from the token’s sub claim.

Register a connector

POST /v1/admin/connectors
Registers a new connector and schedules its first run within 5 minutes. Validates that destination_catalog exists and is active for this tenant in lakehouse.tenant_catalogs.

Request body

FieldRequiredDefaultDescription
source_systemYesTap identity used for staging schema: salesforce, hubspot, stripe, etc.
tap_nameYesSinger tap package name: tap-salesforce, tap-hubspot, tap-stripe, etc.
destination_catalogYesMust match a provisioned lakehouse.tenant_catalogs.catalog_name for this tenant
destination_tableYesFinal Iceberg table name (post-transform)
sync_scheduleNo0 2 * * *Cron expression for the runner scheduler
config_encryptedNonullEncrypted tap config (AES-256, encrypted client-side)
tap_versionNonullPin a specific tap version; null = latest
connector_typeNomeltanomeltano | airbyte | fivetran | custom
meltano_environmentNoprodMeltano environment name
curl -X POST https://api.ivory.finance/v1/admin/connectors \
  -H "Authorization: Bearer $IVORY_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "source_system": "salesforce",
    "tap_name": "tap-salesforce",
    "destination_catalog": "ivory-lakehouse",
    "destination_table": "salesforce_opportunities",
    "sync_schedule": "0 */6 * * *"
  }'

Response 201 Created

{
  "id": "a1b2c3d4-...",
  "source_system": "salesforce",
  "tap_name": "tap-salesforce",
  "iomete_namespace": "ivory_tenant_acme",
  "iomete_table": "salesforce_opportunities",
  "webhook_secret": "8f3a...",
  "webhook_url": "/v1/etl/meltano/webhook",
  "next_run_at": "~5 minutes",
  "status": "active"
}
Save webhook_secret — it is only returned on creation and is never exposed again via the API. Configure it in your runner environment.

List connectors

GET /v1/admin/connectors
Returns all connectors for the authenticated tenant with last-run summary.
curl https://api.ivory.finance/v1/admin/connectors \
  -H "Authorization: Bearer $IVORY_JWT"

Response 200 OK

{
  "connectors": [
    {
      "id": "a1b2c3d4-...",
      "source_system": "salesforce",
      "tap_name": "tap-salesforce",
      "connector_type": "meltano",
      "destination_catalog": "ivory-lakehouse",
      "iomete_namespace": "ivory_tenant_acme",
      "iomete_table": "salesforce_opportunities",
      "sync_schedule": "0 */6 * * *",
      "status": "active",
      "consecutive_failures": 0,
      "rows_total": 142830,
      "last_synced_at": "2026-03-28T12:00:00Z",
      "next_run_at": "2026-03-28T18:00:00Z",
      "last_run_status": "completed",
      "last_run_rows": 1204,
      "last_run_rows_loaded": 1204,
      "last_run_at": "2026-03-28T12:01:43Z",
      "last_error": null
    }
  ],
  "count": 1
}

Get connector detail

GET /v1/admin/connectors/{id}
Full connector record including mapping count, run count, and cumulative IOMETE rows. webhook_secret and config_encrypted are never returned.

Response 200 OK

{
  "id": "a1b2c3d4-...",
  "source_system": "salesforce",
  "status": "active",
  "mapping_count": 48,
  "run_count": 312,
  "total_rows_loaded_iomete": 142830,
  "rows_total": 142830,
  "tap_state": { "bookmarks": { "Opportunity": { "SystemModstamp": "2026-03-28T12:01:00Z" } } },
  "next_run_at": "2026-03-28T18:00:00Z",
  "consecutive_failures": 0,
  "tap_version": null,
  "meltano_environment": "prod"
}

Trigger a manual sync

POST /v1/admin/connectors/{id}/trigger
Sets next_run_at = now(). The runner picks it up within its next poll cycle (≤ 30 s). Returns 409 if the connector is paused.
curl -X POST https://api.ivory.finance/v1/admin/connectors/a1b2c3d4-.../trigger \
  -H "Authorization: Bearer $IVORY_JWT"

Response 202 Accepted

{
  "connector_id": "a1b2c3d4-...",
  "source_system": "salesforce",
  "status": "queued",
  "message": "Sync enqueued. The runner will pick it up within 60s."
}

Pause a connector

POST /v1/admin/connectors/{id}/pause
Stops the scheduler from dispatching new runs. Clears next_run_at. In-progress runs are not interrupted.

Response 200 OK

{ "connector_id": "a1b2c3d4-...", "status": "paused" }

Resume a connector

POST /v1/admin/connectors/{id}/resume
Re-activates a paused or errored connector. Sets next_run_at = now() + schedule_interval.

Response 200 OK

{ "connector_id": "a1b2c3d4-...", "status": "active" }

Delete a connector

DELETE /v1/admin/connectors/{id}
Permanently deletes the connector and cascade-deletes all field mappings, sync runs, and runner job records. Irreversible. The Singer STATE bookmark is also deleted — the next sync after re-registration will be a full load.

Response 204 No Content


Sync run history

GET /v1/admin/connectors/{id}/runs?limit=50
Returns sync run history, latest first.
Query paramDefaultMaxDescription
limit50200Number of runs to return

Response 200 OK

{
  "runs": [
    {
      "id": "run-uuid-...",
      "status": "completed",
      "rows_synced": 1204,
      "rows_loaded": 1204,
      "transform_errors": 0,
      "iomete_table": "ivory_tenant_acme.salesforce_opportunities",
      "started_at": "2026-03-28T12:00:00Z",
      "completed_at": "2026-03-28T12:01:43Z",
      "duration_s": 103,
      "error_msg": null
    }
  ],
  "count": 1
}
FieldDescription
rows_syncedRows read from the Singer tap (reported by the runner)
rows_loadedRows successfully written to IOMETE Iceberg (after transform)
transform_errorsRows that failed the Python transform expression
iomete_tableFully-qualified Iceberg table written (namespace.table)

Connector statuses

StatusDescription
activeScheduler is running; next run at next_run_at
pausedScheduler is disabled; no runs will be dispatched
errorCircuit-breaker tripped after 3 consecutive failures; use resume to re-activate

Webhook callback

POST /v1/etl/meltano/webhook
Called by meltano_runner_worker when a tap run completes. Protected by HMAC-SHA256 — not intended for direct client use. See Field Mappings for the full webhook flow.