List jobs
curl "https://api.webot.agency/api/v1/jobs?status=queued,running&limit=25" \ -H "Authorization: Bearer $WEBOT_API_KEY"
WeBot API docs
Use the public catalog to choose a service, create an API key, submit an account-owned job, attach files through presign, and poll status with idempotent authenticated calls.
The catalog route is public and returns service family IDs, service option IDs, intake_requirements, quality_signals, and customer-safe job statuses. Agent callers should ask for missing intake requirements before creating work.
curl https://api.webot.agency/api/v1/catalog
Use the public /api/v1/catalog/intent-check route before job creation when an agent is still gathering context. It does not create a job, consume quota, attach files, or grant access.
curl https://api.webot.agency/api/v1/catalog/intent-check \
-X POST \
-H "Content-Type: application/json" \
-d '{
"service_family": "growth_market",
"service_type": "seo",
"customer_title": "Local SEO plan",
"customer_instructions": "Ideal customer: dental offices. Offer maturity: new service. Market geography: Coconut Creek. Conversion goal: book consultation calls."
}'
{
"intent_check": {
"ready": true,
"missing_intake_requirements": [],
"quality_signals": ["Audience fit", "Search-intent match", "Offer clarity", "Actionable next step"]
}
}Use placeholder tokens in examples. Real API key plaintext is shown once at create time; never paste real keys into source files, logs, tickets, or shared screenshots.
export WEBOT_API_KEY="wbot_example_prefix_example_token"
curl https://api.webot.agency/api/v1/me \ -H "Authorization: Bearer $WEBOT_API_KEY" curl https://api.webot.agency/api/v1/entitlements \ -H "Authorization: Bearer $WEBOT_API_KEY" curl "https://api.webot.agency/api/v1/usage?period_start=2026-06-01T00:00:00.000Z" \ -H "Authorization: Bearer $WEBOT_API_KEY"
After the public intent check, use the authenticated preflight route to verify active access, remaining quota, active-job capacity, and rate-limit policy before sending a create request. This route does not create a job, write usage, consume rate counters, or attach files.
curl https://api.webot.agency/api/v1/jobs/preflight \
-X POST \
-H "Authorization: Bearer $WEBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_family": "growth_market",
"service_type": "seo",
"customer_title": "Local SEO plan",
"customer_instructions": "Ideal customer: dental offices. Offer maturity: new service. Market geography: Coconut Creek. Conversion goal: book consultation calls."
}'
{
"preflight": {
"ready_to_create": true,
"blocking_reasons": [],
"usage": { "used": 0, "quota": 500, "remaining": 500, "unit": "job" },
"concurrency": { "active_jobs": 0, "limit": 5, "remaining": 5, "allowed": true },
"rate_limit_policy": {
"route": "/api/v1/jobs",
"account": { "limit": 180, "window_seconds": 60 },
"checked_without_consuming": true
}
}
}Mutating requests should include Idempotency-Key. The server owns account ID, quota checks, status, usage writes, and job IDs. Put the answered catalog intake requirements into customer_instructions so fulfillment has the customer context it needs.
curl https://api.webot.agency/api/v1/jobs \
-X POST \
-H "Authorization: Bearer $WEBOT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-create-2026-06-28-001" \
-d '{
"service_family": "create_polish",
"service_type": "tone_rewrites_copy_polish",
"customer_title": "Polish launch email",
"customer_instructions": "Audience: small business owners. Tone: clear and friendly. Brand constraint: no hype. Delivery format: short launch email. Tighten this draft for a useful product announcement."
}'
{
"job": {
"id": "job_example",
"status": "queued",
"links": {
"self": "/api/v1/jobs/job_example",
"events": "/api/v1/jobs/job_example/events",
"approve": "/api/v1/jobs/job_example/approve",
"cancel": "/api/v1/jobs/job_example/cancel",
"revision": "/api/v1/jobs/job_example/revision"
}
},
"next_action": "Check job status or add source files if needed."
}Presign returns a short-lived signed upload contract and a customer-safe file record. Use the returnedupload.url and upload.headers exactly as provided, then call complete after the upload succeeds. The upload URL is not a durable public URL.
curl https://api.webot.agency/api/v1/files/presign \
-X POST \
-H "Authorization: Bearer $WEBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"purpose": "job_source",
"content_type": "application/pdf",
"size_bytes": 1048576,
"file_name": "source-notes.pdf",
"job_id": "job_example",
"retention_days": 7
}'
{
"file": {
"id": "file_example",
"job_id": "job_example",
"purpose": "job_source",
"file_name": "source-notes.pdf",
"content_type": "application/pdf",
"size_bytes": 1048576,
"status": "pending_upload"
},
"upload": {
"method": "PUT",
"url": "SIGNED_UPLOAD_URL_FROM_RESPONSE",
"expires_at": "2026-06-28T12:15:00.000Z",
"headers": {
"content-type": "application/pdf",
"x-webot-max-size": "1048576"
}
}
}
curl "$SIGNED_UPLOAD_URL_FROM_RESPONSE" \
-X PUT \
-H "content-type: application/pdf" \
-H "x-webot-max-size: 1048576" \
--data-binary @source-notes.pdf
curl https://api.webot.agency/api/v1/files/file_example/complete \
-X POST \
-H "Authorization: Bearer $WEBOT_API_KEY"Poll the job resource for current status and the events resource for customer-safe history. Stop polling once the job reaches completed, rejected, or canceled.
curl "https://api.webot.agency/api/v1/jobs?status=queued,running&limit=25" \ -H "Authorization: Bearer $WEBOT_API_KEY"
curl https://api.webot.agency/api/v1/jobs/job_example \ -H "Authorization: Bearer $WEBOT_API_KEY"
curl https://api.webot.agency/api/v1/jobs/job_example/events \ -H "Authorization: Bearer $WEBOT_API_KEY"
curl https://api.webot.agency/api/v1/jobs/job_example/approve \ -X POST \ -H "Authorization: Bearer $WEBOT_API_KEY" \ -H "Idempotency-Key: job-approve-2026-06-28-001"
curl https://api.webot.agency/api/v1/jobs/job_example/cancel \ -X POST \ -H "Authorization: Bearer $WEBOT_API_KEY" \ -H "Idempotency-Key: job-cancel-2026-06-28-001"
curl https://api.webot.agency/api/v1/jobs/job_example/revision \
-X POST \
-H "Authorization: Bearer $WEBOT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-revision-2026-06-28-001" \
-d '{ "customer_instructions": "Make the tone more direct and shorten the opening." }'type WebotClientOptions = {
apiKey: string;
baseUrl?: string;
};
class WebotClient {
private readonly baseUrl: string;
private readonly apiKey: string;
constructor(options: WebotClientOptions) {
this.baseUrl = options.baseUrl ?? "https://api.webot.agency";
this.apiKey = options.apiKey;
}
async createFilePresign(body: {
purpose: "job_source" | "job_reference" | "job_deliverable";
content_type: string;
size_bytes: number;
file_name: string;
job_id?: string;
}) {
const response = await fetch(this.baseUrl + "/api/v1/files/presign", {
method: "POST",
headers: {
Authorization: "Bearer " + this.apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
async completeFileUpload(fileId: string) {
const response = await fetch(this.baseUrl + "/api/v1/files/" + encodeURIComponent(fileId) + "/complete", {
method: "POST",
headers: {
Authorization: "Bearer " + this.apiKey
}
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
async getJob(id: string) {
const response = await fetch(this.baseUrl + "/api/v1/jobs/" + encodeURIComponent(id), {
headers: {
Authorization: "Bearer " + this.apiKey
}
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
async listJobs(params: { status?: string; service_family?: string; cursor?: string; limit?: number } = {}) {
const search = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) {
search.set(key, String(value));
}
}
const response = await fetch(this.baseUrl + "/api/v1/jobs?" + search.toString(), {
headers: {
Authorization: "Bearer " + this.apiKey
}
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
async preflightJob(body: {
service_family: string;
service_type: string;
customer_title: string;
customer_instructions: string;
}) {
const response = await fetch(this.baseUrl + "/api/v1/jobs/preflight", {
method: "POST",
headers: {
Authorization: "Bearer " + this.apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
async createJob(body: {
service_family: string;
service_type: string;
customer_title: string;
customer_instructions: string;
}) {
const response = await fetch(this.baseUrl + "/api/v1/jobs", {
method: "POST",
headers: {
Authorization: "Bearer " + this.apiKey,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID()
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw await response.json();
}
return response.json();
}
}