OpenAPI viewer

JSON over HTTPS, one key per request. Every error says what went wrong, why,
and what would change the outcome.

Scripture is the World English Bible, which is in the public domain.

Billing is metered: search per request, voice search and alignment per
second of audio. GET /v1/usage shows the same figures billing uses.

Make your keys in the developer console at
https://play.fellowverse.app/developers. Every account starts with free
test keys, so you can try everything before anything is billed.

Get started

  • Sign in to the developer console with your email and name your account.
  • Under Keys, make a test key and copy it: it is shown once.
  • Search:
  • curl https://play.fellowverse.app/v1/search \
         -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
         -d '{"query": "rest for the weary", "limit": 3}'
  • See what you used with GET /v1/usage, or under Use in the console.
  • Test keys call the same API as live keys, so the code you write now is the
    code you ship.

    Keep your key on your server

    A key in an Android or iOS app, or in a web page, can be read by anyone who
    has the app. Call Fellowverse from your own server and have your app call
    your server: it holds the key, decides who may search, and can cache
    popular results. Use one key per server or environment, named for it, so
    you can replace one without touching the others.

    Add voice search to an app

    Record the request in the app, send it to your server, and pass the audio
    to POST /v1/voice-search as the body with its type. On Android,
    MediaRecorder with MPEG-4 and AAC gives audio/mp4; in a browser,
    MediaRecorder gives audio/webm or audio/mp4. Show the transcript
    with the results, so people see what was heard; an empty one means nothing
    could be heard.

    Play a recording verse by verse

  • Upload the recording (POST /v1/uploads) and start an alignment
  • (POST /v1/alignments). For a Bible reading, leave out segments and the passage and its verses are found for you.
  • Poll GET /v1/jobs/{id} every few seconds until it is done or failed.
  • Store the result with the recording. Each segment's startMs and
  • endMs let your player seek to a verse; words times each word.
  • If you asked for clips, download them with your key and host them
  • yourself. They are there for 7 days after the job finishes.

    Align each recording once, when it is published, and serve the timings
    from your own storage, so listeners never wait on an alignment.

    Handle errors and limits

    Every error has a stable code to branch on, and problem, reason and
    fix written to be read. Fix a 400 before sending it again. Retry a
    429 after a short wait (for api_test_quota, fix says when the day's
    allowance resets) and a 5xx after a wait that doubles each time.
    Searches are safe to retry; before retrying POST /v1/alignments after a
    timeout, check whether the first request made a job. A failed job's
    error.code says why it failed, such as api_text_mismatch or
    api_passage_unknown, and it fails at once rather than being retried.

    Go live

  • The account's owner chooses Request live access in the console. We
  • get in touch to set up billing and turn on live keys.
  • Make a live key under Keys. It takes a code we email you.
  • Put it on your server in place of the test key. Nothing else changes.
  • To replace a key, roll it and give the old one up to a week of grace
  • while you deploy the new one.

    Every live key made, rolled or revoked is emailed to the account's owners,
    and the console's Activity shows who did what and when.

    Version 1.0.0 Base URL: https://play.fellowverse.app

    Authentication

    apiKey

    Type: HTTP bearer

    Send your key as `Authorization: Bearer fv_live_...`. A key is shown once, when it is made. Keep it on your server, not in a web page or an app. Lost keys are revoked and replaced, not recovered. Test keys (`fv_test_...`) call the same API for free, with a daily allowance per account and alignments of up to 10 minutes. Rolling a key replaces it and lets the old one keep working for up to a week while you switch over.

    Search

    Passages by meaning or by reference, typed or spoken.

    POST/v1/search

    Search by words or a reference

    Takes a few words, a feeling or a reference, and returns passages of two
    to six verses each, with a context line saying where they sit and who is
    speaking.

    A reference such as "Psalm 23" or "John 3:16" returns that passage and the
    rest of its chapter, with kind set to reference. Anything else is a
    search by meaning and wording, with kind set to search.

    Billed per request. Limited to 120 a minute per key.

    Request Body

    Content-Type: application/json

    {
      "query": "something for fear",
      "limit": 5
    }

    Responses

    200The passages found, best first.
    400The request could not be used. `invalid_request` when a field is missing or has the wrong type; `bad_request` when the audio or a value could not be read.
    {
      "error": {
        "code": "bad_request",
        "problem": "The recording is 41 seconds long.",
        "reason": "Voice search takes a spoken request of up to 30 seconds.",
        "fix": "Send a shorter recording; for long audio use alignment."
      }
    }
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    429A per-key limit for the minute was reached (`api_rate_limited`; the reason names the limit), or a test key used up its account's daily allowance (`api_test_quota`; the fix says when it resets).
    {
      "error": {
        "code": "api_rate_limited",
        "problem": "Too many search requests in a minute for this key.",
        "reason": "Each key may make 120 a minute.",
        "fix": "Wait a moment, or ask Fellowverse for a higher limit."
      }
    }
    500Something went wrong on our side. The code is `internal`.
    POST/v1/voice-search

    Search by a spoken request

    Takes a spoken request of up to 30 seconds and 10 MB as the request
    body, with its type, and returns what was heard and the same results as a search. The
    audio is not kept.

    When nothing could be heard, the response holds an empty transcript
    and no results.

    Billed per second of audio, at least one second. Limited to 60 a minute
    per key.

    Parameters

    NameLocationTypeDescription
    limitqueryintegerHow many passages to return.

    Request Body

    Content-Type: audio/mpeg

    The recording itself as the body, with its type in Content-Type. MP3, WAV, M4A, OGG, WebM and FLAC are read.

    Responses

    200What was heard, and the passages found for it.
    400The request could not be used. `invalid_request` when a field is missing or has the wrong type; `bad_request` when the audio or a value could not be read.
    {
      "error": {
        "code": "bad_request",
        "problem": "The recording is 41 seconds long.",
        "reason": "Voice search takes a spoken request of up to 30 seconds.",
        "fix": "Send a shorter recording; for long audio use alignment."
      }
    }
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    413The body is over the limit: 200 MB for an upload, 10 MB for voice search. The code is `request_error`.
    415The Content-Type is not one of the audio types read. The code is `request_error`.
    429A per-key limit for the minute was reached (`api_rate_limited`; the reason names the limit), or a test key used up its account's daily allowance (`api_test_quota`; the fix says when it resets).
    {
      "error": {
        "code": "api_rate_limited",
        "problem": "Too many search requests in a minute for this key.",
        "reason": "Each key may make 120 a minute.",
        "fix": "Wait a moment, or ask Fellowverse for a higher limit."
      }
    }
    500Something went wrong on our side. The code is `internal`.
    503Voice search cannot be served right now; typed search keeps working. The code is `api_stt_off`.

    Alignment

    When each section and word of a text is spoken in a recording.

    POST/v1/uploads

    Upload a recording to align

    Takes a recording of up to 200 MB as the request body, with its type.
    One alignment takes up to an hour of audio, or 10 minutes with a test key.
    Uploads are kept for 7 days (a day with a test key), long enough to align
    them; this is not a storage service.

    Limited to 30 a minute per key.

    Request Body

    Content-Type: audio/mpeg

    The recording itself as the body, with its type in Content-Type. MP3, WAV, M4A, OGG, WebM and FLAC are read.

    Responses

    201The upload was kept. Use its id to start an alignment.
    400The request could not be used. `invalid_request` when a field is missing or has the wrong type; `bad_request` when the audio or a value could not be read.
    {
      "error": {
        "code": "bad_request",
        "problem": "The recording is 41 seconds long.",
        "reason": "Voice search takes a spoken request of up to 30 seconds.",
        "fix": "Send a shorter recording; for long audio use alignment."
      }
    }
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    413The body is over the limit: 200 MB for an upload, 10 MB for voice search. The code is `request_error`.
    415The Content-Type is not one of the audio types read. The code is `request_error`.
    429A per-key limit for the minute was reached (`api_rate_limited`; the reason names the limit), or a test key used up its account's daily allowance (`api_test_quota`; the fix says when it resets).
    {
      "error": {
        "code": "api_rate_limited",
        "problem": "Too many search requests in a minute for this key.",
        "reason": "Each key may make 120 a minute.",
        "fix": "Wait a moment, or ask Fellowverse for a higher limit."
      }
    }
    500Something went wrong on our side. The code is `internal`.
    POST/v1/alignments

    Align an upload to the text it reads

    Starts aligning an upload to its text, given as sections with your own
    ids: paragraphs, sentences, verses or lines. The job runs in the
    background; poll GET /v1/jobs/{id} for the result.

    Announcements and closing lines set apart by a pause are found and left
    out of the sections. Wording that differs slightly from your text is
    tolerated.

    Leave out segments for a Bible reading: the passage is identified from
    what is said, from any verse to any verse and across chapters, and its
    verses become the sections. The result then holds passage. A recording
    that is not a reading, or only quotes a verse, fails with the code
    api_passage_unknown. Only the World English Bible is held, so a reading
    of another translation is found only when enough of its wording agrees.

    Billed per second of audio when the job is done. Limited to 30 a minute
    per key.

    Request Body

    Content-Type: application/json

    Responses

    202The job was queued.
    400The request could not be used. `invalid_request` when a field is missing or has the wrong type; `bad_request` when the audio or a value could not be read.
    {
      "error": {
        "code": "bad_request",
        "problem": "The recording is 41 seconds long.",
        "reason": "Voice search takes a spoken request of up to 30 seconds.",
        "fix": "Send a shorter recording; for long audio use alignment."
      }
    }
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    404The upload, job or clip does not exist, or belongs to another account. The codes are `api_upload_missing`, `api_job_missing` and `api_file_missing`. `api_off` means the request went to an address that does not serve the API; use https://play.fellowverse.app.
    429A per-key limit for the minute was reached (`api_rate_limited`; the reason names the limit), or a test key used up its account's daily allowance (`api_test_quota`; the fix says when it resets).
    {
      "error": {
        "code": "api_rate_limited",
        "problem": "Too many search requests in a minute for this key.",
        "reason": "Each key may make 120 a minute.",
        "fix": "Wait a moment, or ask Fellowverse for a higher limit."
      }
    }
    500Something went wrong on our side. The code is `internal`.
    GET/v1/jobs/{id}

    See a job's state and result

    Poll until status is done or failed. A done job holds its
    result; a failed one holds its error.

    A job that fails for a reason in the request (an unreadable file, text
    that does not match) fails at once with that reason. One that fails on
    our side is tried again, up to three times, and goes back to queued
    in between. Jobs of a suspended account wait in queued until it is
    restored.

    Parameters

    NameLocationTypeDescription
    id*pathstringThe job id returned when the alignment was started.

    Responses

    200The job.
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    404The upload, job or clip does not exist, or belongs to another account. The codes are `api_upload_missing`, `api_job_missing` and `api_file_missing`. `api_off` means the request went to an address that does not serve the API; use https://play.fellowverse.app.
    500Something went wrong on our side. The code is `internal`.
    GET/v1/jobs/{id}/files/{name}

    Fetch a clip an alignment made

    Clips exist only for alignments made with clips set to true, once the
    job is done. Their names are given in the result, one per section. Clips
    are kept for 7 days after the job finishes (a day with a test key); then
    this returns 404 with
    the code api_file_expired.

    Parameters

    NameLocationTypeDescription
    id*pathstringThe job id returned when the alignment was started.
    name*pathstringThe clip's name from the job result, such as `1.mp3`.

    Responses

    200The clip, as MP3.
    400The request could not be used. `invalid_request` when a field is missing or has the wrong type; `bad_request` when the audio or a value could not be read.
    {
      "error": {
        "code": "bad_request",
        "problem": "The recording is 41 seconds long.",
        "reason": "Voice search takes a spoken request of up to 30 seconds.",
        "fix": "Send a shorter recording; for long audio use alignment."
      }
    }
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    404The upload, job or clip does not exist, or belongs to another account. The codes are `api_upload_missing`, `api_job_missing` and `api_file_missing`. `api_off` means the request went to an address that does not serve the API; use https://play.fellowverse.app.
    500Something went wrong on our side. The code is `internal`.

    Account

    Metered use, as billed.

    GET/v1/usage

    See metered use over a period

    Returns your use by product, as billed. Without dates it covers the
    current month, in UTC, to now. Called with a test key, it returns the
    account's test use instead.

    Parameters

    NameLocationTypeDescription
    fromquerystringThe start of the period, included. An ISO date or time.
    toquerystringThe end of the period, left out. An ISO date or time. Now when not given.

    Responses

    200Use in the period.
    400The request could not be used. `invalid_request` when a field is missing or has the wrong type; `bad_request` when the audio or a value could not be read.
    {
      "error": {
        "code": "bad_request",
        "problem": "The recording is 41 seconds long.",
        "reason": "Voice search takes a spoken request of up to 30 seconds.",
        "fix": "Send a shorter recording; for long audio use alignment."
      }
    }
    401The key is missing, wrong, revoked, or past the end of its grace period after a roll. The code is `api_unauthorized`, and the reason says which.
    {
      "error": {
        "code": "api_unauthorized",
        "problem": "The API key was not accepted.",
        "reason": "This key was revoked.",
        "fix": "Make a new key and use that instead."
      }
    }
    403The account is suspended (`api_suspended`), or a live key was used on an account that has test keys only (`api_live_locked`).
    500Something went wrong on our side. The code is `internal`.