workwing-io Integrations API
Running configured integrations and receiving their callbacks
Running configured integrations and receiving their callbacks
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/workwing-io:workwing-io-integrations-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: WorkWing Integrations API
description: 'REST API for WorkWing field service management. Designed as an **integration point** for CRM, ERP, and dispatch systems. Admin configuration (webhooks, roles, job types, integrations) is managed via the web dashboard. Every resource is documented under its own section in the sidebar. ## Authentication
Send your API key as a bearer token on every request:
```
Authorization: Bearer ww_sk_live_. ```
### Scopes
An API key carries a list of `action:resource` scopes. Every operation publishes the ones it requires as `x-required-scopes`, an array on the operation object; a key must hold all of them, or the wildcard scope `*`, and one that does not is refused with `403 E_AP_1102`. The two operations that declare `security: []` need no key at all: the health check, and the integration callback, which authenticates with the signed token in its own path. ## Querying collections
List endpoints (jobs, customers, resources, and so on) accept query parameters for filtering, sorting, field selection, and pagination:
- `?status=eq.T_PENDING` - filter by exact match
- `?priority=in.(T_HIGH,T_URGENT)` - match any of several values
- `?created_at=gte.2024-01-01` - range comparisons (`gt`, `gte`, `lt`, `lte`)
- `?select=id,status,customer:customers(name)` - choose columns and embed related records. Only the resources listed here, the assignments and users they reference, and the foreign keys between them can be embedded; anything else is refused with `E_AP_6003`
- `?order=created_at.desc` - sort (append `.asc` / `.desc`)
- `?limit=25&offset=50` - paginate (maximum 1000 rows per page)
### Columns not available over the API
Some columns are never returned: `tenant_id` on every resource (a key is bound to one tenant), `internal_notes` on jobs (staff-only; it can be set with `PATCH`, and a job''s note timeline is at `GET /v1/jobs/{jobId}/notes`), and the raw PostGIS geography columns `customers.location`, `locations.location` and `resources.current_location`. Read a position from `latitude`/`longitude` on locations and `current_latitude`/`current_longitude` on resources instead. `select=*`, at the top level or inside an embed, returns every other column. Naming one of these columns in `select`, a filter or `order` is refused with `E_AP_6003`. To read, update or delete one record, address it by id: `GET`, `PATCH` or `DELETE /v1/jobs/{jobId}` (likewise customers, resources and locations). `PATCH` and `DELETE` on a collection change every row matching its filters, e.g. `PATCH /v1/jobs?status=eq.T_PENDING`; they are listed under Workflows. ### Counting and paging
Send `Prefer: count=exact` on a list request to get the total row count back in the `Content-Range` response header (`0-24/312`). `count=planned` and `count=estimated` are cheaper approximations for large tables. ## Idempotency
**Guarantee.** Within 24 hours, a write sent with a given `Idempotency-Key` is applied at most once. If you did not receive a response, retrying with the same key and the same request is always safe. **Core rule.** A key is consumed only when the write is actually applied. The key and the write commit in the same database transaction, so they succeed or fail together. Every write request (`POST`, `PATCH`, `PUT`, `DELETE`) must send an `Idempotency-Key` header. | Situation | Response | Write applied?'
version: 1.0.0
servers:
- url: https://api.workwing.io
description: Production
security:
- bearerAuth: []
tags:
- name: Integrations
description: Running configured integrations and receiving their callbacks
paths:
/v1/integrations/execute:
post:
operationId: executeIntegration
summary: Run a configured integration
tags:
- Integrations
description: 'Enqueues the integration and answers 202 with a `queueId`. Add `?wait=true` to hold the connection until the run finishes and receive its result instead: the same fields `GET /v1/integrations/executions/{queueId}` returns, plus `result` with the run''s `executionId`, `status`, `output` and `cacheHit`. For an asynchronous integration the wait ends once the request is sent, so read the outcome back by `queueId`.
Under `?wait=true` a run that fails answers 422 with `E_AP_7003 INTEGRATION_ERROR`, carrying the `queueId` and `executionId` in `error.details`; the provider''s own error text is recorded server-side and not returned. A run still going after the wait window answers 408 with `E_AP_7002`. Either way, read the run back with `GET /v1/integrations/executions/{queueId}` rather than executing again.
A `jobId` or `customerId` must belong to the key''s brand: one in another brand, or one that does not exist, answers 404.
Scope: `execute:integrations`. No `Idempotency-Key` is required on this endpoint.'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
integrationId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
inputData:
default: {}
type: object
additionalProperties: {}
jobId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
customerId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
required:
- integrationId
parameters:
- schema:
type: string
enum:
- '0'
- '1'
- 'true'
- 'false'
in: query
name: wait
required: false
security:
- bearerAuth: []
x-required-scopes:
- execute:integrations
responses:
'200':
description: Default Response
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
queueId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
status:
type: string
executionId:
type:
- string
- 'null'
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
executionStatus:
type:
- string
- 'null'
output: {}
completedAt:
type:
- string
- 'null'
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z))$
result:
type: object
properties:
executionId:
type:
- string
- 'null'
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
status:
type:
- string
- 'null'
output: {}
cacheHit:
type: boolean
required:
- executionId
- status
- output
- cacheHit
required:
- queueId
- status
- executionId
- executionStatus
- output
- completedAt
- result
meta:
type: object
properties:
request_id:
type: string
timestamp:
type: string
format: date-time
'202':
description: Queued. Read the outcome back with `GET /v1/integrations/executions/{queueId}`.
content:
application/json:
schema:
description: Queued. Read the outcome back with `GET /v1/integrations/executions/{queueId}`.
type: object
required:
- data
- meta
properties:
data:
type: object
required:
- queueId
- status
- message
properties:
queueId:
type: string
format: uuid
status:
type: string
enum:
- T_PENDING
message:
type: string
meta:
type: object
required:
- request_id
- timestamp
properties:
request_id:
type: string
timestamp:
type: string
format: date-time
/v1/integrations/callback/{token}:
post:
operationId: receiveIntegrationCallback
summary: Receive an asynchronous integration callback
tags:
- Integrations
description: 'Called by the external service, not by API clients: authorisation is the signed token in the path, so no API key is sent. A token that does not match the execution named by `execution_id`, or an `execution_id` that does not exist, answers 401 alike. The body is stored as received; it must be a JSON object of at most 50 top-level keys and 512KB serialised.'
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: {}
parameters:
- schema:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
in: query
name: execution_id
required: true
- schema:
type: string
in: path
name: token
required: true
security: []
responses:
'200':
description: Default Response
content:
application/json:
schema:
type: object
required:
- data
- meta
properties:
data:
type: object
required:
- status
- execution_id
- callback_count
properties:
status:
type: string
enum:
- accepted
execution_id:
type: string
format: uuid
callback_count:
type: integer
description: Callbacks this execution has received, including this one. At most 10 are accepted.
meta:
type: object
required:
- request_id
- timestamp
properties:
request_id:
type: string
timestamp:
type: string
format: date-time
/v1/integrations/executions/{queueId}:
get:
operationId: getIntegrationExecution
summary: Get the result of an enqueued integration execution
tags:
- Integrations
description: 'Returns the status and transformed output of an execution enqueued by `POST /v1/integrations/execute`, using the `queueId` that call returned. Poll this after a 202, or after a 408 from `?wait=true` - the run usually completes regardless, and re-executing repeats the integration''s side effects.
`status` is the queue entry''s own status (`T_PENDING`, `T_RUNNING`, `T_COMPLETED`, `T_FAILED`). `executionStatus` is the integration''s. For an asynchronous integration the queue entry is `T_COMPLETED` as soon as the request is sent, and `executionStatus` stays `T_PENDING` until the third party calls back, then becomes `T_COMPLETED`, `T_FAILED` or `T_EXPIRED` with `output` filled from that callback. A failed run answers 200 with `status: "T_FAILED"`; the provider''s error text is recorded server-side and not returned.
Scope: `read:integrations`.'
parameters:
- schema:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
in: path
name: queueId
required: true
security:
- bearerAuth: []
x-required-scopes:
- read:integrations
responses:
'200':
description: Default Response
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
queueId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
status:
type: string
executionId:
type:
- string
- 'null'
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
executionStatus:
type:
- string
- 'null'
output: {}
completedAt:
type:
- string
- 'null'
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z))$
required:
- queueId
- status
- executionId
- executionStatus
- output
- completedAt
meta:
type: object
properties:
request_id:
type: string
timestamp:
type: string
format: date-time
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: API Key
description: 'Use your WorkWing API key (format: `ww_sk_live_<tokenId>.<secret>`). The scopes an operation requires are published on it as `x-required-scopes`.'