Skip to main content

Authentication

401 Unauthorized

“Invalid, expired, or missing API key”
  • Confirm your key starts with yoshi_
  • Check that the Authorization header uses the Bearer scheme: Authorization: Bearer yoshi_YOUR_KEY
  • Verify the key hasn’t been revoked in your dashboard
  • Make sure there are no extra spaces or newlines in the key
# Correct
curl https://api.yoshi.ai/accounts \
  -H "Authorization: Bearer yoshi_3xK9mP..."

# Wrong — missing "Bearer"
curl https://api.yoshi.ai/accounts \
  -H "Authorization: yoshi_3xK9mP..."

Rate limiting

429 Too Many Requests

“Rate limit exceeded” Check the response headers to see your current limit and when it resets:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1712764800
Retry-After: 43
Wait Retry-After seconds before retrying. Implement exponential backoff for automated systems:
if (response.status === 429) {
  const retryAfter = Number(response.headers.get("Retry-After")) || 60;
  await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
Use webhooks to replace polling. Every webhook you process is one fewer API call.

Webhooks

Not receiving events

  • URL must be HTTPS. HTTP endpoints are rejected.
  • Return 2xx within 15 seconds. If your handler times out, the event is marked as failed and retried.
  • Check the portal. Open the self-service portal to see delivery attempts, response codes, and retry status.
  • Check event filters. If you specified filter_types when creating the endpoint, only those events are delivered.

Signature verification failing

  • Use the raw request body for verification, not a parsed JSON object. Parsing and re-serializing changes whitespace and key order, which breaks the signature.
  • Verify the timestamp is within your tolerance window (default: 5 minutes).
  • Make sure you’re using the correct signing secret for this endpoint.
See Signature Verification for framework-specific examples.

Transactions

Duplicate transactions

Transactions can appear twice during the pending-to-posted transition. A pending transaction and its posted version may coexist briefly until the pending record clears. Always use the transaction id for deduplication — don’t match on amount, date, or description.

Amount has unexpected sign

The amount field follows accounting convention:
  • On a credit card account, a purchase (outflow) has a positive amount
  • On a checking account, a purchase (outflow) has a negative amount
Use cash_flow_direction for logic and amount_absolute for display:
// Display
const label = `${tx.cash_flow_direction === "outflow" ? "-" : "+"}$${tx.amount_absolute}`;

// Logic
if (tx.cash_flow_direction === "outflow" && tx.amount_absolute > 100) {
  // Large purchase
}

date_posted vs date_authorized

FieldUse when
date_postedReporting, accounting, reconciliation. This is the final settlement date.
date_authorizedShowing when the user actually made the purchase. May be null for some transaction types.

Data freshness

Stale data / as_of timestamp

Yoshi syncs data from your connected institutions on a recurring schedule. The as_of field on each account tells you when data was last refreshed. If as_of is more than a few hours old:
  • The institution may sync less frequently (some update once per business day)
  • There may be a temporary connection issue
Show users the as_of timestamp so they know how current the data is. Do not promise real-time accuracy.

Missing recent transactions

Transactions appear after the next sync cycle, not instantly. If a user just made a purchase:
  1. It won’t appear until the next sync
  2. It may appear as pending first
  3. It settles as posted within 1–3 business days

Pagination

Cursor returns the same data

  • Make sure you’re passing the cursor from the most recent response, not a cached cursor.
  • Don’t store cursors between sessions — they’re designed for sequential traversal.
  • If a cursor becomes invalid, the API returns 400. Restart pagination without a cursor.
See Pagination for the full guide.

Still stuck?

Include the request_id from the error response when reaching out for support. Every API response includes it in the meta object and as an X-Request-Id header:
{
  "meta": {
    "request_id": "req_a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
Last modified on April 30, 2026