Skip to content

Making Requests

Every Driftwood API call follows the same pattern: send a POST request with a JSON body, get back a JSON response.

All API calls must:

  1. Use the POST method
  2. Send a JSON body (even if empty — send {})
  3. Include Content-Type: application/json
  4. Include Authorization: Bearer TOKEN (for authenticated endpoints)
Terminal window
curl -X POST https://api.driftwoodapp.com/api/{operation-name} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'

Each API operation has a unique name used as the URL path:

PatternExampleDescription
{resource}-listcontacts-listList/search records
{resource}-getcontacts-getGet a single record
{resource}-createcontacts-createCreate a record
{resource}-updatecontacts-updateUpdate a record
{resource}-deletecontacts-deleteDelete a record
{
"ok": true,
"result": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Jane",
"last_name": "Smith"
}
}
{
"ok": false,
"error": {
"code": "contacts.not_found",
"message": "Contact not found"
}
}

Always check the ok field first. The HTTP status code will also reflect the error type (400, 401, 403, 404, 429, 500).

TypeFormatExample
UUIDRFC 4122 string"550e8400-e29b-41d4-a716-446655440000"
DatetimeISO 8601 / RFC 3339"2025-06-01T12:00:00Z"
DateYYYY-MM-DD string"2025-06-01"
TagsArray of strings["vip", "enterprise"]
Custom fieldsJSON object{"field_uuid": "value"}
Currency/moneyInteger (cents)150000 = $1,500.00

Update operations accept partial payloads. Only include the fields you want to change:

Terminal window
# Only update the email — all other fields remain unchanged
curl -X POST https://api.driftwoodapp.com/api/contacts-update \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "new-email@example.com"
}'

Some endpoints don’t require parameters. Always send an empty JSON object:

Terminal window
curl -X POST https://api.driftwoodapp.com/api/sources-list \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
import requests
def driftwood_api(operation, token, body=None):
response = requests.post(
f"https://api.driftwoodapp.com/api/{operation}",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json=body or {},
)
data = response.json()
if not data["ok"]:
raise Exception(f"{data['error']['code']}: {data['error']['message']}")
return data["result"]
# Usage
contacts = driftwood_api("contacts-list", token, {"limit": 10})
async function driftwoodApi(operation: string, token: string, body?: object) {
const response = await fetch(
`https://api.driftwoodapp.com/api/${operation}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body ?? {}),
}
);
const data = await response.json();
if (!data.ok) {
throw new Error(`${data.error.code}: ${data.error.message}`);
}
return data.result;
}
// Usage
const contacts = await driftwoodApi("contacts-list", token, { limit: 10 });
func driftwoodAPI(operation, token string, body any) (json.RawMessage, error) {
payload, _ := json.Marshal(body)
req, _ := http.NewRequest("POST",
"https://api.driftwoodapp.com/api/"+operation,
bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
OK bool `json:"ok"`
Result json.RawMessage `json:"result"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
json.NewDecoder(resp.Body).Decode(&result)
if !result.OK {
return nil, fmt.Errorf("%s: %s", result.Error.Code, result.Error.Message)
}
return result.Result, nil
}