Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

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
}