Jobcelis
Official Elixir SDK for the Jobcelis Event Infrastructure Platform.
All API calls go to https://jobcelis.com by default -- you only need your API key to get started.
Installation
Add jobcelis to your list of dependencies in mix.exs:
def deps do
[
{:jobcelis, "~> 1.0"}
]
end
Then run:
mix deps.get
Quick Start
# Only your API key is required -- connects to https://jobcelis.com automatically
client = Jobcelis.client("your_api_key")
{:ok, event} = Jobcelis.send_event(client, "order.created", %{order_id: "123", amount: 99.99})
Custom URL: If you're self-hosting Jobcelis, you can override the base URL:
client = Jobcelis.client("your_api_key", base_url: "https://your-instance.example.com")
Authentication
The auth methods do not require an API key. Use them to register, log in, and manage JWT tokens.
client = Jobcelis.client("")
# Register a new account
{:ok, user} = Jobcelis.register(client, "alice@example.com", "SecurePass123!", name: "Alice")
# Log in -- returns JWT access token and refresh token
{:ok, session} = Jobcelis.login(client, "alice@example.com", "SecurePass123!")
access_token = session["token"]
refresh_token = session["refresh_token"]
# Set the JWT for subsequent authenticated calls
client = Jobcelis.set_auth_token(client, access_token)
# Refresh an expired token
{:ok, new_session} = Jobcelis.refresh_token(client, refresh_token)
client = Jobcelis.set_auth_token(client, new_session["token"])
# Verify MFA (requires auth token already set)
{:ok, result} = Jobcelis.verify_mfa(client, access_token, "123456")
Events
# Send a single event
{:ok, event} = Jobcelis.send_event(client, "order.created", %{order_id: "123", amount: 99.99})
# Send batch events (up to 1000)
{:ok, batch} = Jobcelis.send_events(client, [
%{topic: "order.created", payload: %{order_id: "1"}},
%{topic: "order.created", payload: %{order_id: "2"}}
])
# List events with pagination
{:ok, events} = Jobcelis.list_events(client, limit: 25)
{:ok, next_page} = Jobcelis.list_events(client, limit: 25, cursor: events["cursor"])
# Get / delete a single event
{:ok, event} = Jobcelis.get_event(client, "evt_abc123")
:ok = Jobcelis.delete_event(client, "evt_abc123")
Simulate
# Dry-run an event to see which webhooks would fire
{:ok, result} = Jobcelis.simulate_event(client, "order.created", %{order_id: "test"})
Webhooks
# Create a webhook
{:ok, webhook} = Jobcelis.create_webhook(client, "https://example.com/webhook",
topics: ["order.*"]
)
# List, get, update, delete
{:ok, webhooks} = Jobcelis.list_webhooks(client)
{:ok, wh} = Jobcelis.get_webhook(client, "wh_abc123")
{:ok, _} = Jobcelis.update_webhook(client, "wh_abc123", %{url: "https://new-url.com/hook"})
:ok = Jobcelis.delete_webhook(client, "wh_abc123")
# Health and templates
{:ok, health} = Jobcelis.webhook_health(client, "wh_abc123")
{:ok, templates} = Jobcelis.webhook_templates(client)
Deliveries
{:ok, deliveries} = Jobcelis.list_deliveries(client, limit: 20, status: "failed")
{:ok, _} = Jobcelis.retry_delivery(client, "del_abc123")
Dead Letters
{:ok, dead_letters} = Jobcelis.list_dead_letters(client)
{:ok, dl} = Jobcelis.get_dead_letter(client, "dlq_abc123")
{:ok, _} = Jobcelis.retry_dead_letter(client, "dlq_abc123")
{:ok, _} = Jobcelis.resolve_dead_letter(client, "dlq_abc123")
Replays
{:ok, replay} = Jobcelis.create_replay(client, "order.created",
"2026-01-01T00:00:00Z",
"2026-01-31T23:59:59Z",
webhook_id: "wh_abc123" # optional
)
{:ok, replays} = Jobcelis.list_replays(client)
{:ok, r} = Jobcelis.get_replay(client, "rpl_abc123")
:ok = Jobcelis.cancel_replay(client, "rpl_abc123")
Scheduled Jobs
# Create a job
{:ok, job} = Jobcelis.create_job(client, "daily-report", "default", "0 9 * * *",
payload: %{type: "daily"}
)
# CRUD
{:ok, jobs} = Jobcelis.list_jobs(client, limit: 10)
{:ok, job} = Jobcelis.get_job(client, "job_abc123")
{:ok, _} = Jobcelis.update_job(client, "job_abc123", %{cron_expression: "0 10 * * *"})
:ok = Jobcelis.delete_job(client, "job_abc123")
# List runs for a job
{:ok, runs} = Jobcelis.list_job_runs(client, "job_abc123", limit: 20)
# Preview cron schedule
{:ok, preview} = Jobcelis.cron_preview(client, "0 9 * * *", count: 10)
Pipelines
{:ok, pipeline} = Jobcelis.create_pipeline(client, "order-processing",
["order.created"],
[
%{type: "filter", config: %{field: "amount", gt: 100}},
%{type: "transform", config: %{add_field: "priority", value: "high"}}
]
)
{:ok, pipelines} = Jobcelis.list_pipelines(client)
{:ok, p} = Jobcelis.get_pipeline(client, "pipe_abc123")
{:ok, _} = Jobcelis.update_pipeline(client, "pipe_abc123", %{name: "order-processing-v2"})
:ok = Jobcelis.delete_pipeline(client, "pipe_abc123")
# Test a pipeline with a sample payload
{:ok, result} = Jobcelis.test_pipeline(client, "pipe_abc123", %{
topic: "order.created",
payload: %{id: "1"}
})
Event Schemas
{:ok, schema} = Jobcelis.create_event_schema(client, "order.created", %{
type: "object",
properties: %{
order_id: %{type: "string"},
amount: %{type: "number"}
},
required: ["order_id", "amount"]
})
{:ok, schemas} = Jobcelis.list_event_schemas(client)
{:ok, s} = Jobcelis.get_event_schema(client, "sch_abc123")
{:ok, _} = Jobcelis.update_event_schema(client, "sch_abc123", %{schema: %{type: "object"}})
:ok = Jobcelis.delete_event_schema(client, "sch_abc123")
# Validate a payload against a topic's schema
{:ok, result} = Jobcelis.validate_payload(client, "order.created", %{order_id: "123", amount: 50})
Sandbox
# Create a temporary endpoint for testing
{:ok, endpoint} = Jobcelis.create_sandbox_endpoint(client, name: "my-test")
{:ok, endpoints} = Jobcelis.list_sandbox_endpoints(client)
# Inspect received requests
{:ok, requests} = Jobcelis.list_sandbox_requests(client, "sbx_abc123", limit: 20)
:ok = Jobcelis.delete_sandbox_endpoint(client, "sbx_abc123")
Analytics
{:ok, events_chart} = Jobcelis.events_per_day(client, days: 30)
{:ok, deliveries_chart} = Jobcelis.deliveries_per_day(client, days: 7)
{:ok, topics} = Jobcelis.top_topics(client, limit: 5)
{:ok, stats} = Jobcelis.webhook_stats(client)
Project and Token Management
# Current project
{:ok, project} = Jobcelis.get_project(client)
{:ok, _} = Jobcelis.update_project(client, %{name: "My Project v2"})
# Topics
{:ok, topics} = Jobcelis.list_topics(client)
# API token
{:ok, token} = Jobcelis.get_token(client)
{:ok, new_token} = Jobcelis.regenerate_token(client)
# Limit the token to scopes and client IPs (empty allowed_ips = any IP)
{:ok, _} = Jobcelis.update_token_restrictions(client, scopes: ["events:read"], allowed_ips: ["203.0.113.10"])
Multi-Project Management
{:ok, projects} = Jobcelis.list_projects(client)
{:ok, new_project} = Jobcelis.create_project(client, "staging-env")
{:ok, p} = Jobcelis.get_project_by_id(client, "proj_abc123")
{:ok, _} = Jobcelis.update_project_by_id(client, "proj_abc123", %{name: "production-env"})
{:ok, _} = Jobcelis.set_default_project(client, "proj_abc123")
:ok = Jobcelis.delete_project(client, "proj_abc123")
Team Members
{:ok, members} = Jobcelis.list_members(client, "proj_abc123")
{:ok, member} = Jobcelis.add_member(client, "proj_abc123", "alice@example.com", role: "admin")
{:ok, _} = Jobcelis.update_member(client, "proj_abc123", "mem_abc123", "viewer")
:ok = Jobcelis.remove_member(client, "proj_abc123", "mem_abc123")
Invitations
# List pending invitations
{:ok, invitations} = Jobcelis.list_pending_invitations(client)
# Accept or reject
{:ok, _} = Jobcelis.accept_invitation(client, "inv_abc123")
{:ok, _} = Jobcelis.reject_invitation(client, "inv_def456")
Audit Logs
{:ok, logs} = Jobcelis.list_audit_logs(client, limit: 100)
{:ok, next_page} = Jobcelis.list_audit_logs(client, cursor: logs["cursor"])
Data Export
Export methods return raw binary (CSV or JSON).
# Export as CSV
{:ok, csv_data} = Jobcelis.export_events(client, format: "csv")
File.write!("events.csv", csv_data)
# Export as JSON
{:ok, json_data} = Jobcelis.export_deliveries(client, format: "json")
# Other exports
{:ok, _} = Jobcelis.export_jobs(client, format: "csv")
{:ok, _} = Jobcelis.export_audit_log(client, format: "csv")
GDPR / Privacy
# Consent management
{:ok, consents} = Jobcelis.get_consents(client)
{:ok, _} = Jobcelis.accept_consent(client, "marketing")
# Data portability
{:ok, my_data} = Jobcelis.export_my_data(client)
# Processing restrictions
{:ok, _} = Jobcelis.restrict_processing(client)
:ok = Jobcelis.lift_restriction(client)
# Right to object
{:ok, _} = Jobcelis.object_to_processing(client)
:ok = Jobcelis.restore_consent(client)
Health Check
{:ok, health} = Jobcelis.health(client)
{:ok, status} = Jobcelis.status(client)
Error Handling
All API calls return {:ok, result} on success or {:error, %Jobcelis.Error{}} on failure.
case Jobcelis.get_event(client, "nonexistent") do
{:ok, event} ->
IO.inspect(event)
{:error, %Jobcelis.Error{status: 404, detail: detail}} ->
IO.puts("Not found: #{inspect(detail)}")
{:error, %Jobcelis.Error{status: status, detail: detail}} ->
IO.puts("Error #{status}: #{inspect(detail)}")
end
Webhook Signature Verification
# In a Phoenix controller
def webhook(conn, _params) do
{:ok, body, conn} = Plug.Conn.read_body(conn)
signature = Plug.Conn.get_req_header(conn, "x-signature") |> List.first("")
if Jobcelis.WebhookVerifier.verify("your_webhook_secret", body, signature) do
event = Jason.decode!(body)
IO.puts("Received: #{event["topic"]}")
send_resp(conn, 200, "OK")
else
send_resp(conn, 401, "Invalid signature")
end
end
License
BSL-1.1 (Business Source License)
Releasing
Bump the version in mix.exs and merge to main. The Release workflow publishes it to Hex.pm and tags the commit. Changes that do not bump the version publish nothing.