Schedule a signal
Save a batch request and have Up2Data re-submit it on a schedule.
https://api.up2data.ai/v1/signalsCreating a signal is free. On every tick Up2Data submits request as a POST /v1/batch job on your behalf, bills it at the normal batch rates from your balance at that moment, and sends batch.completed to request.webhook_url when the job finishes. request is the exact batch body you would send yourself, so every item shape and flag that batch accepts works here; only items (1 to 1,000 objects) and webhook_url (https) are checked when you create the signal, everything else is validated when a run submits it. schedule.interval_ms is required and must be at least 60000 (one minute). schedule.start_at defaults to now; schedule.ends_at and schedule.max_runs are optional, omit both for a signal that runs until you pause or delete it. After downtime a missed tick runs once and the schedule realigns; there is no catch-up burst.
A run that fails with 402 insufficient_credits pauses the signal with pause_reason insufficient_credits. Top up, then resume it; the skipped step runs on the next tick. A run rejected with 400 pauses it with invalid_request, since the same body would fail every time; fix request with PATCH and resume.
Every run submits the batch with an Idempotency-Key derived from the signal and run number, so a retried submission can never create a second job for the same tick.
#Request
| Field | Type | Notes |
|---|---|---|
name | string | Optional label, at most 200 characters. |
schedulerequired | object | When and how often to run. |
schedule.interval_msrequired | integer | Milliseconds between runs. At least 60000. |
schedule.start_at | string | ISO 8601. When the first run fires. Defaults to now. |
schedule.ends_at | string | ISO 8601. No run is scheduled after this instant. Must be after start_at. |
schedule.max_runs | integer | Total number of runs, at least 1. The signal completes once run_count reaches it. |
requestrequired | object | A POST /batch body: items plus optional webhook_url. Submitted byte for byte on every run. |
request.itemsrequired | object[] | 1 to 1,000 batch items, the same shapes POST /batch accepts. |
request.webhook_url | string | https URL that receives batch.completed for each run's job. |
#Call it
curl -X POST "https://api.up2data.ai/v1/signals" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Watch key accounts",
"schedule": {
"interval_ms": 86400000,
"start_at": "2026-11-01T08:00:00Z",
"max_runs": 30
},
"request": {
"items": [
{
"type": "profile",
"url": "https://linkedin.com/in/a"
},
{
"type": "company",
"url": "https://linkedin.com/company/acme"
}
],
"webhook_url": "https://your-server.com/hooks/up2data"
}
}'import requests
url = "https://api.up2data.ai/v1/signals"
headers = {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
}
payload = {
"name": "Watch key accounts",
"schedule": {
"interval_ms": 86400000,
"start_at": "2026-11-01T08:00:00Z",
"max_runs": 30
},
"request": {
"items": [
{
"type": "profile",
"url": "https://linkedin.com/in/a"
},
{
"type": "company",
"url": "https://linkedin.com/company/acme"
}
],
"webhook_url": "https://your-server.com/hooks/up2data"
}
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())const url = "https://api.up2data.ai/v1/signals";
const headers = {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
};
const body = {
"name": "Watch key accounts",
"schedule": {
"interval_ms": 86400000,
"start_at": "2026-11-01T08:00:00Z",
"max_runs": 30
},
"request": {
"items": [
{
"type": "profile",
"url": "https://linkedin.com/in/a"
},
{
"type": "company",
"url": "https://linkedin.com/company/acme"
}
],
"webhook_url": "https://your-server.com/hooks/up2data"
}
};
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
});
const data = await response.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
url := "https://api.up2data.ai/v1/signals"
body := bytes.NewBufferString(`{
"name": "Watch key accounts",
"schedule": {
"interval_ms": 86400000,
"start_at": "2026-11-01T08:00:00Z",
"max_runs": 30
},
"request": {
"items": [
{
"type": "profile",
"url": "https://linkedin.com/in/a"
},
{
"type": "company",
"url": "https://linkedin.com/company/acme"
}
],
"webhook_url": "https://your-server.com/hooks/up2data"
}
}`)
req, err := http.NewRequest("POST", url, body)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Key", "YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}#Response
{
"data": {
"signal_id": "9d1f4c2e-7b3a-4e8f-a1c5-2f6b8d9e0a11",
"name": "Watch key accounts",
"status": "active",
"active": true,
"pause_reason": null,
"schedule": { "interval_ms": 86400000, "start_at": "2026-11-01T08:00:00Z", "ends_at": null, "max_runs": 30 },
"run_count": 0,
"next_run_at": "2026-11-01T08:00:00Z",
"last_run_at": null,
"last_job_id": null,
"last_error": null,
"completed_at": null,
"request": {
"items": [
{ "type": "profile", "url": "https://linkedin.com/in/a" },
{ "type": "company", "url": "https://linkedin.com/company/acme" }
],
"webhook_url": "https://your-server.com/hooks/up2data"
},
"created_at": "2026-10-30T12:00:00Z",
"updated_at": "2026-10-30T12:00:00Z"
},
"meta": { "creditsUsed": 0, "creditsRemaining": 98413, "billed": false, "reason": "signal_created", "requestId": "req_t4sk0001" }
}#Errors
| Status | Type | When | Billed |
|---|---|---|---|
| 400 | invalid_request | Malformed body, schedule.interval_ms missing or below 60000, ends_at not after start_at, max_runs below 1, request.items empty or over 1,000, or webhook_url not https. | No |
| 401 | invalid_api_key | Missing, malformed, or revoked key. | No |
| 429 | rate_limited | Over the 5,000 requests/min limit. Honor Retry-After. | No |
| 500 | internal_error | Something broke on the server side. | No |
| 503 | upstream_error | The scheduling service is unreachable. Safe to retry. | No |
See the Errors guide for every error type this version can return.