Fifi CRM

Fifi API

Version 1 · OpenAPI 3.1 document

The Fifi API reads and changes a workspace's contacts, companies, deals, tasks, and notes, calls and meetings, with the same rules as the app, and reads its floor plans and seats and its service records.

Authenticate with an API key from Settings → API (owners and admins): Authorization: Bearer fifi_<prefix>_<secret>. A key reaches only its own workspace, acts as the member who created it, and can do only what its scopes allow.

Requests and responses are JSON. Money is in integer cents; dates are ISO 8601. Each key may make 120 requests a minute, and each workspace 600.

API keys

Owners and admins create keys in Settings → API. Each key has a name, an expiry (30 days, 90 days, 365 days, never) and access to each area: none, read, or read and write. A workspace can have 10 active keys.

  • contacts:read, contacts:write: contacts
  • companies:read, companies:write: companies
  • deals:read, deals:write: deals
  • tasks:read, tasks:write: tasks
  • activities:read, activities:write: notes, calls and meetings
  • webhooks:read, webhooks:write: webhooks
  • floor_plans:read: floor plans and seats (read only)
  • service_records:read: service records (read only)
  • loads:read: loads (read only)
  • invoices:read: invoices and payments (read only)

The lookups take: GET /me, any key; GET /members, a read scope of contacts:read, companies:read, deals:read, tasks:read, activities:read or webhooks:read (floor_plans:read or service_records:read alone isn’t enough); GET /tags, contacts:read; GET /pipeline, deals:read.

A key is shown once, as fifi_<prefix>_<secret>. Fifi stores only a hash of it, so a lost key can’t be shown again: revoke it and create another. Settings → API lists each key’s prefix, scopes, creator, expiry and when and from where it was last used.

A key reaches only its own workspace and acts as the member who created it, within its scopes: it can do what they could in the app, and the activity log shows changes as made by them via the key. It stops working when it’s revoked or expires, or while the workspace is suspended or being deleted. If its creator leaves the workspace, is deactivated, or is no longer an owner or admin, the key is revoked for good: create a new one.

Authentication

Send the key in the Authorization header of every request. The API is for servers: it sends no CORS headers, never accepts cookies, and refuses a request that carries both a Fifi session cookie and a key. Keep keys out of browsers, apps and source code. Requests with missing or wrong keys are limited per IP address, so an integration that shares its address with abusive traffic can be refused (429) for up to a minute.

curl "https://app.fetchfifi.com/api/v1/me" \
  -H "Authorization: Bearer $FIFI_API_KEY"

Requests and responses

Bodies are JSON (Content-Type: application/json) of up to 256 KB, sent with a Content-Length (chunked bodies are refused). Fields use snake_case. Money is in integer cents, timestamps are ISO 8601 in UTC, and calendar dates are YYYY-MM-DD. A service record’s amounts are numbers with up to 3 decimals. A record comes back as { "data": { … } }.

Writes follow the app’s own rules: the same validation, permissions, uniqueness (a contact’s email, a company’s domain), timeline entries and activity log. PATCH changes only the fields you send; null clears an optional one. A successful PATCH always sets updated_at, even when it changes no value, so a sync by updated_since sees it. DELETE moves contacts, companies and deals to Trash.

Custom fields. Contacts, companies and deals carry custom_fields: every active custom field the workspace defined, by its key, with null when it’s empty (GET /custom-fields lists them, with their types and a choice’s options). A choice holds its option’s value, a checkbox true or null, a date YYYY-MM-DD. Send custom_fields on POST and PATCH as a key → value map: only the keys you send change, and null clears one; an unknown or archived key is a 400 naming custom_fields.<key>. Lists filter by up to 5 of them: cf.<key>=value, cf.<key>.gte and cf.<key>.lte (numbers and dates), and cf.<key>.set=true or false.

Every response has an X-Request-Id header: quote it if you contact us.

Paging and filters

Lists come back as { "data": [ … ], "next_cursor": "…" }, newest first (sort=created_at) or most recently changed first (sort=updated_at). Ask for up to 100 at a time with limit, and pass next_cursor back as cursor for the next page; it’s null on the last one. To sync changes, list with sort=updated_at and updated_since. Deleted records are never listed.

Errors

Errors come back as { "error": { "code", "message", "details" } }. For invalid input, details.fields says what’s wrong with each field. A record in another workspace is simply not found.

400
invalid_request
400
invalid_json
400
invalid_cursor
400
cookie_not_allowed
401
unauthorized
403
insufficient_scope
403
forbidden
403
module_disabled
403
sample_workspace
404
not_found
405
method_not_allowed
409
conflict
409
idempotency_key_in_use
413
payload_too_large
415
unsupported_media_type
411
length_required
422
rule_violation
422
idempotency_key_reused
429
rate_limited
500
internal
503
api_disabled

Idempotency

Send an Idempotency-Key header (any 1–255 visible ASCII characters, such as a UUID) with a POST so a retry can’t create the same record twice. A repeat with the same key and body within 24 hours returns the first response, with Idempotent-Replayed: true; the same key with a different body is refused (422). Only successful responses are kept, so a request that failed can be retried with the same key.

Rate limits

Each key may make 120 requests a minute, and all of a workspace’s keys together 600. Responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds). Over a limit, the API answers 429 with Retry-After.

Webhooks

Webhooks tell your app when something changes in a workspace, whether it was changed in Fifi or through the API, so you don’t have to poll. Owners and admins add up to 10 endpoints in Settings → API → Webhooks, or through the API with the webhooks:write scope (POST /webhooks to subscribe, DELETE /webhooks/{id} to unsubscribe, the REST hooks pattern Zapier uses). A key can subscribe only to events of areas it can read: contact.* needs contacts:read, floor_plan.* needs floor_plans:read, and so on.

An endpoint is an https:// URL on the standard port (443) with a public host name. Fifi looks the name up before every delivery and never sends to private, loopback, link-local or cloud metadata addresses (IPv4 or IPv6), and doesn’t follow redirects.

Events

contact.created
A contact was added (by hand, by import, through the API).
contact.updated
A contact changed: its fields, owner, lead status, Do Not Track, tags, or it came back from Trash.
contact.deleted
A contact was moved to Trash.
company.created
A company was added.
company.updated
A company changed, or came back from Trash.
company.deleted
A company was moved to Trash.
deal.created
A deal was added.
deal.updated
Anything about a deal changed, including its stage and status (sent with the events below too).
deal.stage_changed
A deal moved to another pipeline stage.
deal.won
A deal was closed as won.
deal.lost
A deal was closed as lost.
deal.deleted
A deal was moved to Trash.
task.created
A task was added.
task.updated
A task changed, including being completed or reopened.
task.completed
A task was completed (sent with task.updated).
task.deleted
A task was deleted.
activity.created
A note, call or meeting was logged on a contact, company or deal.
floor_plan.created
A floor plan was added.
floor_plan.updated
A floor plan was saved in the editor, renamed, moved up or down the list, or its background image was added, replaced or removed; also a plan a kennel or seat was moved away from, or whose kennel or seat was deleted.
floor_plan.deleted
A floor plan was deleted (the plan as it was just before).
load.created
A load was added.
load.updated
Anything about a load changed: its fields, carrier, stops (their arrival and departure times too) or status, or it came back from Trash (sent with the events below too).
load.status_changed
A load moved to another status (`changes.status` is the one before; sent with load.updated).
load.delivered
A load was marked delivered (sent with load.updated and load.status_changed).
load.deleted
A load was moved to Trash.
invoice.sent
An invoice was issued: sent by email or marked as sent (it has its number now). Its lines, notes and billing address aren't in the event; read them with GET /invoices/{id}.
invoice.voided
An issued invoice was voided (it keeps its number; `void_reason` says why).
invoice.paid
An invoice reached paid: its payments add up to its total (sent with payment.recorded).
payment.recorded
A payment was recorded for an invoice, or a refund (`kind` is `refund`; `amount_cents` is always positive). Its notes aren't in the event; read them with GET /payments.
payment.voided
A payment (or refund) was voided because it was entered in error; it stays on the invoice, struck through, and counts for nothing.

Send test event on the webhook's page sends a ping.

The request

Fifi sends a POST with a JSON body: { "id", "type", "created_at", "data": { "object", "changes" } }. object is the record as the API returns it (see the objects below), except for text people write as notes: a company’s notes, a deal’s lost_reason_note and a note’s, call’s or meeting’s body are left out, as are long-text custom fields (a change to one still raises *.updated, without listing it); fetch the record with the API if you need them. Email content and files are never sent. A floor plan event carries the plan as GET /floor-plans lists it, without its shapes and pods (fetch GET /floor-plans/{id} for them). On *.updated events (and the deal and task events sent with them), changes has the previous value of each field that changed (for custom_fields, only the keys that changed). A record restored from Trash arrives as *.updated without changes, as does a floor plan whose change its summary doesn’t show (its place in the list, or its background replaced). Bodies are at most 64 KB; a bigger one would carry only the record’s id and "truncated": true.

{
  "id": "0199a8f3-0b1c-7d2e-8f30-4a5b6c7d8e9f",
  "type": "contact.updated",
  "created_at": "2026-10-04T14:03:00.000Z",
  "data": {
    "object": {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "email": "ada@acme.com",
      "phone": null,
      "address": null,
      "title": "CTO",
      "lead_status": "qualified",
      "source": "referral",
      "do_not_track": false,
      "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
      "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "tags": [],
      "custom_fields": {
        "preferred_contact": "email",
        "renewal_date": "2026-11-30"
      },
      "created_at": "2026-10-01T14:03:00.000Z",
      "updated_at": "2026-10-04T14:03:00.000Z"
    },
    "changes": {
      "lead_status": "contacted",
      "title": null,
      "custom_fields": {
        "renewal_date": null
      }
    }
  }
}

The headers:

  • Fifi-Event-Id: the event's ID, the same on every retry and redelivery.
  • Fifi-Event-Type: the event, such as deal.won.
  • Fifi-Signature: t=<unix seconds>,v1=<signature>, where the signature is the hex HMAC-SHA256 of <t>.<raw body> with the endpoint's signing secret.

Verifying signatures

Each endpoint has its own signing secret (whsec_…), shown once when you add the endpoint or roll the secret. Check every request before acting on it: compute the signature over the raw body, compare it in constant time, and reject a timestamp more than 5 minutes away from now (a replay). After Roll secret, the old secret keeps working for 24 hours and the header carries two v1 signatures, one for each: accept either, then switch to the new secret.

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as it arrived (a Buffer or string), before any JSON parsing.
// header: the Fifi-Signature header. secret: the endpoint's signing secret (whsec_…).
export function verifyFifiSignature(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = String(header ?? "").split(",").map((p) => p.trim().split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  // Reject anything older (or newer) than 5 minutes: a replayed request.
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
  // During a secret roll there are two v1 signatures: either may match.
  return parts.some(([k, v]) => k === "v1" && /^[0-9a-f]{64}$/.test(v ?? "") && timingSafeEqual(Buffer.from(v, "hex"), expected));
}
import express from "express";

const app = express();
// The raw body: a parsed-and-reserialized JSON body won't match the signature.
app.post("/fifi-webhooks", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyFifiSignature(req.body, req.get("Fifi-Signature"), process.env.FIFI_WEBHOOK_SECRET)) {
    return res.status(400).send("bad signature");
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // Fifi may send an event more than once: skip ones you've already handled.
  if (alreadyHandled(event.id)) return res.sendStatus(200);
  queueForProcessing(event); // answer within 10 seconds; do slow work afterwards
  res.sendStatus(200);
});

Retries, order and duplicates

  • Answer with any 2xx status within 10 seconds. Anything else, a redirect, a timeout or no connection is a failure. Fifi reads at most 4 KB of your response and ignores it.
  • A failure is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours: 7 attempts over about 21 hours.
  • Order isn’t guaranteed, and an event may arrive more than once. Deduplicate by Fifi-Event-Id, and use the object’s updated_at to ignore an older state that arrives after a newer one.
  • Fifi sends at most 2 requests at a time and 60 a minute to each endpoint, so a big import (one contact.created per row) arrives over a while.
  • An endpoint stops for good when the member who added it leaves the workspace or is no longer an owner or admin, or when the API key that added it is revoked or expires. A retried POST /webhooks (the same Idempotency-Key) answers secret: null: Fifi doesn’t keep the secret where it could be shown again.
  • An endpoint that has failed every delivery for 24 hours is turned off, and the person who added it is emailed. Turning it back on doesn’t resend what was missed; the webhook’s page lists the last 30 days of deliveries, with each one’s attempts, response status and time, and Redeliver sends any of them again.

Workspace

GET/me

This key and its workspace

Any valid key: its workspace, name, scopes and rate limits. A quick way to check a key works.

Needs: any key

Example

curl "https://app.fetchfifi.com/api/v1/me" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "workspace": {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c00",
      "name": "CorporateMutt"
    },
    "key": {
      "name": "Zapier",
      "prefix": "k3x9q2ab",
      "scopes": [
        "contacts:read",
        "contacts:write"
      ],
      "expires_at": null
    },
    "rate_limit": {
      "per_key_per_minute": 120,
      "per_workspace_per_minute": 600
    }
  }
}

GET/members

List members

Active members (up to 500), oldest first: their IDs are what owner_id and assignee_id take. Needs a read scope of contacts:read, companies:read, deals:read, tasks:read, activities:read or webhooks:read (floor_plans:read alone isn't enough).

Needs: a read scope of contacts:read, companies:read, deals:read, tasks:read, activities:read or webhooks:read

Example

curl "https://app.fetchfifi.com/api/v1/members" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "name": "Olive Owner",
      "role": "owner"
    }
  ],
  "next_cursor": null
}

GET/tags

List tags

The workspace's contact tags (up to 500), A→Z. Filter contacts by one with GET /contacts?tag_id=.

Needs: contacts:read

Example

curl "https://app.fetchfifi.com/api/v1/tags" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c13",
      "name": "VIP",
      "color": "primary"
    }
  ],
  "next_cursor": null
}

GET/pipeline

Pipeline stages and statuses

The values a deal's stage, status and lost reason take.

Needs: deals:read

Example

curl "https://app.fetchfifi.com/api/v1/pipeline" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "stages": [
      {
        "id": "lead",
        "label": "Lead"
      },
      {
        "id": "qualified",
        "label": "Qualified"
      },
      {
        "id": "proposal",
        "label": "Proposal"
      },
      {
        "id": "negotiation",
        "label": "Negotiation"
      }
    ],
    "statuses": [
      "open",
      "won",
      "lost"
    ],
    "lost_reasons": [
      {
        "id": "price",
        "label": "Price"
      },
      {
        "id": "timing",
        "label": "Timing"
      },
      {
        "id": "competitor",
        "label": "Competitor"
      },
      {
        "id": "no_decision",
        "label": "No decision"
      },
      {
        "id": "other",
        "label": "Other"
      }
    ]
  }
}

Custom fields

GET/custom-fields

List custom fields

The workspace's active custom fields, by record type and in order: their keys are what a contact's, company's or deal's custom_fields hold (deal fields only while Deals is on). Needs a read scope of contacts:read, companies:read, deals:read, tasks:read, activities:read or webhooks:read.

Needs: a read scope of contacts:read, companies:read, deals:read, tasks:read, activities:read or webhooks:read

Query parameters

object"contact" | "company" | "deal"
Only this record type's fields.

Example

curl "https://app.fetchfifi.com/api/v1/custom-fields?object=company" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "object": "company",
      "key": "mc_number",
      "label": "MC number",
      "type": "text",
      "help_text": "The carrier's FMCSA motor carrier number.",
      "position": 0,
      "options": [],
      "remind_days_before": null
    },
    {
      "object": "company",
      "key": "insurance_expiry",
      "label": "Insurance expiry",
      "type": "date",
      "help_text": null,
      "position": 1,
      "options": [],
      "remind_days_before": 30
    }
  ],
  "next_cursor": null
}

Contacts

The contact object

idstring (uuid)required
The record's ID.
first_namestringrequired
last_namestringrequired
emailstring,nullrequired
Stored lowercase. Unique among the workspace's contacts.
phonestring,nullrequired
addressstring,nullrequired
A home or service address (free text).
titlestring,nullrequired
Job title.
lead_status"new" | "contacted" | "qualified" | "unqualified"required
source"inbound" | "referral" | "outbound" | "event" | "import" | "other" or nullrequired
do_not_trackbooleanrequired
Email to this contact is sent without open tracking.
company_idstring (uuid) or nullrequired
Their company; null when none, or when it's deleted.
owner_idstring (uuid)required
Who owns the contact: a member's ID (see GET /members).
tagsarray of objectrequired
Up to 50 tags, oldest first.
custom_fieldsobjectrequired
Every active custom field by key (GET /custom-fields); null when empty. A choice gives its option's value, a checkbox true, a date YYYY-MM-DD.
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).

GET/contacts

List contacts

Live contacts (never deleted ones), newest first, with the Contacts list's filters. Custom fields filter with up to 5 parameters: cf.<key> (equals; a choice by its option's value, a checkbox true or false), cf.<key>.gte and cf.<key>.lte (number and date fields) and cf.<key>.set=true|false.

Needs: contacts:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
owner_idstring (uuid)
Only contacts this member owns.
lead_status"new" | "contacted" | "qualified" | "unqualified"
tag_idstring (uuid)
Only contacts with this tag.
company_idstring (uuid)
Only this company's contacts.
emailstring
Only the contact with this email (exact, case-insensitive).

Example

curl "https://app.fetchfifi.com/api/v1/contacts?lead_status=qualified&limit=2" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "email": "ada@acme.com",
      "phone": "+1 555 0100",
      "address": "12 Analytical Way\nLondon",
      "title": "CTO",
      "lead_status": "qualified",
      "source": "referral",
      "do_not_track": false,
      "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
      "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "tags": [
        {
          "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c13",
          "name": "VIP"
        }
      ],
      "custom_fields": {
        "gate_code": "4410",
        "pets_on_property": true,
        "property_type": "house"
      },
      "created_at": "2026-10-01T14:03:00.000Z",
      "updated_at": "2026-10-02T09:30:00.000Z"
    }
  ],
  "next_cursor": null
}

POST/contacts

Create a contact

Created as in the app: the same checks, a timeline entry, and an activity-log entry that names the key.

Needs: contacts:write · Answers 201 Created

Body (JSON)

first_namestringrequired
Required, 1–100 characters.
last_namestringrequired
Required, 1–100 characters.
emailstring or null
Unique among live contacts (409 if taken).
phonestring or null
addressstring or null
titlestring or null
Job title.
lead_status"new" | "contacted" | "qualified" | "unqualified"
Default new.
source"inbound" | "referral" | "outbound" | "event" | "import" | "other" or null
company_idstring (uuid) or null
A live company in this workspace.
owner_idstring (uuid)
An active member (default: the key's creator). Reassigning follows the app's rules.
custom_fieldsobject
Custom field values by key (GET /custom-fields). Only the keys sent change; null clears. Unknown or archived keys: 400.

Example

curl -X POST "https://app.fetchfifi.com/api/v1/contacts" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"first_name":"Ada","last_name":"Lovelace","email":"ada@acme.com","title":"CTO","lead_status":"qualified","company_id":"0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11"}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@acme.com",
    "phone": "+1 555 0100",
    "address": "12 Analytical Way\nLondon",
    "title": "CTO",
    "lead_status": "qualified",
    "source": "referral",
    "do_not_track": false,
    "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "tags": [
      {
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c13",
        "name": "VIP"
      }
    ],
    "custom_fields": {
      "gate_code": "4410",
      "pets_on_property": true,
      "property_type": "house"
    },
    "created_at": "2026-10-01T14:03:00.000Z",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}

GET/contacts/{id}

Get a contact

Needs: contacts:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/contacts/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@acme.com",
    "phone": "+1 555 0100",
    "address": "12 Analytical Way\nLondon",
    "title": "CTO",
    "lead_status": "qualified",
    "source": "referral",
    "do_not_track": false,
    "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "tags": [
      {
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c13",
        "name": "VIP"
      }
    ],
    "custom_fields": {
      "gate_code": "4410",
      "pets_on_property": true,
      "property_type": "house"
    },
    "created_at": "2026-10-01T14:03:00.000Z",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}

PATCH/contacts/{id}

Update a contact

Send only the fields to change; null clears an optional field.

Needs: contacts:write

Path

idstring (uuid)required
The record's ID (in the path).

Body (JSON)

first_namestring
Required, 1–100 characters.
last_namestring
Required, 1–100 characters.
emailstring or null
Unique among live contacts (409 if taken).
phonestring or null
addressstring or null
titlestring or null
Job title.
lead_status"new" | "contacted" | "qualified" | "unqualified"
Default new.
source"inbound" | "referral" | "outbound" | "event" | "import" | "other" or null
company_idstring (uuid) or null
A live company in this workspace.
owner_idstring (uuid)
An active member (default: the key's creator). Reassigning follows the app's rules.
custom_fieldsobject
Custom field values by key (GET /custom-fields). Only the keys sent change; null clears. Unknown or archived keys: 400.
do_not_trackboolean
expected_updated_atstring (ISO 8601 date-time)
Optional precondition: the updated_at you last read. If the record has changed since, nothing is changed and the answer is 409 conflict (reason stale).

Example

curl -X PATCH "https://app.fetchfifi.com/api/v1/contacts/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"lead_status":"contacted","phone":null}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@acme.com",
    "phone": null,
    "address": "12 Analytical Way\nLondon",
    "title": "CTO",
    "lead_status": "contacted",
    "source": "referral",
    "do_not_track": false,
    "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "tags": [
      {
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c13",
        "name": "VIP"
      }
    ],
    "custom_fields": {
      "gate_code": "4410",
      "pets_on_property": true,
      "property_type": "house"
    },
    "created_at": "2026-10-01T14:03:00.000Z",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}

DELETE/contacts/{id}

Delete a contact

Moves it to Trash, where an owner or admin can restore it for 30 days. As in the app, members delete contacts they own and owners and admins any; a key acts as its creator.

Needs: contacts:write

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl -X DELETE "https://app.fetchfifi.com/api/v1/contacts/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "deleted": true
  }
}

Companies

The company object

idstring (uuid)required
The record's ID.
namestringrequired
domainstring,nullrequired
Email domain, e.g. acme.com. Unique among the workspace's companies.
website_urlstring,nullrequired
descriptionstring,nullrequired
industrystring,nullrequired
phonestring,nullrequired
addressstring,nullrequired
notesstring,nullrequired
owner_idstring (uuid)required
Who owns the company: a member's ID (see GET /members).
custom_fieldsobjectrequired
Every active custom field by key (GET /custom-fields); null when empty. A choice gives its option's value, a checkbox true, a date YYYY-MM-DD.
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).

GET/companies

List companies

Live companies, newest first. Custom fields filter with up to 5 parameters: cf.<key> (equals; a choice by its option's value, a checkbox true or false), cf.<key>.gte and cf.<key>.lte (number and date fields) and cf.<key>.set=true|false.

Needs: companies:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
owner_idstring (uuid)
Only companies this member owns.
domainstring
Only the company with this domain (exact, lowercase).

Example

curl "https://app.fetchfifi.com/api/v1/companies?domain=acme.com" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
      "name": "Acme",
      "domain": "acme.com",
      "website_url": "https://acme.com/",
      "description": "Makers of everything.",
      "industry": "Manufacturing",
      "phone": null,
      "address": null,
      "notes": null,
      "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "custom_fields": {
        "mc_number": "MC-123456",
        "insurance_expiry": "2026-11-30",
        "trucks": 12
      },
      "created_at": "2026-09-28T10:00:00.000Z",
      "updated_at": "2026-10-02T09:30:00.000Z"
    }
  ],
  "next_cursor": null
}

POST/companies

Create a company

Created as in the app: the same checks, a timeline entry, and an activity-log entry that names the key.

Needs: companies:write · Answers 201 Created

Body (JSON)

namestringrequired
Required, 1–200 characters.
domainstring or null
An email domain like acme.com (normalized; filled from website_url when left empty). Unique among live companies (409 if taken).
website_urlstring or null
http(s) only; acme.com means https://acme.com.
descriptionstring or null
industrystring or null
phonestring or null
addressstring or null
notesstring or null
owner_idstring (uuid)
An active member (default: the key's creator). Reassigning follows the app's rules.
custom_fieldsobject
Custom field values by key (GET /custom-fields). Only the keys sent change; null clears. Unknown or archived keys: 400.

Example

curl -X POST "https://app.fetchfifi.com/api/v1/companies" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Acme","website_url":"acme.com","industry":"Manufacturing"}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "name": "Acme",
    "domain": "acme.com",
    "website_url": "https://acme.com/",
    "description": "Makers of everything.",
    "industry": "Manufacturing",
    "phone": null,
    "address": null,
    "notes": null,
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "custom_fields": {
      "mc_number": "MC-123456",
      "insurance_expiry": "2026-11-30",
      "trucks": 12
    },
    "created_at": "2026-09-28T10:00:00.000Z",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}

GET/companies/{id}

Get a company

Needs: companies:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/companies/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "name": "Acme",
    "domain": "acme.com",
    "website_url": "https://acme.com/",
    "description": "Makers of everything.",
    "industry": "Manufacturing",
    "phone": null,
    "address": null,
    "notes": null,
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "custom_fields": {
      "mc_number": "MC-123456",
      "insurance_expiry": "2026-11-30",
      "trucks": 12
    },
    "created_at": "2026-09-28T10:00:00.000Z",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}

PATCH/companies/{id}

Update a company

Send only the fields to change; null clears an optional field.

Needs: companies:write

Path

idstring (uuid)required
The record's ID (in the path).

Body (JSON)

namestring
Required, 1–200 characters.
domainstring or null
An email domain like acme.com (normalized; filled from website_url when left empty). Unique among live companies (409 if taken).
website_urlstring or null
http(s) only; acme.com means https://acme.com.
descriptionstring or null
industrystring or null
phonestring or null
addressstring or null
notesstring or null
owner_idstring (uuid)
An active member (default: the key's creator). Reassigning follows the app's rules.
custom_fieldsobject
Custom field values by key (GET /custom-fields). Only the keys sent change; null clears. Unknown or archived keys: 400.
expected_updated_atstring (ISO 8601 date-time)
Optional precondition: the updated_at you last read. If the record has changed since, nothing is changed and the answer is 409 conflict (reason stale).

Example

curl -X PATCH "https://app.fetchfifi.com/api/v1/companies/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"industry":"Robotics"}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "name": "Acme",
    "domain": "acme.com",
    "website_url": "https://acme.com/",
    "description": "Makers of everything.",
    "industry": "Robotics",
    "phone": null,
    "address": null,
    "notes": null,
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "custom_fields": {
      "mc_number": "MC-123456",
      "insurance_expiry": "2026-11-30",
      "trucks": 12
    },
    "created_at": "2026-09-28T10:00:00.000Z",
    "updated_at": "2026-10-02T09:30:00.000Z"
  }
}

DELETE/companies/{id}

Delete a company

Moves it to Trash, where an owner or admin can restore it for 30 days. Its contacts stay, without a company until it's restored. As in the app, members delete companies they own and owners and admins any; a key acts as its creator.

Needs: companies:write

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl -X DELETE "https://app.fetchfifi.com/api/v1/companies/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "deleted": true
  }
}

Deals

The deal object

idstring (uuid)required
The record's ID.
namestringrequired
value_centsintegerrequired
Value in US cents (integer).
stage"lead" | "qualified" | "proposal" | "negotiation"required
Pipeline stage (see GET /pipeline). A closed deal keeps its last stage.
status"open" | "won" | "lost"required
lost_reason"price" | "timing" | "competitor" | "no_decision" | "other" or nullrequired
lost_reason_notestring,nullrequired
expected_close_datestring (YYYY-MM-DD) or nullrequired
A calendar date, YYYY-MM-DD.
closed_atstring (ISO 8601 date-time) or nullrequired
company_idstring (uuid)required
contact_idsarray of string (uuid)required
Its live contacts (up to 20).
owner_idstring (uuid)required
Who owns the deal: a member's ID (see GET /members).
custom_fieldsobjectrequired
Every active custom field by key (GET /custom-fields); null when empty. A choice gives its option's value, a checkbox true, a date YYYY-MM-DD.
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).

GET/deals

List deals

Live deals, open and closed, newest first. Custom fields filter with up to 5 parameters: cf.<key> (equals; a choice by its option's value, a checkbox true or false), cf.<key>.gte and cf.<key>.lte (number and date fields) and cf.<key>.set=true|false.

Needs: deals:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
owner_idstring (uuid)
Only deals this member owns.
stage"lead" | "qualified" | "proposal" | "negotiation"
status"open" | "won" | "lost"
company_idstring (uuid)
Only this company's deals.
contact_idstring (uuid)
Only deals with this contact.

Example

curl "https://app.fetchfifi.com/api/v1/deals?status=open&stage=proposal" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
      "name": "Acme renewal",
      "value_cents": 1250000,
      "stage": "proposal",
      "status": "open",
      "lost_reason": null,
      "lost_reason_note": null,
      "expected_close_date": "2026-11-30",
      "closed_at": null,
      "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
      "contact_ids": [
        "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10"
      ],
      "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "custom_fields": {
        "loads_per_week": 12
      },
      "created_at": "2026-10-01T15:00:00.000Z",
      "updated_at": "2026-10-03T11:20:00.000Z"
    }
  ],
  "next_cursor": null
}

POST/deals

Create a deal

Created open, as in the app: the same checks, a timeline entry, and an activity-log entry that names the key.

Needs: deals:write · Answers 201 Created

Body (JSON)

namestringrequired
Required, 1–200 characters.
value_centsintegerrequired
Value in US cents: an integer up to 2,000,000,000 ($20,000,000).
stage"lead" | "qualified" | "proposal" | "negotiation"
Default lead.
expected_close_datestring (YYYY-MM-DD) or null
YYYY-MM-DD, from 2000 to 2100.
company_idstring (uuid)required
A live company in this workspace.
contact_idsarray of string (uuid)required
1–20 live contacts in this workspace.
owner_idstring (uuid)
An active member (default: the key's creator). Reassigning follows the app's rules.
custom_fieldsobject
Custom field values by key (GET /custom-fields). Only the keys sent change; null clears. Unknown or archived keys: 400.

Example

curl -X POST "https://app.fetchfifi.com/api/v1/deals" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name":"Acme renewal","value_cents":1250000,"stage":"proposal","expected_close_date":"2026-11-30","company_id":"0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11","contact_ids":["0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10"]}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "name": "Acme renewal",
    "value_cents": 1250000,
    "stage": "proposal",
    "status": "open",
    "lost_reason": null,
    "lost_reason_note": null,
    "expected_close_date": "2026-11-30",
    "closed_at": null,
    "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "contact_ids": [
      "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10"
    ],
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "custom_fields": {
      "loads_per_week": 12
    },
    "created_at": "2026-10-01T15:00:00.000Z",
    "updated_at": "2026-10-03T11:20:00.000Z"
  }
}

GET/deals/{id}

Get a deal

Needs: deals:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/deals/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "name": "Acme renewal",
    "value_cents": 1250000,
    "stage": "proposal",
    "status": "open",
    "lost_reason": null,
    "lost_reason_note": null,
    "expected_close_date": "2026-11-30",
    "closed_at": null,
    "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "contact_ids": [
      "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10"
    ],
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "custom_fields": {
      "loads_per_week": 12
    },
    "created_at": "2026-10-01T15:00:00.000Z",
    "updated_at": "2026-10-03T11:20:00.000Z"
  }
}

PATCH/deals/{id}

Update, close or reopen a deal

Send only the fields to change. status closes (won, lost) or reopens a deal, with the same timeline entries as the app; a closed deal's stage can't change until it's reopened (422).

Needs: deals:write

Path

idstring (uuid)required
The record's ID (in the path).

Body (JSON)

namestring
Required, 1–200 characters.
value_centsinteger
Value in US cents: an integer up to 2,000,000,000 ($20,000,000).
stage"lead" | "qualified" | "proposal" | "negotiation"
Default lead.
expected_close_datestring (YYYY-MM-DD) or null
YYYY-MM-DD, from 2000 to 2100.
company_idstring (uuid)
A live company in this workspace.
contact_idsarray of string (uuid)
1–20 live contacts in this workspace.
owner_idstring (uuid)
An active member (default: the key's creator). Reassigning follows the app's rules.
custom_fieldsobject
Custom field values by key (GET /custom-fields). Only the keys sent change; null clears. Unknown or archived keys: 400.
status"open" | "won" | "lost"
won or lost closes an open deal; open reopens a closed one (clearing its lost reason).
lost_reason"price" | "timing" | "competitor" | "no_decision" | "other"
Required with status: lost.
lost_reason_notestring or null
Required with lost_reason: other.
expected_updated_atstring (ISO 8601 date-time)
Optional precondition: the updated_at you last read. If the record has changed since, nothing is changed and the answer is 409 conflict (reason stale).

Example

curl -X PATCH "https://app.fetchfifi.com/api/v1/deals/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"lost","lost_reason":"timing"}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "name": "Acme renewal",
    "value_cents": 1250000,
    "stage": "proposal",
    "status": "lost",
    "lost_reason": "timing",
    "lost_reason_note": null,
    "expected_close_date": "2026-11-30",
    "closed_at": "2026-10-04T16:00:00.000Z",
    "company_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c11",
    "contact_ids": [
      "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10"
    ],
    "owner_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "custom_fields": {
      "loads_per_week": 12
    },
    "created_at": "2026-10-01T15:00:00.000Z",
    "updated_at": "2026-10-03T11:20:00.000Z"
  }
}

DELETE/deals/{id}

Delete a deal

Moves it to Trash, where an owner or admin can restore it for 30 days. As in the app, members delete deals they own and owners and admins any; a key acts as its creator.

Needs: deals:write

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl -X DELETE "https://app.fetchfifi.com/api/v1/deals/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "deleted": true
  }
}

Tasks

The task object

idstring (uuid)required
The record's ID.
titlestringrequired
due_datestring (YYYY-MM-DD) or nullrequired
A calendar date, YYYY-MM-DD.
priority"low" | "normal" | "high"required
completedbooleanrequired
completed_atstring (ISO 8601 date-time) or nullrequired
assignee_idstring (uuid)required
Who it's for: a member's ID (see GET /members).
record_type"contact" | "company" | "deal" or nullrequired
The contact, company or deal it's about, if any.
record_idstring (uuid) or nullrequired
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).

GET/tasks

List tasks

Live tasks, open and done, newest first.

Needs: tasks:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
assignee_idstring (uuid)
Only tasks for this member.
completed"true" | "false"
false: open tasks only; true: done ones only.
due_fromstring (YYYY-MM-DD)
Due on or after this date (YYYY-MM-DD).
due_tostring (YYYY-MM-DD)
Due on or before this date (YYYY-MM-DD).
record_type"contact" | "company" | "deal"
record_idstring (uuid)
With record_type: only tasks about this record.

Example

curl "https://app.fetchfifi.com/api/v1/tasks?completed=false&due_to=2026-10-31" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
      "title": "Send the proposal",
      "due_date": "2026-10-09",
      "priority": "high",
      "completed": false,
      "completed_at": null,
      "assignee_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "record_type": "deal",
      "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
      "created_at": "2026-10-02T08:00:00.000Z",
      "updated_at": "2026-10-02T08:00:00.000Z"
    }
  ],
  "next_cursor": null
}

POST/tasks

Create a task

Created as in the app, with an activity-log entry that names the key.

Needs: tasks:write · Answers 201 Created

Body (JSON)

titlestringrequired
Required, 1–300 characters.
due_datestring (YYYY-MM-DD) or null
YYYY-MM-DD, from 2000 to 2100.
priority"low" | "normal" | "high"
Default normal.
assignee_idstring (uuid)
An active member (default: the key's creator).
record_type"contact" | "company" | "deal"
With record_id: the live contact, company or deal it's about.
record_idstring (uuid)

Example

curl -X POST "https://app.fetchfifi.com/api/v1/tasks" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"title":"Send the proposal","due_date":"2026-10-09","priority":"high","record_type":"deal","record_id":"0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20"}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "title": "Send the proposal",
    "due_date": "2026-10-09",
    "priority": "high",
    "completed": false,
    "completed_at": null,
    "assignee_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "record_type": "deal",
    "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "created_at": "2026-10-02T08:00:00.000Z",
    "updated_at": "2026-10-02T08:00:00.000Z"
  }
}

GET/tasks/{id}

Get a task

Needs: tasks:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/tasks/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "title": "Send the proposal",
    "due_date": "2026-10-09",
    "priority": "high",
    "completed": false,
    "completed_at": null,
    "assignee_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "record_type": "deal",
    "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "created_at": "2026-10-02T08:00:00.000Z",
    "updated_at": "2026-10-02T08:00:00.000Z"
  }
}

PATCH/tasks/{id}

Update or complete a task

Send only the fields to change. Moving the due date moves a pending reminder with it, as in the app.

Needs: tasks:write

Path

idstring (uuid)required
The record's ID (in the path).

Body (JSON)

titlestring
Required, 1–300 characters.
due_datestring (YYYY-MM-DD) or null
YYYY-MM-DD, from 2000 to 2100.
priority"low" | "normal" | "high"
Default normal.
assignee_idstring (uuid)
An active member (default: the key's creator).
completedboolean
true completes the task (logged on its record's timeline); false reopens it.

Example

curl -X PATCH "https://app.fetchfifi.com/api/v1/tasks/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "title": "Send the proposal",
    "due_date": "2026-10-09",
    "priority": "high",
    "completed": true,
    "completed_at": "2026-10-04T16:00:00.000Z",
    "assignee_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "record_type": "deal",
    "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c20",
    "created_at": "2026-10-02T08:00:00.000Z",
    "updated_at": "2026-10-02T08:00:00.000Z"
  }
}

DELETE/tasks/{id}

Delete a task

Deletes it, as in the app: its assignee, its creator or an owner or admin may (a key acts as its creator). Deleted tasks can't be restored.

Needs: tasks:write

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl -X DELETE "https://app.fetchfifi.com/api/v1/tasks/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "deleted": true
  }
}

Activities

The activity object

idstring (uuid)required
The record's ID.
type"note" | "call" | "meeting"required
A note, a call or a meeting.
record_type"contact" | "company" | "deal"required
The contact, company or deal whose timeline it's on.
record_idstring (uuid)required
titlestring,nullrequired
Its optional title (the app's “subject”).
bodystringrequired
occurred_atstring (ISO 8601 date-time)required
When it happened; a meeting's start (ISO 8601, UTC).
duration_minutesinteger or nullrequired
A meeting's length in minutes; null otherwise.
author_idstring (uuid) or nullrequired
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).

GET/activities

List notes, calls and meetings

Live entries, newest first. Entries on a deleted record stay listed until it's purged, but can't be changed.

Needs: activities:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
record_type"contact" | "company" | "deal"
record_idstring (uuid)
With record_type: only this record's entries.
type"note" | "call" | "meeting"
author_idstring (uuid)
Only entries this member logged.

Example

curl "https://app.fetchfifi.com/api/v1/activities?record_type=contact&record_id=0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
      "type": "call",
      "record_type": "contact",
      "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
      "title": "Intro call",
      "body": "Wants a quote for 12 seats by Friday.",
      "occurred_at": "2026-10-03T15:30:00.000Z",
      "duration_minutes": null,
      "author_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
      "created_at": "2026-10-03T15:45:00.000Z",
      "updated_at": "2026-10-03T15:45:00.000Z"
    }
  ],
  "next_cursor": null
}

POST/activities

Log a note, call or meeting

Logged on a live contact, company or deal, as in the app, by the key's creator.

Needs: activities:write · Answers 201 Created

Body (JSON)

type"note" | "call" | "meeting"required
record_type"contact" | "company" | "deal"required
record_idstring (uuid)required
A live contact, company or deal in this workspace.
titlestring or null
Up to 200 characters; null clears it.
bodystringrequired
Required, 1–10,000 characters.
occurred_atstring (ISO 8601 date-time)
ISO 8601 with a time zone. Default: now. Only meetings can be in the future (up to two years).
duration_minutesinteger or null
Meetings only: 5 minutes to 24 hours.

Example

curl -X POST "https://app.fetchfifi.com/api/v1/activities" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"type":"call","record_type":"contact","record_id":"0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10","title":"Intro call","body":"Wants a quote for 12 seats by Friday.","occurred_at":"2026-10-03T11:30:00-04:00"}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
    "type": "call",
    "record_type": "contact",
    "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "title": "Intro call",
    "body": "Wants a quote for 12 seats by Friday.",
    "occurred_at": "2026-10-03T15:30:00.000Z",
    "duration_minutes": null,
    "author_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "created_at": "2026-10-03T15:45:00.000Z",
    "updated_at": "2026-10-03T15:45:00.000Z"
  }
}

GET/activities/{id}

Get a note, call or meeting

Needs: activities:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/activities/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
    "type": "call",
    "record_type": "contact",
    "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "title": "Intro call",
    "body": "Wants a quote for 12 seats by Friday.",
    "occurred_at": "2026-10-03T15:30:00.000Z",
    "duration_minutes": null,
    "author_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "created_at": "2026-10-03T15:45:00.000Z",
    "updated_at": "2026-10-03T15:45:00.000Z"
  }
}

PATCH/activities/{id}

Edit a note, call or meeting

As in the app, only its author edits an entry (a key acts as its creator), and only a meeting's time and length can change.

Needs: activities:write

Path

idstring (uuid)required
The record's ID (in the path).

Body (JSON)

titlestring or null
Up to 200 characters; null clears it.
bodystring
Required, 1–10,000 characters.
occurred_atstring (ISO 8601 date-time)
ISO 8601 with a time zone. Default: now. Only meetings can be in the future (up to two years).
duration_minutesinteger or null
Meetings only: 5 minutes to 24 hours.

Example

curl -X PATCH "https://app.fetchfifi.com/api/v1/activities/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"body":"Wants a quote for 15 seats by Friday."}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
    "type": "call",
    "record_type": "contact",
    "record_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c10",
    "title": "Intro call",
    "body": "Wants a quote for 15 seats by Friday.",
    "occurred_at": "2026-10-03T15:30:00.000Z",
    "duration_minutes": null,
    "author_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c12",
    "created_at": "2026-10-03T15:45:00.000Z",
    "updated_at": "2026-10-03T15:45:00.000Z"
  }
}

DELETE/activities/{id}

Delete a note, call or meeting

As in the app, its author or an owner or admin deletes an entry (a key acts as its creator). Deleted entries can't be restored.

Needs: activities:write

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl -X DELETE "https://app.fetchfifi.com/api/v1/activities/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
    "deleted": true
  }
}

Webhook endpoints

The webhook endpoint object

idstring (uuid)required
The record's ID.
urlstringrequired
Where events are POSTed: HTTPS, port 443, a public host name.
descriptionstringrequired
Up to 200 characters; may be empty.
eventsarray of "contact.created" | "contact.updated" | "contact.deleted" | "company.created" | "company.updated" | "company.deleted" | "deal.created" | "deal.updated" | "deal.stage_changed" | "deal.won" | "deal.lost" | "deal.deleted" | "task.created" | "task.updated" | "task.completed" | "task.deleted" | "activity.created" | "floor_plan.created" | "floor_plan.updated" | "floor_plan.deleted" | "load.created" | "load.updated" | "load.status_changed" | "load.delivered" | "load.deleted" | "invoice.sent" | "invoice.voided" | "invoice.paid" | "payment.recorded" | "payment.voided"required
The events it receives.
status"active" | "disabled"required
disabled: no deliveries are sent (turned off by an owner or admin, or by Fifi after 24 hours of failed deliveries).
disabled_reason"failing" | "manual" | "creator_removed" | "key_revoked" or nullrequired
Why it's disabled: failing (every delivery failed for 24 hours), manual, creator_removed (the member who added it left or is no longer an owner or admin) or key_revoked (the API key that added it was revoked or expired). The last two can't be turned back on.
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).

GET/webhooks

List webhook endpoints

The workspace's endpoints (at most 10), oldest first. Secrets are never shown again.

Needs: webhooks:read

Example

curl "https://app.fetchfifi.com/api/v1/webhooks" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
      "url": "https://hooks.example.com/fifi",
      "description": "Zapier: new contacts",
      "events": [
        "contact.created",
        "contact.updated"
      ],
      "status": "active",
      "disabled_reason": null,
      "created_at": "2026-10-04T12:00:00.000Z",
      "updated_at": "2026-10-04T12:00:00.000Z"
    }
  ],
  "next_cursor": null
}

POST/webhooks

Subscribe a webhook endpoint

Adds an endpoint (REST hooks: subscribe on activation, DELETE to unsubscribe). The response carries its signing secret, once. At most 10 per workspace (422 limit past that). Audited as made by the key's creator via the key.

Needs: webhooks:write · Answers 201 Created

Body (JSON)

urlstring (uri)required
An https:// URL on port 443 with a public host name (not an IP address).
descriptionstring
Up to 200 characters.
eventsarray of "contact.created" | "contact.updated" | "contact.deleted" | "company.created" | "company.updated" | "company.deleted" | "deal.created" | "deal.updated" | "deal.stage_changed" | "deal.won" | "deal.lost" | "deal.deleted" | "task.created" | "task.updated" | "task.completed" | "task.deleted" | "activity.created" | "floor_plan.created" | "floor_plan.updated" | "floor_plan.deleted" | "load.created" | "load.updated" | "load.status_changed" | "load.delivered" | "load.deleted" | "invoice.sent" | "invoice.voided" | "invoice.paid" | "payment.recorded" | "payment.voided"required
One or more events. Each needs the key to have its area's read scope (contact.* needs contacts:read, and so on).

Example

curl -X POST "https://app.fetchfifi.com/api/v1/webhooks" \
  -H "Authorization: Bearer $FIFI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"url":"https://hooks.example.com/fifi","description":"Zapier: new contacts","events":["contact.created","contact.updated"]}'
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "url": "https://hooks.example.com/fifi",
    "description": "Zapier: new contacts",
    "events": [
      "contact.created",
      "contact.updated"
    ],
    "status": "active",
    "disabled_reason": null,
    "created_at": "2026-10-04T12:00:00.000Z",
    "updated_at": "2026-10-04T12:00:00.000Z",
    "secret": "whsec_Q2h3n8pX0aB7cD1eF4gH6iJ9kL2mN5oP8qR1sT4uV7w"
  }
}

GET/webhooks/{id}

Get a webhook endpoint

Needs: webhooks:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/webhooks/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "url": "https://hooks.example.com/fifi",
    "description": "Zapier: new contacts",
    "events": [
      "contact.created",
      "contact.updated"
    ],
    "status": "active",
    "disabled_reason": null,
    "created_at": "2026-10-04T12:00:00.000Z",
    "updated_at": "2026-10-04T12:00:00.000Z"
  }
}

DELETE/webhooks/{id}

Unsubscribe a webhook endpoint

Deletes the endpoint and its delivery log. Nothing more is sent to it.

Needs: webhooks:write

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl -X DELETE "https://app.fetchfifi.com/api/v1/webhooks/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c30",
    "deleted": true
  }
}

Floor plans

The floor plan object

idstring (uuid)required
The record's ID.
namestringrequired
1–60 characters, unique among the workspace's live plans in any case.
columnsintegerrequired
Grid width in cells (4–120).
rowsintegerrequired
Grid height in cells (4–120).
versionintegerrequired
Starts at 1 and goes up by one each time its layout or grid is saved, a kennel or seat leaves it, or it's deleted.
has_backgroundbooleanrequired
Whether it has a background image (the image itself isn't available through the API).
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).
shapesarray of objectrequired
Up to 500, oldest first.
podsarray of objectrequired
The kennels placed on it (only while Boarding is on), then its seats (only while Online booking is on), each in their order.

GET/floor-plans

List floor plans

Live floor plans (at most 20 a workspace), newest first. Works while Boarding or Online booking is on.

Needs: floor_plans:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).

Example

curl "https://app.fetchfifi.com/api/v1/floor-plans" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
      "name": "Salon",
      "columns": 30,
      "rows": 20,
      "version": 4,
      "has_background": false,
      "created_at": "2026-10-02T08:00:00.000Z",
      "updated_at": "2026-10-05T16:20:00.000Z"
    }
  ],
  "next_cursor": null
}

GET/floor-plans/{id}

Get a floor plan

A live plan with its shapes and pods, in quarter cells as stored. Kennels' pods are included only while Boarding is on, and seats' pods only while Online booking is on.

Needs: floor_plans:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/floor-plans/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
    "name": "Salon",
    "columns": 30,
    "rows": 20,
    "version": 4,
    "has_background": false,
    "created_at": "2026-10-02T08:00:00.000Z",
    "updated_at": "2026-10-05T16:20:00.000Z",
    "shapes": [
      {
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c41",
        "kind": "wall",
        "label": "",
        "x": 0,
        "y": 0,
        "w": 120,
        "h": 1,
        "rotation": 0
      },
      {
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c42",
        "kind": "area",
        "label": "Waiting area",
        "x": 4,
        "y": 56,
        "w": 40,
        "h": 20,
        "rotation": 0
      }
    ],
    "pods": [
      {
        "kind": "kennel",
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c43",
        "name": "Run 1",
        "x": 64,
        "y": 8,
        "w": 12,
        "h": 12,
        "rotation": 0
      },
      {
        "kind": "seat",
        "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c44",
        "name": "Table 1",
        "x": 8,
        "y": 8,
        "w": 12,
        "h": 8,
        "rotation": 90
      }
    ]
  }
}

Seats

The seat object

idstring (uuid)required
The record's ID.
namestringrequired
1–60 characters, unique among the workspace's live seats in any case.
kindstringrequired
What it is: Table, Tub, Chair, Dryer, Station or the workspace's own word (1–30 characters).
activebooleanrequired
false: switched off (it takes no new bookings; the ones it has keep it).
orderintegerrequired
Its place in the workspace's seat list (ascending).
floor_plan_idstring (uuid) or nullrequired
The live floor plan it's placed on; null when unplaced.
xinteger or nullrequired
Left edge of its footprint, in quarter cells; null when it isn't on a plan.
yinteger or nullrequired
Top edge of its footprint, in quarter cells; null when it isn't on a plan.
winteger or nullrequired
Width before rotation, in quarter cells; null when it isn't on a plan.
hinteger or nullrequired
Height before rotation, in quarter cells; null when it isn't on a plan.
rotation0 or 90 or 180 or 270 or nullrequired
Degrees clockwise; null when it isn't on a plan.

GET/seats

List seats

Live appointment seats (tables, tubs, chairs…; at most 100 a workspace), newest first, each with where it's placed. Seats belong to Online booking: while it's off (and Boarding on) the list is empty, as plans then leave seats out.

Needs: floor_plans:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).

Example

curl "https://app.fetchfifi.com/api/v1/seats" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c44",
      "name": "Table 1",
      "kind": "Table",
      "active": true,
      "order": 0,
      "floor_plan_id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c40",
      "x": 8,
      "y": 8,
      "w": 12,
      "h": 8,
      "rotation": 90
    },
    {
      "id": "0199a8f2-6b1c-7c3e-9a51-3f2d8e4b7c45",
      "name": "Tub 1",
      "kind": "Tub",
      "active": true,
      "order": 1,
      "floor_plan_id": null,
      "x": null,
      "y": null,
      "w": null,
      "h": null,
      "rotation": null
    }
  ],
  "next_cursor": null
}

Service records

The service record object

idstring (uuid)required
The record's ID.
status"draft" | "completed" | "voided"required
draft (being written), completed (kept at least 3 years) or voided (kept, with a reason).
booking_idstring (uuid) or nullrequired
The visit (booking) it records, or null: written for a customer, or the visit's contact was purged.
contact_idstring (uuid) or nullrequired
The customer (see GET /contacts/{id}), or null once the contact is in the trash or purged.
technician_idstring (uuid)required
The technician: a member's ID (see GET /members).
service_atstring (ISO 8601 date-time)required
When the service was done (ISO 8601, UTC).
service_namestringrequired
The service (1–120 characters).
customer_namestringrequired
The customer's name as written on the record.
service_addressstringrequired
The service address as written on the record (empty if none).
technician_namestringrequired
The technician's name, written on the record when it was completed (empty for a draft).
licence_numberstring,nullrequired
The technician's applicator licence in force when the record was completed (null for a draft, or none on file).
conditionsstring,nullrequired
Conditions, like weather and wind (up to 200 characters), or null.
notesstring,nullrequired
Notes (up to 5,000 characters), or null.
completed_atstring (ISO 8601 date-time) or nullrequired
When it was completed (null for a draft).
voided_atstring (ISO 8601 date-time) or nullrequired
When it was voided, or null.
void_reasonstring,nullrequired
Why it was voided, or null.
created_atstring (ISO 8601 date-time)required
When it was created (ISO 8601, UTC).
updated_atstring (ISO 8601 date-time)required
When it last changed (ISO 8601, UTC).
applicationsarray of objectrequired
The products applied, in order (up to 30).

GET/service-records

List service records

Service records not in the trash (voided ones too, with their status), newest first, each with its applications.

Needs: service_records:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
contact_idstring (uuid)
Only this contact's records.
technician_idstring (uuid)
Only this technician's records (a member's ID).
status"draft" | "completed" | "voided"
Only records with this status.
service_afterstring (ISO 8601 date-time)
Only records serviced at or after this time (ISO 8601, with a time zone).
service_beforestring (ISO 8601 date-time)
Only records serviced before this time (ISO 8601, with a time zone).

Example

curl "https://app.fetchfifi.com/api/v1/service-records" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c01",
      "status": "completed",
      "booking_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c02",
      "contact_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c03",
      "technician_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c04",
      "service_at": "2026-10-12T14:00:00.000Z",
      "service_name": "General pest service",
      "customer_name": "Megan Ross",
      "service_address": "14 Alder Ln, Raleigh, NC 27609",
      "technician_name": "Luis Ortega",
      "licence_number": "NC-12345",
      "conditions": "Sunny, 72°F, wind 5 mph NW",
      "notes": "Perimeter treated; ants at the patio slab.",
      "completed_at": "2026-10-12T15:02:00.000Z",
      "voided_at": null,
      "void_reason": null,
      "created_at": "2026-10-12T14:50:00.000Z",
      "updated_at": "2026-10-12T15:02:00.000Z",
      "applications": [
        {
          "product_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c05",
          "product_name": "Bifen I/T",
          "epa_reg_no": "53883-118",
          "amount": 2.5,
          "unit": "fl_oz",
          "target_pests": "Ants, spiders",
          "areas": "Foundation perimeter, 3 ft band",
          "method": "Spray",
          "rate": "1 fl oz per gal"
        },
        {
          "product_id": null,
          "product_name": "Glue boards",
          "epa_reg_no": null,
          "amount": 4,
          "unit": "each",
          "target_pests": "Spiders",
          "areas": "Garage corners",
          "method": "Trap",
          "rate": null
        }
      ]
    }
  ],
  "next_cursor": null
}

GET/service-records/{id}

Get a service record

A service record not in the trash, with its applications in order.

Needs: service_records:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/service-records/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c01",
    "status": "completed",
    "booking_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c02",
    "contact_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c03",
    "technician_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c04",
    "service_at": "2026-10-12T14:00:00.000Z",
    "service_name": "General pest service",
    "customer_name": "Megan Ross",
    "service_address": "14 Alder Ln, Raleigh, NC 27609",
    "technician_name": "Luis Ortega",
    "licence_number": "NC-12345",
    "conditions": "Sunny, 72°F, wind 5 mph NW",
    "notes": "Perimeter treated; ants at the patio slab.",
    "completed_at": "2026-10-12T15:02:00.000Z",
    "voided_at": null,
    "void_reason": null,
    "created_at": "2026-10-12T14:50:00.000Z",
    "updated_at": "2026-10-12T15:02:00.000Z",
    "applications": [
      {
        "product_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c05",
        "product_name": "Bifen I/T",
        "epa_reg_no": "53883-118",
        "amount": 2.5,
        "unit": "fl_oz",
        "target_pests": "Ants, spiders",
        "areas": "Foundation perimeter, 3 ft band",
        "method": "Spray",
        "rate": "1 fl oz per gal"
      },
      {
        "product_id": null,
        "product_name": "Glue boards",
        "epa_reg_no": null,
        "amount": 4,
        "unit": "each",
        "target_pests": "Spiders",
        "areas": "Garage corners",
        "method": "Trap",
        "rate": null
      }
    ]
  }
}

GET/service-products

List service products

The workspace's treatment products (at most 500, archived ones included), newest first.

Needs: service_records:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).

Example

curl "https://app.fetchfifi.com/api/v1/service-products" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9c05",
      "name": "Bifen I/T",
      "epa_reg_no": "53883-118",
      "active_ingredient": "Bifenthrin 7.9%",
      "default_unit": "fl_oz",
      "default_method": "Spray",
      "archived": false,
      "created_at": "2026-09-01T09:00:00.000Z",
      "updated_at": "2026-09-01T09:00:00.000Z"
    }
  ],
  "next_cursor": null
}

Loads

GET/loads

List loads

Loads not in the trash (delivered and cancelled ones too), newest first, each with its stops and its documents' metadata.

Needs: loads:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
status"open" | "covered" | "dispatched" | "in_transit" | "delivered" | "cancelled"
Only loads with this status.
shipper_idstring (uuid)
Only this shipper's loads (a company's ID).
carrier_idstring (uuid)
Only loads this carrier hauls (a company's ID).
deal_idstring (uuid)
Only loads on this lane (a deal's ID).
pickup_afterstring (ISO 8601 date-time)
Only loads whose first pickup is at or after this time (ISO 8601, with a time zone).
pickup_beforestring (ISO 8601 date-time)
Only loads whose first pickup is before this time (ISO 8601, with a time zone).

Example

curl "https://app.fetchfifi.com/api/v1/loads" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d01",
      "number": 48213,
      "status": "in_transit",
      "owner_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d02",
      "shipper_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d03",
      "shipper_contact_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d04",
      "deal_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d05",
      "carrier_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d06",
      "driver_contact_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d07",
      "truck_number": "214",
      "trailer_number": "5531",
      "equipment": "dry_van",
      "temp_min_f": null,
      "temp_max_f": null,
      "weight_lb": 38000,
      "pieces": 22,
      "commodity": "Corrugated boxes",
      "hazmat": false,
      "reference": "PO 77120",
      "rate_cents": 145000,
      "carrier_pay_cents": 118000,
      "margin_cents": 27000,
      "pickup_at": "2026-10-15T13:00:00.000Z",
      "delivery_at": "2026-10-16T15:00:00.000Z",
      "origin": "Joliet, IL",
      "destination": "Memphis, TN",
      "delivered_at": null,
      "cancelled_at": null,
      "cancel_reason": null,
      "notes": "Driver to call 30 minutes out.",
      "created_at": "2026-10-13T16:20:00.000Z",
      "updated_at": "2026-10-15T15:05:00.000Z",
      "stops": [
        {
          "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d08",
          "position": 0,
          "kind": "pickup",
          "facility": "Joliet Packaging Co. plant",
          "address": "1200 Mound Rd, Joliet, IL 60436",
          "locality": "Joliet, IL",
          "timezone": "America/Chicago",
          "window_start": "2026-10-15T13:00:00.000Z",
          "window_end": "2026-10-15T15:00:00.000Z",
          "appointment_ref": "JP-5521",
          "booking_id": null,
          "arrived_at": "2026-10-15T13:10:00.000Z",
          "departed_at": "2026-10-15T14:05:00.000Z",
          "notes": null
        },
        {
          "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d09",
          "position": 1,
          "kind": "delivery",
          "facility": "Delta Distribution Center",
          "address": "3450 Lamar Ave, Memphis, TN 38118",
          "locality": "Memphis, TN",
          "timezone": "America/Chicago",
          "window_start": "2026-10-16T13:00:00.000Z",
          "window_end": "2026-10-16T15:00:00.000Z",
          "appointment_ref": null,
          "booking_id": null,
          "arrived_at": null,
          "departed_at": null,
          "notes": "Lumper paid by the receiver."
        }
      ],
      "documents": [
        {
          "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d10",
          "kind": "rate_confirmation",
          "stop_id": null,
          "content_type": "application/pdf",
          "byte_size": 48213,
          "name": "rate-confirmation-48213.pdf",
          "created_at": "2026-10-13T16:40:00.000Z"
        }
      ]
    }
  ],
  "next_cursor": null
}

GET/loads/{id}

Get a load

A load not in the trash, with its stops in order and its documents' metadata (download them from the load's page in Fifi).

Needs: loads:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/loads/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d01",
    "number": 48213,
    "status": "in_transit",
    "owner_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d02",
    "shipper_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d03",
    "shipper_contact_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d04",
    "deal_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d05",
    "carrier_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d06",
    "driver_contact_id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d07",
    "truck_number": "214",
    "trailer_number": "5531",
    "equipment": "dry_van",
    "temp_min_f": null,
    "temp_max_f": null,
    "weight_lb": 38000,
    "pieces": 22,
    "commodity": "Corrugated boxes",
    "hazmat": false,
    "reference": "PO 77120",
    "rate_cents": 145000,
    "carrier_pay_cents": 118000,
    "margin_cents": 27000,
    "pickup_at": "2026-10-15T13:00:00.000Z",
    "delivery_at": "2026-10-16T15:00:00.000Z",
    "origin": "Joliet, IL",
    "destination": "Memphis, TN",
    "delivered_at": null,
    "cancelled_at": null,
    "cancel_reason": null,
    "notes": "Driver to call 30 minutes out.",
    "created_at": "2026-10-13T16:20:00.000Z",
    "updated_at": "2026-10-15T15:05:00.000Z",
    "stops": [
      {
        "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d08",
        "position": 0,
        "kind": "pickup",
        "facility": "Joliet Packaging Co. plant",
        "address": "1200 Mound Rd, Joliet, IL 60436",
        "locality": "Joliet, IL",
        "timezone": "America/Chicago",
        "window_start": "2026-10-15T13:00:00.000Z",
        "window_end": "2026-10-15T15:00:00.000Z",
        "appointment_ref": "JP-5521",
        "booking_id": null,
        "arrived_at": "2026-10-15T13:10:00.000Z",
        "departed_at": "2026-10-15T14:05:00.000Z",
        "notes": null
      },
      {
        "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d09",
        "position": 1,
        "kind": "delivery",
        "facility": "Delta Distribution Center",
        "address": "3450 Lamar Ave, Memphis, TN 38118",
        "locality": "Memphis, TN",
        "timezone": "America/Chicago",
        "window_start": "2026-10-16T13:00:00.000Z",
        "window_end": "2026-10-16T15:00:00.000Z",
        "appointment_ref": null,
        "booking_id": null,
        "arrived_at": null,
        "departed_at": null,
        "notes": "Lumper paid by the receiver."
      }
    ],
    "documents": [
      {
        "id": "0199b2c4-1d2e-7a3b-8c4d-5e6f7a8b9d10",
        "kind": "rate_confirmation",
        "stop_id": null,
        "content_type": "application/pdf",
        "byte_size": 48213,
        "name": "rate-confirmation-48213.pdf",
        "created_at": "2026-10-13T16:40:00.000Z"
      }
    ]
  }
}

Invoices

GET/invoices

List invoices

Invoices not in the trash (drafts and void ones too, with their status), newest first, without their lines.

Needs: invoices:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
status"draft" | "sent" | "partially_paid" | "paid" | "void"
Only invoices with this status.
contact_idstring (uuid)
Only invoices billed to this contact.
company_idstring (uuid)
Only invoices billed to this company.
issued_afterstring (YYYY-MM-DD)
Only invoices issued on or after this date (YYYY-MM-DD).
issued_beforestring (YYYY-MM-DD)
Only invoices issued before this date (YYYY-MM-DD).
overdue"true" | "false"
true: only overdue invoices (sent or partially paid, due before today in the workspace owner's time zone). false: only the others.

Example

curl "https://app.fetchfifi.com/api/v1/invoices" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c6",
      "number": "INV-1042",
      "status": "sent",
      "overdue": false,
      "contact_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c7",
      "company_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c8",
      "bill_to_name": "Harbor Grill",
      "bill_to_email": "ap@harborgrill.example",
      "bill_to_address": "18 Pier Rd, Wilmington, NC 28401",
      "customer_reference": "PO 5521",
      "issue_date": "2026-10-15",
      "due_date": "2026-11-14",
      "terms": "net_30",
      "currency": "USD",
      "subtotal_cents": 48000,
      "tax_label": "Sales tax",
      "tax_rate": 7.25,
      "tax_cents": 580,
      "total_cents": 48580,
      "paid_cents": 0,
      "balance_cents": 48580,
      "notes": "Thank you for your business.",
      "issued_at": "2026-10-15T14:02:00.000Z",
      "paid_at": null,
      "voided_at": null,
      "void_reason": null,
      "created_at": "2026-10-15T13:40:00.000Z",
      "updated_at": "2026-10-15T14:02:00.000Z"
    }
  ],
  "next_cursor": null
}

GET/invoices/{id}

Get an invoice

An invoice not in the trash, with its lines in order and its payments (newest received first, voided ones included).

Needs: invoices:read

Path

idstring (uuid)required
The record's ID (in the path).

Example

curl "https://app.fetchfifi.com/api/v1/invoices/<id>" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": {
    "id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c6",
    "number": "INV-1042",
    "status": "partially_paid",
    "overdue": false,
    "contact_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c7",
    "company_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c8",
    "bill_to_name": "Harbor Grill",
    "bill_to_email": "ap@harborgrill.example",
    "bill_to_address": "18 Pier Rd, Wilmington, NC 28401",
    "customer_reference": "PO 5521",
    "issue_date": "2026-10-15",
    "due_date": "2026-11-14",
    "terms": "net_30",
    "currency": "USD",
    "subtotal_cents": 48000,
    "tax_label": "Sales tax",
    "tax_rate": 7.25,
    "tax_cents": 580,
    "total_cents": 48580,
    "paid_cents": 24000,
    "balance_cents": 24580,
    "notes": "Thank you for your business.",
    "issued_at": "2026-10-15T14:02:00.000Z",
    "paid_at": null,
    "voided_at": null,
    "void_reason": null,
    "created_at": "2026-10-15T13:40:00.000Z",
    "updated_at": "2026-10-15T14:02:00.000Z",
    "lines": [
      {
        "position": 0,
        "description": "General pest service, Oct 3, 2026",
        "quantity": 1,
        "unit_price_cents": 40000,
        "line_total_cents": 40000,
        "taxable": false,
        "source_type": "booking",
        "source_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c9"
      },
      {
        "position": 1,
        "description": "Bait stations (4)",
        "quantity": 4,
        "unit_price_cents": 2000,
        "line_total_cents": 8000,
        "taxable": true,
        "source_type": null,
        "source_id": null
      }
    ],
    "payments": [
      {
        "id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5d0",
        "invoice_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c6",
        "kind": "payment",
        "amount_cents": 24000,
        "received_on": "2026-10-20",
        "method": "check",
        "reference": "Check 1042",
        "notes": null,
        "voided_at": null,
        "void_reason": null,
        "processor": null,
        "processor_payment_id": null,
        "fee_cents": null,
        "dispute_status": null,
        "created_at": "2026-10-20T16:12:00.000Z",
        "updated_at": "2026-10-20T16:12:00.000Z"
      }
    ]
  }
}

Payments

GET/payments

List payments

Payments and refunds recorded for invoices (money received elsewhere: Fifi doesn't move money), newest first, those of invoices not in the trash. A refund has kind refund and a positive amount_cents. Voided entries (entered in error) are left out unless include_voided=true. Needs the invoices:read scope.

Needs: invoices:read

Query parameters

limitintegerdefault 25
Records per page, 1–100 (default 25).
cursorstring
The next_cursor of the previous page. Use it with the same sort.
sort"created_at" | "updated_at"default "created_at"
created_at: newest first (default). updated_at: most recently changed first.
updated_sincestring (ISO 8601 date-time)
Only records changed at or after this time (ISO 8601, with a time zone).
invoice_idstring (uuid)
Only payments for this invoice.
received_afterstring (YYYY-MM-DD)
Only payments received on or after this date (YYYY-MM-DD).
received_beforestring (YYYY-MM-DD)
Only payments received before this date (YYYY-MM-DD).
method"cash" | "check" | "bank_transfer" | "card" | "other" | "online"
Only payments by this method.
kind"payment" | "refund"
payment or refund.
include_voided"true" | "false"
true: voided entries too (default false).

Example

curl "https://app.fetchfifi.com/api/v1/payments" \
  -H "Authorization: Bearer $FIFI_API_KEY"
{
  "data": [
    {
      "id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5d0",
      "invoice_id": "0199c1a2-3b4c-7d5e-8f60-718293a4b5c6",
      "kind": "payment",
      "amount_cents": 24000,
      "received_on": "2026-10-20",
      "method": "check",
      "reference": "Check 1042",
      "notes": null,
      "voided_at": null,
      "void_reason": null,
      "processor": null,
      "processor_payment_id": null,
      "fee_cents": null,
      "dispute_status": null,
      "created_at": "2026-10-20T16:12:00.000Z",
      "updated_at": "2026-10-20T16:12:00.000Z"
    }
  ],
  "next_cursor": null
}