Quick Start
Get started with emit.run in minutes
Quick Start
This guide walks through the full flow: create a space, get an API key, submit a job, process it with a worker, and watch it complete.
1. Create a Space
In the dashboard, go to Spaces → Create Space. Give it a name like production or email-jobs.
2. Create API Tokens
Open your space and go to Tokens → Create Token.
You'll typically want two tokens:
| Token | Scopes | Used by |
|---|---|---|
my-producer | jobs:create | Your app that submits jobs |
my-worker | Worker preset (or jobs:worker) | Your worker that processes jobs |
Copy each key when it appears — it's shown only once.
3. Create a Job
Submit a job to the dispatch queue. The name is how your worker knows what to do; payload is the data it receives.
const res = await fetch(
`https://emit.run/api/v1/spaces/${spaceId}/jobs`,
{
method: "POST",
headers: {
"x-api-key": process.env.EMIT_PRODUCER_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "send-email",
payload: {
to: "user@example.com",
subject: "Welcome!",
template: "onboarding",
},
}),
}
);
const job = await res.json();
console.log("Created job:", job.id); // { id: "01JLQX...", status: "pending", ... }curl -X POST https://emit.run/api/v1/spaces/YOUR_SPACE_ID/jobs \
-H "x-api-key: emit_YOUR_PRODUCER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "send-email",
"payload": {
"to": "user@example.com",
"subject": "Welcome!",
"template": "onboarding"
}
}'import requests
res = requests.post(
f"https://emit.run/api/v1/spaces/{space_id}/jobs",
headers={
"x-api-key": EMIT_PRODUCER_KEY,
"Content-Type": "application/json",
},
json={
"name": "send-email",
"payload": {
"to": "user@example.com",
"subject": "Welcome!",
"template": "onboarding",
},
},
)
job = res.json()
print("Created job:", job["id"]) # { "id": "01JLQX...", "status": "pending", ... }Optional: webhook callback
Add callbackUrl to get a POST notification when the job completes or goes dead — no polling required:
{
"name": "send-email",
"payload": { "to": "user@example.com", "template": "onboarding" },
"callbackUrl": "https://example.com/webhooks/jobs",
"callbackHeaders": { "Authorization": "Bearer whsec_your_secret" }
}See Callbacks for the full payload shape.
4. Build a Worker
A worker is a loop that polls for jobs, processes them, and reports back. Workers need no SDK — it's plain HTTP.
const API = "https://emit.run/api/v1";
const KEY = process.env.EMIT_WORKER_KEY!;
const SPACE = process.env.EMIT_SPACE_ID!;
const headers = { "x-api-key": KEY, "Content-Type": "application/json" };
async function work() {
while (true) {
// 1. Poll for up to 5 jobs
const pollRes = await fetch(`${API}/spaces/${SPACE}/jobs/poll`, {
method: "POST",
headers,
body: JSON.stringify({ count: 5 }),
});
const { jobs } = await pollRes.json();
if (jobs.length === 0) {
await new Promise((r) => setTimeout(r, 2000)); // back off
continue;
}
for (const job of jobs) {
// 2. Ack — tells the system we're starting; starts the timeout timer
await fetch(`${API}/jobs/${job.id}/ack`, { method: "POST", headers });
try {
// 3. Do the actual work
const result = await processJob(job);
// 4. Report success
await fetch(`${API}/jobs/${job.id}/complete`, {
method: "POST",
headers,
body: JSON.stringify({ result }),
});
} catch (err) {
// 4. Report failure — retries automatically if attempts remain
await fetch(`${API}/jobs/${job.id}/fail`, {
method: "POST",
headers,
body: JSON.stringify({ error: String(err) }),
});
}
}
}
}
async function processJob(job: { name: string; payload: any }) {
switch (job.name) {
case "send-email":
// send the email...
return { sent: true };
default:
throw new Error(`Unknown job type: ${job.name}`);
}
}
work();import os
import time
import requests
API = "https://emit.run/api/v1"
KEY = os.environ["EMIT_WORKER_KEY"]
SPACE = os.environ["EMIT_SPACE_ID"]
HEADERS = {"x-api-key": KEY, "Content-Type": "application/json"}
def work():
while True:
# 1. Poll for up to 5 jobs
res = requests.post(
f"{API}/spaces/{SPACE}/jobs/poll",
headers=HEADERS,
json={"count": 5},
)
jobs = res.json()["jobs"]
if not jobs:
time.sleep(2) # back off
continue
for job in jobs:
# 2. Ack — tells the system we're starting; starts the timeout timer
requests.post(f"{API}/jobs/{job['id']}/ack", headers=HEADERS)
try:
# 3. Do the actual work
result = process_job(job)
# 4. Report success
requests.post(
f"{API}/jobs/{job['id']}/complete",
headers=HEADERS,
json={"result": result},
)
except Exception as e:
# 4. Report failure — retries automatically if attempts remain
requests.post(
f"{API}/jobs/{job['id']}/fail",
headers=HEADERS,
json={"error": str(e)},
)
def process_job(job):
if job["name"] == "send-email":
# send the email...
return {"sent": True}
raise ValueError(f"Unknown job type: {job['name']}")
work()5. Watch Progress (Optional)
The recommended pattern for client-facing apps: your backend creates the job and returns the job ID, your frontend connects using the space's public token to stream live updates.
Every space comes with a pre-generated public token (jobs:read:progress scope) that's safe to embed in client-side code — it can only subscribe to individual job streams and can't create, poll, or see job results.
Find it in the dashboard under Tokens → Public read-only (progress).
// Backend: create the job, return its ID to the client
app.post("/api/send-email", async (req, res) => {
const job = await fetch(`${API}/spaces/${SPACE}/jobs`, {
method: "POST",
headers: { "x-api-key": process.env.EMIT_PRODUCER_KEY!, "Content-Type": "application/json" },
body: JSON.stringify({ name: "send-email", payload: req.body }),
}).then((r) => r.json());
res.json({ jobId: job.id });
// PUBLIC_TOKEN comes from your space dashboard — safe to expose to clients
});
// Frontend: watch the job using the public token
const { jobId } = await submitEmailRequest(formData);
const ws = new WebSocket(
`wss://emit.run/api/v1/realtime/${jobId}?token=${SPACE_PUBLIC_TOKEN}`
);
ws.onmessage = (e) => {
const event = JSON.parse(e.data);
switch (event.type) {
case "progress":
updateProgressBar(event.data.percent);
break;
case "completed":
showSuccess(); // result payload is stripped — client just knows it's done
ws.close();
break;
case "dead":
showError(); // error detail is stripped — client just knows it failed
ws.close();
break;
}
};See WebSockets for the full event reference and space-level streams.
Next Steps
- Authentication — Full scope reference and auth details
- Jobs API — Complete endpoint reference with examples
- Callbacks — Webhook notifications on job completion
- WebSockets — Real-time streaming setup