FuelCard API v1
Sync drivers, cards, gas stations and fuel transactions between FuelCard and MedRide (or any system).
Getting started
- In FuelCard, open Integrations and request API access with your MedRide account ID.
- When FuelCard approves it, create an API key and paste it into MedRide.
- Optional: add a webhook URL to get new transactions in real time instead of polling.
Base URL: https://www.multiversefuel.com/api/v1. All requests and responses are JSON. Times are UTC (ISO 8601). Money is given in cents (amountCents) and dollars (amount).
Authentication
Send the key in the Authorization header (or X-Api-Key). Keys start with fc_live_ (or fc_test_ in the sandbox).
curl https://www.multiversefuel.com/api/v1/ping \ -H "Authorization: Bearer fc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Each key has permissions (scopes): drivers:read, drivers:write, cards:read, cards:write, transactions:read, stations:read, reports:read. Calls outside the key's scopes get 403.
Endpoints
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /ping | — | Check that your key works; returns its company and scopes. |
| GET | /company | any | Company profile and totals. |
| GET | /drivers | drivers:read | List drivers. Filters: status, externalId, q, updatedSince, page, pageSize. |
| GET | /drivers/{id} | drivers:read | One driver. |
| POST | /drivers | drivers:write | Create a driver (and their cardholder). Body: firstName, lastName, email, phone, employeeId, externalId, termsAccepted. |
| PATCH | /drivers/{id} | drivers:write | Update email, phone, employeeId or externalId (the MedRide driver id). |
| POST | /drivers/{id}/deactivate | drivers:write | Deactivate and freeze their cards (?cancelCards=true to cancel). |
| POST | /drivers/{id}/reactivate | drivers:write | Reactivate a driver. |
| GET | /cards | cards:read | List cards with balances. Filters: status, kind (driver|generic), driverId, driverExternalId, updatedSince. |
| GET | /cards/{id} | cards:read | One card with loaded / spent / on hold / available. |
| POST | /cards/{id}/status | cards:write | Freeze or unfreeze: {"status":"inactive"} or {"status":"active"}. |
| GET | /transactions | transactions:read | Fuel transactions (card expenses). Filters: from, to, driverId, driverExternalId, cardId, stationId, sinceId. |
| GET | /transactions/{id} | transactions:read | One transaction. |
| GET | /fuel-log | transactions:read | Company fuel log, newest first: full fill-up records (driver, station address/phone, gallons, price, odometer, fuel / non-fuel / tax). Filters: since, until, updatedSince, driverExternalId, driverId, cardId, stationId, category (fuel, car_wash, tires_parts, repair, other), needsNote=true, cursor, limit (max 200). |
| GET | /fuel-log/{id} | transactions:read | One fill-up record. |
| GET | /stations | stations:read | Gas stations your cards were used at. Filters: state, city, q. |
| GET | /stations/{id} | stations:read | One gas station. |
| GET | /reports/spend | reports:read | Spend totals. groupBy = driver | card | station | day, from, to. |
Paging & incremental sync
Lists return { "data": [...], "page": 1, "pageSize": 100, "totalCount": 250, "totalPages": 3, "hasMore": true }. Use page and pageSize (max 200).
Recommended sync for transactions: remember the highest id you've stored and ask for /transactions?sinceId=<that id> every few minutes (or use webhooks). For drivers and cards, use updatedSince.
Linking drivers: set each FuelCard driver's externalId to the MedRide driver id (PATCH /drivers/{id}). Transactions then carry driverExternalId, so expenses land on the right MedRide driver automatically.
Transaction object
{
"id": 1042, "stripeTransactionId": "ipi_1Q...", "type": "capture",
"amountCents": 5840, "amount": 58.40, "currency": "usd",
"cardId": 17, "cardLast4": "4242", "cardKind": "driver", "vehicleLabel": "Van 12",
"driverId": 9, "driverExternalId": "MR-DRV-3381", "driverName": "Maria Lopez",
"merchant": { "stationId": 4, "networkId": "1234567890", "name": "Circle K #2741",
"category": "automated_fuel_dispensers", "city": "Phoenix", "state": "AZ", "postalCode": "85004" },
"fuel": { "type": "unleaded_regular", "unit": "us_gallon", "quantity": 16.5, "unitPrice": 3.539 },
"createdUtc": "2026-10-22T14:03:11Z"
}
Fuel log record
Returned by /fuel-log and sent as data in transaction.created / transaction.updated webhooks. It keeps every field of the transaction object above (same names) and adds driver, station, fleet and accounting details. Money in accounting is in cents. Fields the station didn't send are null.
{
"id": 1042, "stripeTransactionId": "ipi_1Q...", "stripeAuthorizationId": "iauth_1Q...",
"type": "capture", "status": "captured",
"amountCents": 5840, "amount": 58.40, "currency": "usd",
"cardId": 17, "cardLast4": "4242", "cardKind": "driver", "vehicleLabel": "Van 12",
"driverId": 9, "driverExternalId": "MR-DRV-3381", "driverName": "Maria Lopez",
"driver": { "id": 9, "firstName": "Maria", "lastName": "Lopez", "fullName": "Maria Lopez",
"employeeId": "E-114", "externalId": "MR-DRV-3381", "email": "[email protected]", "phone": "+16025550101" },
"merchant": { "stationId": 4, "networkId": "1234567890", "name": "Circle K #2741",
"category": "automated_fuel_dispensers", "city": "Phoenix", "state": "AZ", "postalCode": "85004" },
"station": { "id": 4, "merchantId": "1234567890", "name": "Circle K #2741",
"category": "automated_fuel_dispensers", "merchantCategoryCode": "5542",
"streetAddress": "1001 N Central Ave, Phoenix, AZ 85004", "city": "Phoenix", "state": "AZ",
"postalCode": "85004", "country": "US", "phone": "(602) 555-0199",
"latitude": 33.4596, "longitude": -112.0740, "terminalId": "T0042", "taxId": null, "url": null },
"fuel": { "type": "unleaded_regular", "unit": "us_gallon", "quantity": 16.5, "unitPrice": 3.539 },
"fleet": { "odometer": 48213, "vehicleNumber": "12", "driverPrompt": "3381", "userId": null,
"unspecifiedId": null, "purchaseType": "fuel_purchase", "serviceType": "self_service" },
"accounting": { "totalCents": 5840, "fuelCents": 5840, "nonFuelCents": 0, "taxLocalCents": 120,
"taxNationalCents": 302, "taxTotalCents": 422, "currency": "usd",
"merchantAmountCents": 5840, "merchantCurrency": "usd",
"authorizedAmountCents": 10000, "reference": null, "total": 58.40 },
"authorizationMethod": "chip", "wallet": null,
"authorizedUtc": "2026-10-22T14:01:55Z", "createdUtc": "2026-10-22T14:03:11Z", "updatedUtc": null,
"expense": { "category": "fuel", "categoryLabel": "Fuel", "accountCode": "6100", "accountName": "Fuel expense",
"note": null, "noteBy": null, "noteUtc": null, "needsNote": false }
}
expense tells you how to book the purchase: category is fuel, car_wash, tires_parts, repair or other, and accountCode / accountName come from the company's Expense accounts page. Non-fuel purchases need a note: needsNote is true until someone adds one, and you get a transaction.updated event when the note is saved.
Street address, phone and location come from FuelCard's station directory (card networks only send name, city, state, zip and merchant id). They can be null for a brand-new station and fill in later — you'll get a transaction.updated event when fill-up details change.
MedRide live sync
- Live: add your MedRide endpoint as the webhook URL (Integrations page). Every captured fill-up is POSTed within seconds as
transaction.created; late details (fleet prompts, tax breakdown) arrive astransaction.updated; declines asauthorization.declined. - Match drivers: set each driver's
externalIdto the MedRide driver id. Every record carries it indriverExternalIdanddriver.externalId. - Store idempotently: upsert by
stripeTransactionId(orid); ignore duplicate eventids. - Reconcile / backfill: call
GET /fuel-log?updatedSince=<last sync time>, follownextCursoruntilhasMoreis false. Cursor pages look like{ "data": [...], "nextCursor": 1001, "hasMore": true }.
curl "https://www.multiversefuel.com/api/v1/fuel-log?updatedSince=2026-10-01T00:00:00Z&limit=200" \ -H "Authorization: Bearer fc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Webhooks
FuelCard POSTs these events to your URL:
transaction.created— A fill-up was captured (or refunded). Full fuel-log record: driver, station, gallons, price, odometer, fuel / non-fuel / tax breakdown.transaction.updated— More details arrived for an existing fill-up (for example fleet prompts or the station's fuel/tax breakdown). Same record shape.authorization.declined— A purchase was declined at the pump, with the reason.card.created— A card was issued.card.updated— A card was frozen, unfrozen, activated or canceled.driver.created— A driver was added.driver.updated— A driver's details or status changed.
Body:
{ "id": "evt_9f1c...", "type": "transaction.created", "created": "2026-10-22T14:03:12Z",
"companyId": 5, "data": { ...fuel log record... } }
Every request has a FuelCard-Signature: t=<unix time>,v1=<hex> header. v1 is HMAC-SHA256 of "{t}.{raw body}" using your signing secret. Reject requests whose signature doesn't match or whose t is more than 5 minutes old. Answer with any 2xx within 10 seconds; otherwise we retry after 1m, 5m, 15m, 1h, 3h, 6h, 12h and 24h. Events can arrive more than once — use id to ignore duplicates.
// C# example
bool Verify(string secret, string header, string body)
{
var parts = header.Split(',').Select(p => p.Split('=', 2)).ToDictionary(p => p[0], p => p[1]);
var t = long.Parse(parts["t"]);
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes($"{t}.{body}"))).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(parts["v1"]));
}
Errors & limits
Errors look like { "error": "not_found", "message": "Driver not found." }. Status codes: 400 invalid input, 401 missing/invalid key, 403 missing scope, 404 not found, 409 conflict, 422 card network error, 429 too many requests.
Limit: 120 requests per minute per key. On 429, wait for the Retry-After seconds.
Security
- Keys are stored hashed; FuelCard can't show a key again. Revoke and replace a key if it leaks.
- Full card numbers and CVCs are never available through the API — only the last 4 digits.
- Every call is logged and visible to the company on its Integrations page.
- Webhook URLs must be HTTPS.